This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch feature/CAMEL-23673-mcp-runtime-tools in repository https://gitbox.apache.org/repos/asf/camel.git
commit f6b6e62f23dfee5190ff3cb9242dd0a445c1a418 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jun 3 11:38:06 2026 +0200 CAMEL-23673: camel-jbang-mcp - Add runtime tools for topology, errors, thread-dump, history, stop, and receive Co-Authored-By: Claude <[email protected]> Signed-off-by: Claus Ibsen <[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(
