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 f9d629bdda4c camel-jbang - Add tui_get_processor_detail MCP tool
f9d629bdda4c is described below

commit f9d629bdda4ca48b490486a19a4bb1afb8a4c431
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 24 10:56:35 2026 +0200

    camel-jbang - Add tui_get_processor_detail MCP tool
    
    Add a new MCP tool to the TUI that exposes processor detail data
    (EIP configuration, options, catalog documentation) as structured JSON
    for AI agents. This is the programmatic equivalent of the Diagram tab's
    detail panel, making EIP and component configuration accessible to AI
    without parsing visual output.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/tui/McpFacade.java     |  27 ++++
 .../jbang/core/commands/tui/TuiToolRegistry.java   | 152 ++++++++++++++++++++-
 2 files changed, 178 insertions(+), 1 deletion(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 8b27c64d458f..044ee50c679b 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -479,6 +479,33 @@ class McpFacade {
         }
     }
 
+    JsonObject getProcessorDetail(String routeId) {
+        String pid = ctx.selectedPid;
+        if (pid == null) {
+            JsonObject err = new JsonObject();
+            err.put("error", "No integration selected");
+            return err;
+        }
+        try {
+            Path outputFile = ctx.getOutputFile(pid);
+            PathUtils.deleteFile(outputFile);
+
+            JsonObject action = new JsonObject();
+            action.put("action", "processor-detail");
+            action.put("routeId", routeId != null ? routeId : "*");
+            Path actionFile = ctx.getActionFile(pid);
+            PathUtils.writeTextSafely(action.toJson(), actionFile);
+
+            JsonObject response = TuiHelper.pollJsonResponse(outputFile, 5000);
+            PathUtils.deleteFile(outputFile);
+            return response;
+        } catch (Exception e) {
+            JsonObject err = new JsonObject();
+            err.put("error", e.getMessage());
+            return err;
+        }
+    }
+
     // ---- Diagram navigation ----
 
     String navigateDiagramToRoute(String routeId) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
index 981ac876bbd5..eeffc94d43d5 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiToolRegistry.java
@@ -79,7 +79,7 @@ class TuiToolRegistry {
     }
 
     /**
-     * Returns all 39 tool definitions. The result is cached since it is 
immutable.
+     * Returns all 40 tool definitions. The result is cached since it is 
immutable.
      */
     List<ToolDef> getToolDefinitions() {
         List<ToolDef> tools = cachedTools;
@@ -136,6 +136,7 @@ class TuiToolRegistry {
             case "tui_animate" -> callAnimate(args);
             case "tui_animate_status" -> callAnimateStatus(args);
             case "tui_catalog_doc" -> callCatalogDoc(args);
+            case "tui_get_processor_detail" -> callGetProcessorDetail(args);
             case "tui_list_examples" -> callListExamples(args);
             case "tui_run_example" -> callRunExample(args);
             default -> throw new IllegalArgumentException("Unknown tool: " + 
name);
@@ -576,6 +577,20 @@ class TuiToolRegistry {
                                                            + "Only used when 
includeOptions is true.")),
                 List.of("name"))));
 
+        tools.add(toToolDef(toolDef(
+                "tui_get_processor_detail",
+                "Returns configured options for all processors in a route as 
structured JSON. "
+                                            + "Each processor entry includes 
type, id, endpointUri (for from/to), "
+                                            + "and the 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. "
+                                            + "This is the programmatic 
equivalent of the Diagram tab's detail panel.",
+                Map.of("routeId", propDef("string",
+                        "Route ID to inspect (use * for all routes). Defaults 
to * if omitted."),
+                        "includeDocs", propDef("boolean",
+                                "If true, enrich each processor's options with 
documentation from the Camel catalog "
+                                                          + "(description, 
type, group, defaultValue, required, deprecated, enum values)")))));
+
         // --- Example tools ---
 
         tools.add(toToolDef(toolDef(
@@ -1887,6 +1902,141 @@ class TuiToolRegistry {
         return o;
     }
 
+    private String callGetProcessorDetail(Map<String, Object> args) {
+        String routeId = args.get("routeId") instanceof String v ? v : "*";
+        boolean includeDocs = Boolean.TRUE.equals(args.get("includeDocs"));
+
+        JsonObject response = facade.getProcessorDetail(routeId);
+        if (response == null) {
+            return "{\"error\": \"No response from integration\"}";
+        }
+        if (response.containsKey("error")) {
+            return Jsoner.serialize(response);
+        }
+
+        if (includeDocs) {
+            enrichProcessorDetailWithDocs(response);
+        }
+        return Jsoner.serialize(response);
+    }
+
+    private void enrichProcessorDetailWithDocs(JsonObject json) {
+        String version = facade.getSelectedCamelVersion();
+        CamelCatalog catalog;
+        try {
+            catalog = CatalogLoader.loadCatalog(null, version, true);
+        } catch (Exception e) {
+            return;
+        }
+        if (catalog == null) {
+            return;
+        }
+
+        JsonArray routes = (JsonArray) json.get("routes");
+        if (routes != null) {
+            for (Object routeObj : routes) {
+                if (routeObj instanceof JsonObject routeJson) {
+                    enrichRouteProcessors(routeJson, catalog);
+                }
+            }
+        } else {
+            enrichRouteProcessors(json, catalog);
+        }
+    }
+
+    private static void enrichRouteProcessors(JsonObject routeJson, 
CamelCatalog catalog) {
+        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)) {
+                enrichComponentProcessorOptions(processor, opts, catalog);
+            } else {
+                enrichEipProcessorOptions(processor, type, opts, catalog);
+            }
+        }
+    }
+
+    private static void enrichComponentProcessorOptions(JsonObject processor, 
JsonObject opts, CamelCatalog catalog) {
+        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 = catalog.componentModel(scheme);
+        if (model == null) {
+            return;
+        }
+        processor.put("componentDescription", model.getDescription());
+
+        if (opts != null && model.getEndpointOptions() != null) {
+            JsonObject optDocs = new JsonObject();
+            for (BaseOptionModel opt : model.getEndpointOptions()) {
+                if (opts.containsKey(opt.getName())) {
+                    optDocs.put(opt.getName(), buildProcessorOptionDoc(opt));
+                }
+            }
+            if (!optDocs.isEmpty()) {
+                processor.put("optionDocs", optDocs);
+            }
+        }
+    }
+
+    private static void enrichEipProcessorOptions(JsonObject processor, String 
type, JsonObject opts, CamelCatalog catalog) {
+        EipModel model = catalog.eipModel(type);
+        if (model == null) {
+            return;
+        }
+        processor.put("eipDescription", model.getDescription());
+
+        if (opts != null && model.getOptions() != null) {
+            JsonObject optDocs = new JsonObject();
+            for (BaseOptionModel opt : model.getOptions()) {
+                if (opts.containsKey(opt.getName())) {
+                    optDocs.put(opt.getName(), buildProcessorOptionDoc(opt));
+                }
+            }
+            if (!optDocs.isEmpty()) {
+                processor.put("optionDocs", optDocs);
+            }
+        }
+    }
+
+    private static JsonObject buildProcessorOptionDoc(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", toJsonArray(opt.getEnums()));
+        }
+        return doc;
+    }
+
     @SuppressWarnings("unchecked")
     private String callListExamples(Map<String, Object> args) {
         List<JsonObject> catalog = exampleCatalog;

Reply via email to