This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new e364973ad061 CAMEL-23679: camel-tui - Add structured data access MCP
tools
e364973ad061 is described below
commit e364973ad0617a6e27cf4feee6512a82f8930d8f
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jun 3 17:20:19 2026 +0200
CAMEL-23679: camel-tui - Add structured data access MCP tools
Add 6 new MCP tools to the TUI MCP server for structured JSON data access:
tui_get_table, tui_action, tui_get_log, tui_get_errors, tui_get_diagram,
and tui_send_message. Implements getTableDataAsJson() across 13 tab classes
(Overview, Routes, Endpoints, Health, Consumers, Errors, Inflight, Log,
Diagram, HTTP, CircuitBreaker, Configuration, Threads) so AI agents can
access tab data as typed JSON instead of parsing rendered screen text.
Also adds executeActionByName() to ActionsPopup and sendMessage() to
RuntimeHelper for direct IPC message sending.
Closes #23734
---
.../camel/dsl/jbang/core/common/RuntimeHelper.java | 19 ++++
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 36 +++++++
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 42 ++++++++
.../jbang/core/commands/tui/CircuitBreakerTab.java | 37 +++++++
.../jbang/core/commands/tui/ConfigurationTab.java | 31 ++++++
.../dsl/jbang/core/commands/tui/ConsumersTab.java | 34 ++++++
.../jbang/core/commands/tui/DiagramSupport.java | 4 +
.../dsl/jbang/core/commands/tui/DiagramTab.java | 14 +++
.../dsl/jbang/core/commands/tui/EndpointsTab.java | 38 +++++++
.../dsl/jbang/core/commands/tui/ErrorsTab.java | 43 ++++++++
.../dsl/jbang/core/commands/tui/HealthTab.java | 30 ++++++
.../camel/dsl/jbang/core/commands/tui/HttpTab.java | 43 ++++++++
.../dsl/jbang/core/commands/tui/InflightTab.java | 30 ++++++
.../camel/dsl/jbang/core/commands/tui/LogTab.java | 42 ++++++++
.../dsl/jbang/core/commands/tui/MonitorTab.java | 5 +
.../dsl/jbang/core/commands/tui/OverviewTab.java | 35 ++++++
.../dsl/jbang/core/commands/tui/RoutesTab.java | 35 ++++++
.../dsl/jbang/core/commands/tui/ThreadsTab.java | 35 ++++++
.../dsl/jbang/core/commands/tui/TuiMcpServer.java | 117 +++++++++++++++++++++
19 files changed, 670 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java
index 94f7585ab1fb..3e8bdbaa6419 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/RuntimeHelper.java
@@ -253,6 +253,25 @@ public final class RuntimeHelper {
return readStatusFromFile(historyFile);
}
+ public static JsonObject sendMessage(long pid, String endpoint, String
body, String headers) {
+ String result = executeAction(pid, "send", root -> {
+ root.put("endpoint", endpoint);
+ if (body != null) {
+ root.put("body", body);
+ }
+ if (headers != null) {
+ root.put("headers", headers);
+ }
+ });
+ try {
+ return (JsonObject) Jsoner.deserialize(result);
+ } catch (Exception e) {
+ JsonObject wrapper = new JsonObject();
+ wrapper.put("result", result);
+ return wrapper;
+ }
+ }
+
public static JsonObject readStatusFromFile(Path path) {
try {
if (Files.exists(path) && path.toFile().length() > 0) {
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 1fab0476680f..c37c0738799c 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
@@ -2034,6 +2034,42 @@ class ActionsPopup {
record InfraServiceEntry(String alias, String description, List<String>
implementations, boolean running) {
}
+ boolean executeActionByName(String name) {
+ if (name == null || name.isBlank()) {
+ return false;
+ }
+ String normalized = name.replace("-", "_").toUpperCase();
+ Action action;
+ try {
+ action = Action.valueOf(normalized);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (action) {
+ case RESET_STATS -> {
+ if (resetStatsAction != null) {
+ resetStatsAction.run();
+ }
+ }
+ case RESET_SCREEN -> {
+ if (resetScreenAction != null) {
+ resetScreenAction.run();
+ }
+ }
+ case SCREENSHOT -> screenshotAction.run();
+ case SHOW_KEYSTROKES -> toggleKeystrokes.run();
+ case TAPE_RECORDING -> toggleTapeRecording.run();
+ case DOCTOR -> doctorPopup.open();
+ case CAPTION -> captionOverlay.openInline();
+ case MCP_INFO -> openMcpInfo();
+ case MCP_LOG -> openMcpLog();
+ default -> {
+ return false;
+ }
+ }
+ return true;
+ }
+
private record PendingLaunch(String name, Process process, Path
outputFile, long startTime) {
}
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 737b0dc326db..f3f03a59f330 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
@@ -79,6 +79,7 @@ import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.dsl.jbang.core.common.RuntimeHelper;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -2822,6 +2823,47 @@ public class CamelMonitor extends CamelCommand {
return null;
}
+ JsonObject getTableData(String tabName) {
+ if (tabName != null && !tabName.isBlank()) {
+ String prev = getActiveTabName();
+ String switched = navigateToTab(tabName);
+ if (switched == null) {
+ return null;
+ }
+ }
+ MonitorTab tab = activeTab();
+ return tab != null ? tab.getTableDataAsJson() : null;
+ }
+
+ boolean executeAction(String actionName) {
+ return actionsPopup.executeActionByName(actionName);
+ }
+
+ JsonObject getLogData(int limit, String filter, String level) {
+ return logTab.getLogDataAsJson(limit, filter, level);
+ }
+
+ JsonObject getDiagramData() {
+ MonitorTab tab = activeTab();
+ if (tab instanceof DiagramTab dt) {
+ return dt.getTableDataAsJson();
+ }
+ return diagramTab.getTableDataAsJson();
+ }
+
+ JsonObject sendMessage(String endpoint, String body, String headers) {
+ if (ctx.selectedPid == null) {
+ return null;
+ }
+ long pid;
+ try {
+ pid = Long.parseLong(ctx.selectedPid);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ return RuntimeHelper.sendMessage(pid, endpoint, body, headers);
+ }
+
private record PendingKey(KeyEvent event, long fireAt) {
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
index d33f39a76617..200b5046c510 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CircuitBreakerTab.java
@@ -39,6 +39,8 @@ import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -520,4 +522,39 @@ class CircuitBreakerTab implements MonitorTab {
- `S` — reverse sort order
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "CircuitBreaker");
+ JsonArray rows = new JsonArray();
+ for (CircuitBreakerInfo cb : info.circuitBreakers) {
+ JsonObject row = new JsonObject();
+ row.put("routeId", cb.routeId);
+ row.put("id", cb.id);
+ row.put("component", cb.component);
+ row.put("state", cb.state);
+ row.put("bufferedCalls", cb.bufferedCalls);
+ row.put("successfulCalls", cb.successfulCalls);
+ row.put("failedCalls", cb.failedCalls);
+ row.put("notPermittedCalls", cb.notPermittedCalls);
+ row.put("failureRate", cb.failureRate);
+ row.put("total", cb.total);
+ row.put("totalFailed", cb.totalFailed);
+ row.put("meanTime", cb.meanTime);
+ row.put("minTime", cb.minTime);
+ row.put("maxTime", cb.maxTime);
+ row.put("inflight", cb.inflight);
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.circuitBreakers.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
index 5bab0d7e6d57..b57eb2c068f7 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConfigurationTab.java
@@ -36,6 +36,8 @@ import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.scrollbar.Scrollbar;
import dev.tamboui.widgets.scrollbar.ScrollbarState;
import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -305,4 +307,33 @@ class ConfigurationTab implements MonitorTab {
- `Esc` — back
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Configuration");
+ JsonArray rows = new JsonArray();
+ for (ConfigProperty cp : info.configProperties) {
+ JsonObject row = new JsonObject();
+ row.put("key", cp.key);
+ row.put("value", cp.value);
+ if (cp.defaultValue != null) {
+ row.put("defaultValue", cp.defaultValue);
+ }
+ if (cp.source != null) {
+ row.put("source", cp.source);
+ }
+ if (cp.location != null) {
+ row.put("location", cp.location);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.configProperties.size());
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
index 37ba385ea62c..b4a147a883a4 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTab.java
@@ -34,6 +34,8 @@ import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -395,4 +397,36 @@ class ConsumersTab implements MonitorTab {
- `S` — reverse sort order
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Consumers");
+ JsonArray rows = new JsonArray();
+ for (ConsumerInfo ci : info.consumers) {
+ JsonObject row = new JsonObject();
+ row.put("id", ci.id);
+ row.put("uri", ci.uri);
+ row.put("state", ci.state);
+ row.put("className", ci.className);
+ row.put("scheduled", ci.scheduled);
+ row.put("inflight", ci.inflight);
+ if (ci.totalCounter != null) {
+ row.put("totalCounter", ci.totalCounter);
+ }
+ if (ci.polling != null) {
+ row.put("polling", ci.polling);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.consumers.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
index 43985f4db3fc..193419ff8e52 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java
@@ -84,6 +84,10 @@ class DiagramSupport {
private int cropH = -1;
private final AtomicBoolean loading = new AtomicBoolean(false);
+ List<String> getLines() {
+ return lines;
+ }
+
boolean isShowDiagram() {
return showDiagram;
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
index 3c08ef6fbbee..034485a3729f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java
@@ -28,6 +28,7 @@ import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -276,4 +277,17 @@ class DiagramTab implements MonitorTab {
- `Esc` — close diagram
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ List<String> lines = diagram.getLines();
+ if (lines == null || lines.isEmpty()) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Diagram");
+ result.put("diagram", String.join("\n", lines));
+ result.put("lines", lines.size());
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
index d34f07644234..db5b29d7b7a3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/EndpointsTab.java
@@ -41,6 +41,8 @@ import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -749,4 +751,40 @@ class EndpointsTab implements MonitorTab {
- `f` — cycle filter mode
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Endpoints");
+ JsonArray rows = new JsonArray();
+ for (EndpointInfo ei : info.endpoints) {
+ JsonObject row = new JsonObject();
+ row.put("uri", ei.uri);
+ row.put("component", ei.component);
+ row.put("direction", ei.direction);
+ row.put("routeId", ei.routeId);
+ row.put("hits", ei.hits);
+ row.put("remote", ei.remote);
+ row.put("stub", ei.stub);
+ if (ei.meanBodySize >= 0) {
+ row.put("meanBodySize", ei.meanBodySize);
+ }
+ if (ei.maxBodySize >= 0) {
+ row.put("maxBodySize", ei.maxBodySize);
+ }
+ if (ei.meanHeadersSize >= 0) {
+ row.put("meanHeadersSize", ei.meanHeadersSize);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.endpoints.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
index 2b817ab500ad..b6acc9c6ff05 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ErrorsTab.java
@@ -36,6 +36,8 @@ import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
import org.apache.camel.diagram.RouteDiagramHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -550,4 +552,45 @@ class ErrorsTab implements MonitorTab {
- `Esc` — back to list
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Errors");
+ JsonArray rows = new JsonArray();
+ for (ErrorInfo ei : info.errors) {
+ JsonObject row = new JsonObject();
+ row.put("routeId", ei.routeId);
+ row.put("nodeId", ei.nodeId);
+ row.put("exchangeId", ei.exchangeId);
+ row.put("handled", ei.handled);
+ row.put("timestamp", ei.timestamp);
+ row.put("elapsed", ei.elapsed);
+ if (ei.exceptionType != null) {
+ row.put("exceptionType", ei.exceptionType);
+ }
+ if (ei.exceptionMessage != null) {
+ row.put("exceptionMessage", ei.exceptionMessage);
+ }
+ if (ei.stackTrace != null) {
+ row.put("stackTrace", ei.stackTrace);
+ }
+ if (ei.body != null) {
+ row.put("body", ei.body);
+ }
+ if (!ei.headers.isEmpty()) {
+ row.put("headers", new JsonObject(ei.headers));
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.errors.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
index b8645f9adacd..3be1eaa89f68 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HealthTab.java
@@ -32,6 +32,8 @@ import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -282,4 +284,32 @@ class HealthTab implements MonitorTab {
- `S` — reverse sort order
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Health");
+ JsonArray rows = new JsonArray();
+ for (HealthCheckInfo hi : info.healthChecks) {
+ JsonObject row = new JsonObject();
+ row.put("group", hi.group);
+ row.put("name", hi.name);
+ row.put("state", hi.state);
+ row.put("readiness", hi.readiness);
+ row.put("liveness", hi.liveness);
+ if (hi.message != null) {
+ row.put("message", hi.message);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.healthChecks.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index 29d379af96b3..4f14b398e58e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -1625,4 +1625,47 @@ class HttpTab implements MonitorTab {
- `Esc` — close probe view
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "HTTP");
+ JsonArray rows = new JsonArray();
+ for (HttpEndpointInfo hi : info.httpEndpoints) {
+ JsonObject row = new JsonObject();
+ row.put("method", hi.method);
+ row.put("path", hi.path);
+ row.put("url", hi.url);
+ row.put("hits", hi.hits);
+ row.put("routeId", hi.routeId);
+ row.put("state", hi.state);
+ row.put("fromRest", hi.fromRest);
+ row.put("contractFirst", hi.contractFirst);
+ if (hi.consumes != null) {
+ row.put("consumes", hi.consumes);
+ }
+ if (hi.produces != null) {
+ row.put("produces", hi.produces);
+ }
+ if (hi.description != null) {
+ row.put("description", hi.description);
+ }
+ if (hi.operationId != null) {
+ row.put("operationId", hi.operationId);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.httpEndpoints.size());
+ if (info.httpServer != null) {
+ result.put("httpServer", info.httpServer);
+ }
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
index 80704fb410da..474aede450f3 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/InflightTab.java
@@ -36,6 +36,8 @@ import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -310,4 +312,32 @@ class InflightTab implements MonitorTab {
- `Esc` — back
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Inflight");
+ JsonArray rows = new JsonArray();
+ for (InflightInfo ii : info.inflightExchanges) {
+ JsonObject row = new JsonObject();
+ row.put("exchangeId", ii.exchangeId);
+ row.put("fromRouteId", ii.fromRouteId);
+ row.put("fromRemoteEndpoint", ii.fromRemoteEndpoint);
+ row.put("atRouteId", ii.atRouteId);
+ row.put("nodeId", ii.nodeId);
+ row.put("elapsed", ii.elapsed);
+ row.put("duration", ii.duration);
+ row.put("blocked", ii.blocked);
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.inflightExchanges.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
index bb2bb659dfb9..e114eff4a211 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LogTab.java
@@ -53,6 +53,7 @@ import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.scrollbar.Scrollbar;
import dev.tamboui.widgets.scrollbar.ScrollbarState;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
+import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
@@ -847,4 +848,45 @@ class LogTab implements MonitorTab {
- `Esc` — clear find / back
""";
}
+
+ JsonObject getLogDataAsJson(int limit, String filter, String level) {
+ List<LogEntry> entries = filteredLogEntries;
+ if (entries == null || entries.isEmpty()) {
+ JsonObject result = new JsonObject();
+ result.put("lines", new JsonArray());
+ result.put("totalLines", 0);
+ result.put("returnedLines", 0);
+ return result;
+ }
+
+ List<LogEntry> filtered = new ArrayList<>();
+ for (int i = entries.size() - 1; i >= 0 && filtered.size() < limit;
i--) {
+ LogEntry e = entries.get(i);
+ if (level != null && !level.isBlank() &&
!level.equalsIgnoreCase(e.level)) {
+ continue;
+ }
+ if (filter != null && !filter.isBlank()
+ &&
!e.message.toLowerCase().contains(filter.toLowerCase())) {
+ continue;
+ }
+ filtered.add(e);
+ }
+
+ JsonObject result = new JsonObject();
+ JsonArray rows = new JsonArray();
+ for (LogEntry e : filtered) {
+ JsonObject row = new JsonObject();
+ row.put("time", e.time);
+ row.put("level", e.level);
+ if (e.logger != null) {
+ row.put("logger", e.logger);
+ }
+ row.put("message", e.message);
+ rows.add(row);
+ }
+ result.put("lines", rows);
+ result.put("totalLines", entries.size());
+ result.put("returnedLines", filtered.size());
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
index 1b213347e4f8..4c80a484c3a8 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorTab.java
@@ -22,6 +22,7 @@ import dev.tamboui.layout.Rect;
import dev.tamboui.terminal.Frame;
import dev.tamboui.text.Span;
import dev.tamboui.tui.event.KeyEvent;
+import org.apache.camel.util.json.JsonObject;
/**
* Interface for TUI monitor tabs. Each tab handles its own events, rendering,
and footer hints.
@@ -53,4 +54,8 @@ interface MonitorTab {
default String getHelpText() {
return null;
}
+
+ default JsonObject getTableDataAsJson() {
+ return null;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index 505627de6223..a6411a3c701b 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -43,6 +43,8 @@ import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
import static
org.apache.camel.dsl.jbang.core.common.CamelCommandHelper.extractState;
@@ -791,4 +793,37 @@ class OverviewTab implements MonitorTab {
- `F3` — switch integration
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ List<IntegrationInfo> integrations = ctx.data.get();
+ if (integrations == null || integrations.isEmpty()) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Overview");
+ JsonArray rows = new JsonArray();
+ for (IntegrationInfo info : integrations) {
+ JsonObject row = new JsonObject();
+ row.put("pid", info.pid);
+ row.put("name", info.name);
+ row.put("camelVersion", info.camelVersion);
+ row.put("platform", info.platform);
+ row.put("state", info.state);
+ row.put("ready", info.ready);
+ row.put("uptime", info.uptime);
+ row.put("exchangesTotal", info.exchangesTotal);
+ row.put("failed", info.failed);
+ row.put("inflight", info.inflight);
+ row.put("throughput", info.throughput);
+ row.put("routeStarted", info.routeStarted);
+ row.put("routeTotal", info.routeTotal);
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", integrations.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
index 295dba6e591b..8fcf67c03807 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/RoutesTab.java
@@ -1283,4 +1283,39 @@ class RoutesTab implements MonitorTab {
- `Esc` — back to route list
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Routes");
+ JsonArray rows = new JsonArray();
+ for (RouteInfo ri : info.routes) {
+ JsonObject row = new JsonObject();
+ row.put("routeId", ri.routeId);
+ row.put("from", ri.from);
+ row.put("state", ri.state);
+ row.put("uptime", ri.uptime);
+ row.put("total", ri.total);
+ row.put("failed", ri.failed);
+ row.put("inflight", ri.inflight);
+ row.put("mean", ri.meanTime);
+ row.put("max", ri.maxTime);
+ row.put("min", ri.minTime);
+ row.put("last", ri.lastTime);
+ row.put("throughput", ri.throughput);
+ if (ri.group != null) {
+ row.put("group", ri.group);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.routes.size());
+ Integer sel = routeTableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
index 8f9a311499be..e2c3b73da672 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ThreadsTab.java
@@ -576,4 +576,39 @@ class ThreadsTab implements MonitorTab {
- `Esc` — back
""";
}
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ List<ThreadData> threads = sortedThreads();
+ if (threads.isEmpty()) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Threads");
+ JsonArray rows = new JsonArray();
+ for (ThreadData td : threads) {
+ JsonObject row = new JsonObject();
+ row.put("id", td.id);
+ row.put("name", td.name);
+ row.put("state", td.state);
+ row.put("blockedCount", td.blockedCount);
+ row.put("blockedTime", td.blockedTime);
+ row.put("waitedCount", td.waitedCount);
+ row.put("waitedTime", td.waitedTime);
+ if (td.lockName != null) {
+ row.put("lockName", td.lockName);
+ }
+ if (td.stackTrace != null && !td.stackTrace.isEmpty()) {
+ JsonArray st = new JsonArray();
+ st.addAll(td.stackTrace);
+ row.put("stackTrace", st);
+ }
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", allThreads.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
}
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 a1dc64ae002f..c93da5e0685b 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
@@ -347,6 +347,49 @@ class TuiMcpServer {
+ "The underlying content is unchanged since
drawing is an overlay.",
Map.of()));
+ // --- Structured data tools ---
+
+ toolList.add(toolDef(
+ "tui_get_table",
+ "Returns the currently visible table data as structured JSON
with typed values. "
+ + "Much more reliable than parsing screen
text. "
+ + "Returns tab name, rows array with all
fields, totalRows, and selectedIndex.",
+ Map.of("tab", propDef("string",
+ "Tab name to get data from (e.g. 'Routes',
'Endpoints', 'Health'). "
+ + "If omitted, uses the active
tab."))));
+ toolList.add(toolDef(
+ "tui_action",
+ "Invokes a TUI action by name, bypassing fragile key
sequences. "
+ + "Actions: reset-stats, reset-screen,
screenshot, show-keystrokes, "
+ + "tape-recording, doctor, caption, mcp-info,
mcp-log.",
+ Map.of("action", propDef("string", "Action name in kebab-case
(e.g. 'reset-stats', 'screenshot')")),
+ List.of("action")));
+ toolList.add(toolDef(
+ "tui_get_log",
+ "Returns recent log lines as structured data with optional
filtering. "
+ + "Returns newest entries first.",
+ Map.of("limit", propDef("integer", "Maximum lines to return
(default 50)"),
+ "filter", propDef("string", "Case-insensitive
substring filter on log message"),
+ "level", propDef("string", "Filter by log level (INFO,
WARN, ERROR, DEBUG, TRACE)"))));
+ toolList.add(toolDef(
+ "tui_get_errors",
+ "Returns structured error data from the Errors tab. "
+ + "Includes routeId, exchangeId, exception
details, stack trace, body, and headers.",
+ Map.of()));
+ toolList.add(toolDef(
+ "tui_get_diagram",
+ "Returns the route topology diagram as text. "
+ + "Shows the ASCII/Unicode art diagram of
routes and their connections.",
+ Map.of()));
+ toolList.add(toolDef(
+ "tui_send_message",
+ "Sends a message to a Camel endpoint in the selected
integration. "
+ + "Uses the file-based IPC protocol to
deliver the message directly.",
+ Map.of("endpoint", propDef("string", "Endpoint URI to send to
(e.g. 'direct:myRoute', 'seda:queue')"),
+ "body", propDef("string", "Message body to send"),
+ "headers", propDef("string", "Message headers as
key=value pairs separated by newlines")),
+ List.of("endpoint")));
+
JsonObject result = new JsonObject();
result.put("tools", toolList);
return result;
@@ -380,6 +423,12 @@ class TuiMcpServer {
case "tui_sleep" -> callSleep(args);
case "tui_draw" -> callDraw(args);
case "tui_draw_clear" -> callDrawClear();
+ case "tui_get_table" -> callGetTable(args);
+ case "tui_action" -> callAction(args);
+ case "tui_get_log" -> callGetLog(args);
+ case "tui_get_errors" -> callGetErrors();
+ case "tui_get_diagram" -> callGetDiagram();
+ case "tui_send_message" -> callSendMessage(args);
default -> {
isError = true;
yield "Unknown tool: " + toolName;
@@ -844,6 +893,74 @@ class TuiMcpServer {
return "Drawing cleared";
}
+ private String callGetTable(Map<String, Object> args) {
+ String tab = args.get("tab") instanceof String s ? s : null;
+ JsonObject data = monitor.getTableData(tab);
+ if (data == null) {
+ return "No table data available" + (tab != null ? " for tab: " +
tab : "");
+ }
+ return Jsoner.serialize(data);
+ }
+
+ private String callAction(Map<String, Object> args) {
+ String action = (String) args.get("action");
+ if (action == null || action.isBlank()) {
+ return "Error: action is required";
+ }
+ boolean executed = monitor.executeAction(action);
+ if (executed) {
+ return "Action '" + action + "' executed";
+ }
+ return "Unknown or unsupported action: " + action
+ + ". Available: reset-stats, reset-screen, screenshot,
show-keystrokes, "
+ + "tape-recording, doctor, caption, mcp-info, mcp-log";
+ }
+
+ private String callGetLog(Map<String, Object> args) {
+ int limit = 50;
+ if (args.get("limit") instanceof Number n) {
+ limit = Math.max(1, Math.min(1000, n.intValue()));
+ }
+ String filter = args.get("filter") instanceof String s ? s : null;
+ String level = args.get("level") instanceof String s ? s : null;
+ JsonObject data = monitor.getLogData(limit, filter, level);
+ return Jsoner.serialize(data);
+ }
+
+ private String callGetErrors() {
+ JsonObject data = monitor.getTableData("Errors");
+ if (data == null) {
+ JsonObject empty = new JsonObject();
+ empty.put("tab", "Errors");
+ empty.put("rows", new JsonArray());
+ empty.put("totalRows", 0);
+ return Jsoner.serialize(empty);
+ }
+ return Jsoner.serialize(data);
+ }
+
+ private String callGetDiagram() {
+ JsonObject data = monitor.getDiagramData();
+ if (data == null) {
+ return "No diagram available. Navigate to the Diagram tab first.";
+ }
+ return Jsoner.serialize(data);
+ }
+
+ private String callSendMessage(Map<String, Object> args) {
+ String endpoint = (String) args.get("endpoint");
+ if (endpoint == null || endpoint.isBlank()) {
+ return "Error: endpoint is required";
+ }
+ String body = args.get("body") instanceof String s ? s : null;
+ String headers = args.get("headers") instanceof String s ? s : null;
+ JsonObject response = monitor.sendMessage(endpoint, body, headers);
+ if (response == null) {
+ return "Error: no integration selected or PID unavailable";
+ }
+ return Jsoner.serialize(response);
+ }
+
private static JsonArray toJsonArray(List<String> list) {
JsonArray arr = new JsonArray();
arr.addAll(list);