This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch feature/processor-detail-mcp-tool
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 9ab526172ac15120503cc1d1baf0b1e398f77d29
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 24 09:51:24 2026 +0200

    camel-jbang - Add camel_runtime_processor_detail MCP tool
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/dev-console/processor-detail.json |   4 +-
 .../impl/console/ProcessorDetailDevConsole.java    |  45 +++++--
 .../camel/cli/connector/LocalCliConnector.java     |   6 +-
 .../dsl/jbang/core/commands/ai/ToolRegistry.java   | 138 +++++++++++++++++++++
 .../dsl/jbang/core/commands/mcp/RuntimeTools.java  |  18 +++
 5 files changed, 201 insertions(+), 10 deletions(-)

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..0b565ffcb18b 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
@@ -98,15 +110,35 @@ public class ProcessorDetailDevConsole extends 
AbstractDevConsole {
             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 +146,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..152feb878a81 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,7 +28,9 @@ 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;
@@ -236,6 +238,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", "string",
+                        "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) {
+                            // return unenriched result
+                        }
+                    }
+                    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 +1008,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(

Reply via email to