You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/06/03 10:49:59 UTC
(camel) 01/01: CAMEL-23679: camel-tui - Add structured data access MCP tools
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch feature/tui-mcp-structured-tools
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 58624e178b7d22e7f321f3f2373e3cbd285cb993
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jun 3 12:49:43 2026 +0200
CAMEL-23679: camel-tui - Add structured data access MCP tools
Co-Authored-By: Claude <no...@anthropic.com>
Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
.../camel/dsl/jbang/core/common/RuntimeHelper.java | 19 ++++
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 36 +++++++
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 42 ++++++++
.../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 ++++++
.../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/TuiMcpServer.java | 117 +++++++++++++++++++++
15 files changed, 524 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 94897399442a..11bd051dc0cd 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
@@ -233,6 +233,25 @@ public final class RuntimeHelper {
}
}
+ 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/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/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/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);