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 ac745bf41b13 camel-jbang - Add camel_runtime_processor_detail MCP tool
ac745bf41b13 is described below
commit ac745bf41b13b75971fd9db53fa4448d822cf018
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jul 25 07:35:49 2026 +0200
camel-jbang - Add camel_runtime_processor_detail MCP tool
Enhanced the processor-detail dev-console to accept * as routeId,
returning processor details for all routes in a single call (default
when no routeId is given). Added new camel_runtime_processor_detail
MCP tool with includeDocs option that enriches the response with
documentation from the Camel catalog for each EIP and component
endpoint option. Updated LocalCliConnector to accept id as fallback
key and default to * when no routeId is provided.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../catalog/dev-consoles/processor-detail.json | 4 +-
.../apache/camel/dev-console/processor-detail.json | 4 +-
.../impl/console/ProcessorDetailDevConsole.java | 50 ++++++--
.../camel/cli/connector/LocalCliConnector.java | 6 +-
.../dsl/jbang/core/commands/ai/ToolRegistry.java | 142 +++++++++++++++++++++
.../dsl/jbang/core/commands/mcp/RuntimeTools.java | 18 +++
6 files changed, 210 insertions(+), 14 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/processor-detail.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/processor-detail.json
index 2ea6f8d4be50..9b0d6bdadbb9 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/processor-detail.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/processor-detail.json
@@ -18,13 +18,13 @@
"displayName": "Route Id",
"group": "query",
"label": "query",
- "required": true,
+ "required": false,
"type": "string",
"javaType": "java.lang.String",
"deprecated": false,
"autowired": false,
"secret": false,
- "description": "The route id to get processor details for"
+ "description": "The route id to get processor details for (use * for all
routes)"
}
}
}
diff --git
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/processor-detail.json
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/processor-detail.json
index 2ea6f8d4be50..9b0d6bdadbb9 100644
---
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/processor-detail.json
+++
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/processor-detail.json
@@ -18,13 +18,13 @@
"displayName": "Route Id",
"group": "query",
"label": "query",
- "required": true,
+ "required": false,
"type": "string",
"javaType": "java.lang.String",
"deprecated": false,
"autowired": false,
"secret": false,
- "description": "The route id to get processor details for"
+ "description": "The route id to get processor details for (use * for all
routes)"
}
}
}
diff --git
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDetailDevConsole.java
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDetailDevConsole.java
index 186cdd488486..a334114649e7 100644
---
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDetailDevConsole.java
+++
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDetailDevConsole.java
@@ -17,6 +17,7 @@
package org.apache.camel.impl.console;
import java.io.StringReader;
+import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
@@ -44,8 +45,8 @@ import org.apache.camel.util.json.JsonObject;
@DevConsole(name = "processor-detail", description = "Show configured options
for all processors in a route")
public class ProcessorDetailDevConsole extends AbstractDevConsole {
- @Metadata(label = "query", required = true,
- description = "The route id to get processor details for",
+ @Metadata(label = "query",
+ description = "The route id to get processor details for (use *
for all routes)",
javaType = "java.lang.String")
public static final String ROUTE_ID = "routeId";
@@ -58,10 +59,22 @@ public class ProcessorDetailDevConsole extends
AbstractDevConsole {
JsonObject json = doCallJson(options);
StringBuilder sb = new StringBuilder();
- String routeId = json.getString("routeId");
+ JsonArray routes = (JsonArray) json.get("routes");
+ if (routes != null) {
+ for (Object routeObj : routes) {
+ appendRouteText(sb, (JsonObject) routeObj);
+ }
+ } else {
+ appendRouteText(sb, json);
+ }
+ return sb.toString();
+ }
+
+ private static void appendRouteText(StringBuilder sb, JsonObject
routeJson) {
+ String routeId = routeJson.getString("routeId");
if (routeId != null) {
sb.append(String.format("Route: %s%n", routeId));
- JsonArray processors = (JsonArray) json.get("processors");
+ JsonArray processors = (JsonArray) routeJson.get("processors");
if (processors != null) {
for (Object obj : processors) {
JsonObject p = (JsonObject) obj;
@@ -75,7 +88,6 @@ public class ProcessorDetailDevConsole extends
AbstractDevConsole {
}
}
}
- return sb.toString();
}
@Override
@@ -87,26 +99,47 @@ public class ProcessorDetailDevConsole extends
AbstractDevConsole {
routeId = subPath;
}
- JsonObject root = new JsonObject();
if (routeId == null || routeId.isBlank()) {
- return root;
+ routeId = "*";
}
+ JsonObject root = new JsonObject();
+
ManagedCamelContext mcc
=
getCamelContext().getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
if (mcc == null) {
return root;
}
+ if ("*".equals(routeId)) {
+ List<ManagedRouteMBean> managedRoutes = mcc.getManagedRoutes();
+ if (managedRoutes == null || managedRoutes.isEmpty()) {
+ return root;
+ }
+ JsonArray routes = new JsonArray();
+ for (ManagedRouteMBean mr : managedRoutes) {
+ JsonObject routeJson = buildRouteDetail(mr);
+ if (routeJson != null) {
+ routes.add(routeJson);
+ }
+ }
+ root.put("routes", routes);
+ return root;
+ }
+
ManagedRouteMBean mr = mcc.getManagedRoute(routeId);
if (mr == null) {
return root;
}
+ return buildRouteDetail(mr);
+ }
+ private static JsonObject buildRouteDetail(ManagedRouteMBean mr) {
+ String routeId = mr.getRouteId();
+ JsonObject root = new JsonObject();
root.put("routeId", routeId);
JsonArray processors = new JsonArray();
- // add the "from" node (uses the route ID as its processor ID in the
diagram)
JsonObject fromEntry = new JsonObject();
fromEntry.put("id", routeId);
fromEntry.put("type", "from");
@@ -114,7 +147,6 @@ public class ProcessorDetailDevConsole extends
AbstractDevConsole {
fromEntry.put("options", new JsonObject());
processors.add(fromEntry);
- // parse the route XML to extract all processor elements with their
configured options
try {
String xml = mr.dumpRouteAsXml();
if (xml != null && !xml.isBlank()) {
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index de21d1a064d7..615f43f7907c 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -789,8 +789,12 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
.resolveById("processor-detail");
if (dc != null) {
String routeId = root.getString("routeId");
+ if (routeId == null || routeId.isBlank()) {
+ routeId = root.getString("id");
+ }
JsonObject json
- = (JsonObject) dc.call(DevConsole.MediaType.JSON,
Map.of("routeId", routeId != null ? routeId : ""));
+ = (JsonObject) dc.call(DevConsole.MediaType.JSON,
+ Map.of("routeId", routeId != null ? routeId :
"*"));
LOG.trace("Updating output file: {}", outputFile);
IOHelper.writeText(json.toJson(), outputFile);
} else {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
index 04825e196bdf..99e17e0a19ce 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ToolRegistry.java
@@ -28,11 +28,15 @@ import java.util.stream.Collectors;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.dsl.jbang.core.common.ExampleHelper;
import org.apache.camel.dsl.jbang.core.common.RuntimeHelper;
+import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
+import org.apache.camel.tooling.model.EipModel;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import static org.apache.camel.dsl.jbang.core.commands.ai.ToolDescriptor.tool;
@@ -43,6 +47,8 @@ import static
org.apache.camel.dsl.jbang.core.commands.ai.ToolDescriptor.tool;
*/
public final class ToolRegistry {
+ private static final Logger LOG =
LoggerFactory.getLogger(ToolRegistry.class);
+
private static final List<ToolDescriptor> TOOLS = new ArrayList<>();
private static final Map<String, ToolDescriptor> BY_NAME = new
LinkedHashMap<>();
@@ -236,6 +242,33 @@ public final class ToolRegistry {
root -> root.put("id", routeId != null ? routeId :
"*"));
}));
+ register(tool("get_processor_detail",
+ "Show configured options for all processors in a route. "
+ + "Returns each processor's
type, id, and configured options (attributes, expressions). "
+ + "Use includeDocs=true to
enrich the response with documentation from the Camel catalog "
+ + "for each EIP option and
component endpoint option.")
+ .param("routeId", "string", "Route ID to inspect (use * for
all routes)", false)
+ .param("includeDocs", "boolean",
+ "If true, enrich each processor's options with
documentation from the Camel catalog", false)
+ .executor((ctx, args) -> {
+ String routeId = args.get("routeId");
+ String result = ctx.executeAction("processor-detail",
+ root -> root.put("routeId", routeId != null ?
routeId : "*"));
+ String includeDocs = args.get("includeDocs");
+ if ("true".equalsIgnoreCase(includeDocs)) {
+ try {
+ Object parsed = Jsoner.deserialize(result);
+ if (parsed instanceof JsonObject jo) {
+ enrichWithDocs(jo, ctx);
+ return jo.toJson();
+ }
+ } catch (Exception e) {
+ LOG.debug("Failed to enrich processor detail with
docs: {}", e.getMessage(), e);
+ }
+ }
+ return result;
+ }));
+
register(tool("get_top_processors",
"Show top processor statistics: which processors are slowest
and most active.")
.executor((ctx, args) -> ctx.executeAction("top-processors",
null)));
@@ -979,6 +1012,115 @@ public final class ToolRegistry {
}));
}
+ // ---- Processor detail enrichment ----
+
+ private static void enrichWithDocs(JsonObject json, ToolContext ctx) {
+ CamelCatalog cat = ctx.catalog();
+ JsonArray routes = (JsonArray) json.get("routes");
+ if (routes != null) {
+ for (Object routeObj : routes) {
+ if (routeObj instanceof JsonObject routeJson) {
+ enrichRouteProcessors(routeJson, cat);
+ }
+ }
+ } else {
+ enrichRouteProcessors(json, cat);
+ }
+ }
+
+ private static void enrichRouteProcessors(JsonObject routeJson,
CamelCatalog cat) {
+ JsonArray processors = (JsonArray) routeJson.get("processors");
+ if (processors == null) {
+ return;
+ }
+ for (Object obj : processors) {
+ if (!(obj instanceof JsonObject processor)) {
+ continue;
+ }
+ String type = processor.getString("type");
+ if (type == null) {
+ continue;
+ }
+ JsonObject opts = processor.getMap("options");
+ if ("from".equals(type) || "to".equals(type) || "toD".equals(type)
|| "wireTap".equals(type)
+ || "enrich".equals(type) || "pollEnrich".equals(type)) {
+ enrichComponentOptions(processor, opts, cat);
+ } else {
+ enrichEipOptions(processor, type, opts, cat);
+ }
+ }
+ }
+
+ private static void enrichComponentOptions(JsonObject processor,
JsonObject opts, CamelCatalog cat) {
+ String uri = processor.getString("endpointUri");
+ if (uri == null) {
+ uri = opts != null ? opts.getString("uri") : null;
+ }
+ if (uri == null) {
+ return;
+ }
+ String scheme = uri.contains(":") ? uri.substring(0, uri.indexOf(':'))
: uri;
+ ComponentModel model = cat.componentModel(scheme);
+ if (model == null) {
+ return;
+ }
+ processor.put("componentDescription", model.getDescription());
+
+ if (opts != null && model.getEndpointOptions() != null) {
+ JsonObject optionDocs = new JsonObject();
+ for (BaseOptionModel opt : model.getEndpointOptions()) {
+ if (opts.containsKey(opt.getName())) {
+ optionDocs.put(opt.getName(), buildOptionDoc(opt));
+ }
+ }
+ if (!optionDocs.isEmpty()) {
+ processor.put("optionDocs", optionDocs);
+ }
+ }
+ }
+
+ private static void enrichEipOptions(JsonObject processor, String type,
JsonObject opts, CamelCatalog cat) {
+ EipModel model = cat.eipModel(type);
+ if (model == null) {
+ return;
+ }
+ processor.put("eipDescription", model.getDescription());
+
+ if (opts != null && model.getOptions() != null) {
+ JsonObject optionDocs = new JsonObject();
+ for (BaseOptionModel opt : model.getOptions()) {
+ if (opts.containsKey(opt.getName())) {
+ optionDocs.put(opt.getName(), buildOptionDoc(opt));
+ }
+ }
+ if (!optionDocs.isEmpty()) {
+ processor.put("optionDocs", optionDocs);
+ }
+ }
+ }
+
+ private static JsonObject buildOptionDoc(BaseOptionModel opt) {
+ JsonObject doc = new JsonObject();
+ doc.put("description", opt.getDescription());
+ doc.put("type", opt.getType());
+ if (opt.getGroup() != null && !opt.getGroup().isEmpty()) {
+ doc.put("group", opt.getGroup());
+ }
+ if (opt.getDefaultValue() != null) {
+ doc.put("defaultValue", opt.getDefaultValue().toString());
+ }
+ if (opt.isRequired()) {
+ doc.put("required", true);
+ }
+ if (opt.isDeprecated()) {
+ doc.put("deprecated", true);
+ }
+ if (opt.getEnums() != null && !opt.getEnums().isEmpty()) {
+ doc.put("enum", opt.getEnums());
+ }
+ return doc;
+ }
+
// ---- Utility ----
static boolean matchesFilter(String name, String title, String
description, String filter) {
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 e89bfa66ce23..3b0d4652a899 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
@@ -221,6 +221,24 @@ public class RuntimeTools {
Map.of("routeId", routeId != null ? routeId : "*"));
}
+ @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint
= false, openWorldHint = false),
+ description = """
+ Show configured options for all processors in a route. \
+ Returns each processor's type, id, and configured options
(attributes, expressions). \
+ Use includeDocs=true to enrich the response with
documentation from the Camel catalog \
+ for each EIP option and component endpoint option.""")
+ public JsonObject camel_runtime_processor_detail(
+ @ToolArg(description = NAME_OR_PID_DESC) String nameOrPid,
+ @ToolArg(description = "Route ID to inspect (use * for all
routes)") String routeId,
+ @ToolArg(description = "If true, enrich each processor's options
with documentation from the Camel catalog") Boolean includeDocs) {
+ Map<String, String> args = new HashMap<>();
+ args.put("routeId", routeId != null ? routeId : "*");
+ if (includeDocs != null && includeDocs) {
+ args.put("includeDocs", "true");
+ }
+ return delegateToRegistry("get_processor_detail", nameOrPid, args);
+ }
+
@Tool(annotations = @Tool.Annotations(readOnlyHint = false,
destructiveHint = true, openWorldHint = false),
description = "Control a route: start, stop, suspend, or resume it.")
public JsonObject camel_runtime_route_control(