This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch tui-mcp-CAMEL-23606 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 20f572763943dfc58cc132b8bda723d5bc71550f Author: Claus Ibsen <[email protected]> AuthorDate: Sun May 24 19:24:43 2026 +0200 CAMEL-23606: camel-tui - MCP navigation tool for tab switching and integration selection Adds tui_navigate MCP tool so AI agents can switch tabs and select integrations without key injection. Returns available options on error so the agent can self-correct. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../dsl/jbang/core/commands/tui/ActionsPopup.java | 7 +++- .../dsl/jbang/core/commands/tui/CamelMonitor.java | 37 ++++++++++++++++ .../dsl/jbang/core/commands/tui/TuiMcpServer.java | 49 ++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java index 0a2d768614e0..bfce21ee00ef 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java @@ -699,7 +699,8 @@ class ActionsPopup { + "| `tui_get_screen` | Returns the current screen content as text |\n" + "| `tui_get_events` | Returns recent key presses and navigation events |\n" + "| `tui_get_state` | Returns active tab, selected integration, etc. |\n" - + "| `tui_show_caption` | Shows a message on the TUI screen |\n\n" + + "| `tui_show_caption` | Shows a message on the TUI screen |\n" + + "| `tui_navigate` | Switch tabs and select integrations |\n\n" + "## Setup for Claude Code\n\n" + "Run this command to connect Claude Code to the TUI:\n\n" + " claude mcp add --transport http camel-tui " + url + "\n\n" @@ -720,7 +721,9 @@ class ActionsPopup { + "- \"What tab am I on and what integration is selected?\"\n" + "- \"What keys did I press in the last minute?\"\n" + "- \"What color is the throughput chart?\"\n" - + "- \"Show me a message on the TUI screen\"\n"; + + "- \"Show me a message on the TUI screen\"\n" + + "- \"Switch to the Health tab\"\n" + + "- \"Select the myApp integration\"\n"; docTitle = "MCP Info"; docScroll = 0; showDocViewer = true; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java index e11639e0a850..ce9cdb0e5248 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java @@ -3060,4 +3060,41 @@ public class CamelMonitor extends CamelCommand { captionOverlay.showCaption(text); } + String navigateToTab(String tabName) { + for (int i = 0; i < TAB_NAMES.length; i++) { + if (TAB_NAMES[i].equalsIgnoreCase(tabName)) { + handleTabKey(i); + return TAB_NAMES[i]; + } + } + return null; + } + + String selectIntegration(String nameOrPid) { + List<IntegrationInfo> infos = data.get(); + for (IntegrationInfo info : infos) { + if (info.vanishing) { + continue; + } + if (nameOrPid.equals(info.pid) + || (info.name != null && info.name.equalsIgnoreCase(nameOrPid))) { + ctx.selectedPid = info.pid; + ctx.infraTableFocused = false; + return info.name != null ? info.name : info.pid; + } + } + return null; + } + + List<String> getTabNames() { + return List.of(TAB_NAMES); + } + + List<String> getIntegrationNames() { + return data.get().stream() + .filter(i -> !i.vanishing) + .map(i -> i.name != null ? i.name : i.pid) + .toList(); + } + } diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java index 0d3fd0854fd9..3cfd6e483b69 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java @@ -213,6 +213,13 @@ class TuiMcpServer { + "Supports \\n for newlines.", Map.of("text", propDef("string", "The caption text to display")), List.of("text"))); + toolList.add(toolDef( + "tui_navigate", + "Navigates the TUI: switch tabs and/or select an integration. " + + "Both parameters are optional — set whichever you want to change. " + + "Tab names: Overview, Log, Routes, Consumers, Endpoints, HTTP, Health, Inspect, Circuit Breaker.", + Map.of("tab", propDef("string", "Tab to switch to (e.g. 'Routes', 'Health')"), + "integration", propDef("string", "Integration name or PID to select")))); JsonObject result = new JsonObject(); result.put("tools", toolList); @@ -239,6 +246,7 @@ class TuiMcpServer { case "tui_get_events" -> callGetEvents(args); case "tui_get_state" -> callGetState(); case "tui_show_caption" -> callShowCaption(args); + case "tui_navigate" -> callNavigate(args); default -> { isError = true; yield "Unknown tool: " + toolName; @@ -333,6 +341,47 @@ class TuiMcpServer { return "Caption displayed: " + text; } + private String callNavigate(Map<String, Object> args) { + JsonObject result = new JsonObject(); + String tab = (String) args.get("tab"); + String integration = (String) args.get("integration"); + + if (tab == null && integration == null) { + result.put("error", "Provide at least one of: tab, integration"); + result.put("availableTabs", toJsonArray(monitor.getTabNames())); + result.put("availableIntegrations", toJsonArray(monitor.getIntegrationNames())); + return Jsoner.serialize(result); + } + + if (integration != null) { + String selected = monitor.selectIntegration(integration); + if (selected != null) { + result.put("selectedIntegration", selected); + } else { + result.put("integrationError", "Not found: " + integration); + result.put("availableIntegrations", toJsonArray(monitor.getIntegrationNames())); + } + } + + if (tab != null) { + String switched = monitor.navigateToTab(tab); + if (switched != null) { + result.put("activeTab", switched); + } else { + result.put("tabError", "Unknown tab: " + tab); + result.put("availableTabs", toJsonArray(monitor.getTabNames())); + } + } + + return Jsoner.serialize(result); + } + + private static JsonArray toJsonArray(List<String> list) { + JsonArray arr = new JsonArray(); + arr.addAll(list); + return arr; + } + // --- JSON-RPC helpers --- private void sendResult(HttpExchange exchange, JsonObject request, JsonObject result) throws IOException {
