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 69601c7e9015 CAMEL-23678: camel-jbang-mcp - Add runtime tools for
topology, errors, thread-dump, history, stop, and receive (#23730)
69601c7e9015 is described below
commit 69601c7e9015c385b10c57138010d38dc1c4f4cb
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jun 3 12:48:46 2026 +0200
CAMEL-23678: camel-jbang-mcp - Add runtime tools for topology, errors,
thread-dump, history, stop, and receive (#23730)
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude <[email protected]>
---
.../camel/dsl/jbang/core/common/RuntimeHelper.java | 20 ++++++
.../jbang/core/commands/mcp/RuntimeService.java | 24 +++++++
.../dsl/jbang/core/commands/mcp/RuntimeTools.java | 74 ++++++++++++++++++++++
3 files changed, 118 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..94f7585ab1fb 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,26 @@ public final class RuntimeHelper {
}
}
+ /**
+ * Reads the error file for the given process.
+ *
+ * @return the parsed error data, or {@code null} if the file does not
exist or is unreadable
+ */
+ public static JsonObject readErrorFile(long pid) {
+ Path errorFile = CommandLineHelper.getCamelDir().resolve(pid +
"-error.json");
+ return readStatusFromFile(errorFile);
+ }
+
+ /**
+ * Reads the message history file for the given process (trace of the last
completed exchange).
+ *
+ * @return the parsed history data, or {@code null} if the file does not
exist or is unreadable
+ */
+ public static JsonObject readHistoryFile(long pid) {
+ Path historyFile = CommandLineHelper.getCamelDir().resolve(pid +
"-history.json");
+ return readStatusFromFile(historyFile);
+ }
+
public static JsonObject readStatusFromFile(Path path) {
try {
if (Files.exists(path) && path.toFile().length() > 0) {
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeService.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeService.java
index 7d349268a836..a75f5e63191b 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeService.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeService.java
@@ -99,6 +99,30 @@ public class RuntimeService {
return new JsonObject();
}
+ public JsonObject readErrorFile(long pid) {
+ JsonObject result = RuntimeHelper.readErrorFile(pid);
+ if (result == null) {
+ JsonObject empty = new JsonObject();
+ empty.put("errors", new org.apache.camel.util.json.JsonArray());
+ return empty;
+ }
+ return result;
+ }
+
+ public JsonObject readHistoryFile(long pid) {
+ JsonObject result = RuntimeHelper.readHistoryFile(pid);
+ if (result == null) {
+ JsonObject empty = new JsonObject();
+ empty.put("message", "No message history available for PID " +
pid);
+ return empty;
+ }
+ return result;
+ }
+
+ public String stopApplication(long pid) {
+ return RuntimeHelper.stopApplication(pid);
+ }
+
public JsonObject executeAction(long pid, String action,
Consumer<JsonObject> configure) {
String result = RuntimeHelper.executeAction(pid, action, configure);
if (result != null && result.startsWith("Timeout")) {
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
index 7369845a9fa4..2a00858f51d3 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java
@@ -284,6 +284,80 @@ public class RuntimeTools {
});
}
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the inter-route topology showing how routes connect to
each other \
+ and to external endpoints. Returns nodes and edges
describing the route graph.""")
+ public JsonObject camel_runtime_route_topology(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = "Include live metrics (message counts,
throughput) on nodes and edges") Boolean metric,
+ @ToolArg(description = "Include external systems (databases,
messaging brokers, etc.) as nodes") Boolean external) {
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ return runtimeService.executeAction(p.pid(), "route-topology", root ->
{
+ root.put("metric", metric != null && metric ? "true" : "false");
+ root.put("external", external != null && external ? "true" :
"false");
+ });
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get captured routing errors from the running Camel
application. \
+ Returns error details including exception, exchange context,
and route information.""")
+ public JsonObject camel_runtime_errors(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ return runtimeService.readErrorFile(p.pid());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = "Get a JVM thread dump showing thread names, states,
and stack traces.")
+ public JsonObject camel_runtime_thread_dump(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ return runtimeService.executeAction(p.pid(), "thread-dump", null);
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Get the message history trace of the last completed
exchange. \
+ This is always captured (no need to enable tracing) and
shows the single most recent exchange \
+ with its route path, processors visited, headers, body, and
timing.""")
+ public JsonObject camel_runtime_history(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ return runtimeService.readHistoryFile(p.pid());
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = true, openWorldHint = false),
+ description = """
+ Initiate graceful shutdown of a running Camel application. \
+ The application will finish processing in-flight exchanges
before stopping.""")
+ public JsonObject camel_runtime_stop(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid) {
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ String result = runtimeService.stopApplication(p.pid());
+ JsonObject response = new JsonObject();
+ response.put("result", result);
+ return response;
+ }
+
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = false, openWorldHint = false),
+ description = """
+ Receive (poll) a message from a Camel endpoint in the
running application. \
+ This is the complement to camel_runtime_send — it consumes
one message from the endpoint.""")
+ public JsonObject camel_runtime_receive(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = "Endpoint URI to receive from") String
endpoint) {
+ if (endpoint == null || endpoint.isBlank()) {
+ throw new ToolCallException("endpoint is required", null);
+ }
+ RuntimeService.ProcessInfo p =
runtimeService.findSingleProcess(nameOrPid);
+ return runtimeService.executeAction(p.pid(), "receive", root -> {
+ root.put("enabled", "true");
+ root.put("endpoint", endpoint);
+ });
+ }
+
@Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
description = "Browse messages in a Camel endpoint (e.g., browse
messages queued in a SEDA endpoint).")
public JsonObject camel_runtime_browse(