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

oscerd 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 7047210cdcf4 CAMEL-23475: camel-jbang-mcp - make ComponentDetailResult 
lean by default with option filtering (#23359)
7047210cdcf4 is described below

commit 7047210cdcf403bb4cbf907b116af2b99faf220b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed May 20 12:59:45 2026 +0200

    CAMEL-23475: camel-jbang-mcp - make ComponentDetailResult lean by default 
with option filtering (#23359)
    
    Add option filtering to camel_catalog_component_doc and split out a
    small camel_catalog_component_maven tool so callers only pay the
    maven-coordinate token cost when they actually need it.
    
    * optionsFilter: case-insensitive substring match on option name.
    * includeOptions: required | common | all (default: common, which
      excludes deprecated and advanced options).
    * ComponentDetailResult no longer carries groupId/artifactId/version;
      use camel_catalog_component_maven for those.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc    |  14 +++
 .../modules/ROOT/pages/camel-jbang-mcp.adoc        |  11 +-
 .../dsl/jbang/core/commands/mcp/CatalogTools.java  | 140 +++++++++++++++++----
 .../jbang/core/commands/mcp/CatalogToolsTest.java  |  95 ++++++++++++--
 .../commands/mcp/McpJsonSerializationTest.java     |  17 ++-
 5 files changed, 241 insertions(+), 36 deletions(-)

diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index 9830b267a59f..7327faf48188 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -63,6 +63,20 @@ auto-disables `contentCache` on resource-based components 
(such as `xslt`) whose
 the route. Set `camel.component.<name>.contentCache=true` (or pass 
`?contentCache=true` on the
 URI) to opt back in to caching during dev mode.
 
+==== camel-jbang-mcp
+
+The `camel_catalog_component_doc` tool no longer returns every component 
option by default. Two new
+arguments make the response lean by default to reduce LLM context-window 
pressure:
+
+* `optionsFilter` (optional) — case-insensitive substring match on option name.
+* `includeOptions` (optional) — one of `required`, `common`, or `all`; 
defaults to `common`, which
+  excludes deprecated options and options whose label contains `advanced`. 
Pass `all` to restore the
+  previous behaviour.
+
+The `groupId`, `artifactId`, and `version` fields have been removed from the 
`camel_catalog_component_doc`
+response. A new `camel_catalog_component_maven` tool returns just those Maven 
coordinates and should be
+called only when you actually need to add the component as a dependency.
+
 ==== camel-jbang plugins
 
 Plugins are now loaded lazily. Built-in commands that do not consume plugins
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 8103d14cbc62..a65c413b0d96 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -34,7 +34,7 @@ over the MCP protocol.
 
 == Available Tools
 
-The server exposes 27 tools organized into eleven functional areas, plus 3 
prompts that provide structured
+The server exposes 28 tools organized into eleven functional areas, plus 3 
prompts that provide structured
 multi-step workflows.
 
 === Catalog Exploration
@@ -48,8 +48,13 @@ multi-step workflows.
   runtime type (`main`, `spring-boot`, `quarkus`). Supports querying specific 
Camel versions.
 
 | `camel_catalog_component_doc`
-| Get detailed documentation for a specific component including all endpoint 
options, component-level options,
-  Maven coordinates, and URI syntax.
+| Get documentation for a specific component: URI syntax, component-level and 
endpoint options. Supports
+  `optionsFilter` (case-insensitive substring on option name) and 
`includeOptions` (`required`, `common`, or
+  `all`; default `common`, which excludes deprecated and advanced options) to 
control payload size.
+
+| `camel_catalog_component_maven`
+| Get the Maven coordinates (`groupId`, `artifactId`, `version`) of a specific 
component, for adding it as a
+  project dependency.
 
 | `camel_catalog_dataformats`
 | List available data formats (JSON, XML, CSV, Avro, Protobuf, and others).
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
index ea3bc1a8885a..3242c1beabe2 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.mcp;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import jakarta.enterprise.context.ApplicationScoped;
@@ -27,6 +28,7 @@ import io.quarkiverse.mcp.server.Tool;
 import io.quarkiverse.mcp.server.ToolArg;
 import io.quarkiverse.mcp.server.ToolCallException;
 import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.tooling.model.BaseOptionModel;
 import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.tooling.model.DataFormatModel;
 import org.apache.camel.tooling.model.EipModel;
@@ -88,10 +90,16 @@ public class CatalogTools {
      * Tool to get detailed documentation for a specific component.
      */
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
-          description = "Get detailed documentation for a Camel component 
including all options, " +
-                        "endpoint parameters, and usage examples.")
+          description = "Get documentation for a Camel component: URI syntax 
and endpoint options. "
+                        + "Default returns common options only (excludes 
deprecated and advanced). "
+                        + "Use camel_catalog_component_maven for 
groupId/artifactId/version.")
     public ComponentDetailResult camel_catalog_component_doc(
             @ToolArg(description = "Component name (e.g., kafka, http, file, 
timer)") String component,
+            @ToolArg(description = "Filter options by name (case-insensitive 
substring match)",
+                     required = false) String optionsFilter,
+            @ToolArg(description = "Which options to include: required | 
common | all (default: common). "
+                                   + "'common' excludes deprecated and 
advanced options.",
+                     required = false) String includeOptions,
             @ToolArg(description = ToolArgDocs.RUNTIME) String runtime,
             @ToolArg(description = ToolArgDocs.VERSION_QUERY) String 
camelVersion,
             @ToolArg(description = ToolArgDocs.PLATFORM_BOM) String 
platformBom) {
@@ -100,6 +108,8 @@ public class CatalogTools {
             throw new ToolCallException("Component name is required", null);
         }
 
+        OptionScope scope = OptionScope.parse(includeOptions);
+
         try {
             CamelCatalog cat = catalogService.loadCatalog(runtime, 
camelVersion, platformBom);
             ComponentModel model = cat.componentModel(component);
@@ -132,7 +142,42 @@ public class CatalogTools {
                 throw new ToolCallException(hint.toString(), null);
             }
 
-            return toComponentDetailResult(model);
+            return toComponentDetailResult(model, scope, optionsFilter);
+        } catch (ToolCallException e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new ToolCallException(
+                    "Component not found: " + component + " (" + 
e.getClass().getName() + "): " + e.getMessage(), null);
+        }
+    }
+
+    /**
+     * Tool to get the Maven coordinates (groupId, artifactId, version) for a 
specific component.
+     */
+    @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
+          description = "Get Maven coordinates (groupId, artifactId, version) 
for a Camel component, "
+                        + "for adding it as a dependency.")
+    public ComponentMavenResult camel_catalog_component_maven(
+            @ToolArg(description = "Component name (e.g., kafka, http, file, 
timer)") String component,
+            @ToolArg(description = ToolArgDocs.RUNTIME) String runtime,
+            @ToolArg(description = ToolArgDocs.VERSION_QUERY) String 
camelVersion,
+            @ToolArg(description = ToolArgDocs.PLATFORM_BOM) String 
platformBom) {
+
+        if (component == null || component.isBlank()) {
+            throw new ToolCallException("Component name is required", null);
+        }
+
+        try {
+            CamelCatalog cat = catalogService.loadCatalog(runtime, 
camelVersion, platformBom);
+            ComponentModel model = cat.componentModel(component);
+            if (model == null) {
+                throw new ToolCallException("Component not found: " + 
component, null);
+            }
+            return new ComponentMavenResult(
+                    model.getScheme(),
+                    model.getGroupId(),
+                    model.getArtifactId(),
+                    model.getVersion());
         } catch (ToolCallException e) {
             throw e;
         } catch (Throwable e) {
@@ -377,27 +422,33 @@ public class CatalogTools {
                 model.getSupportLevel() != null ? 
model.getSupportLevel().name() : null);
     }
 
-    private ComponentDetailResult toComponentDetailResult(ComponentModel 
model) {
+    private ComponentDetailResult toComponentDetailResult(ComponentModel 
model, OptionScope scope, String optionsFilter) {
+        Predicate<BaseOptionModel> filter = 
scope.asPredicate().and(nameFilter(optionsFilter));
+
         List<OptionInfo> componentOptions = new ArrayList<>();
         if (model.getComponentOptions() != null) {
-            model.getComponentOptions().forEach(opt -> 
componentOptions.add(new OptionInfo(
-                    opt.getName(),
-                    opt.getDescription(),
-                    opt.getType(),
-                    opt.isRequired(),
-                    opt.getDefaultValue() != null ? 
opt.getDefaultValue().toString() : null,
-                    null)));
+            model.getComponentOptions().stream()
+                    .filter(filter)
+                    .forEach(opt -> componentOptions.add(new OptionInfo(
+                            opt.getName(),
+                            opt.getDescription(),
+                            opt.getType(),
+                            opt.isRequired(),
+                            opt.getDefaultValue() != null ? 
opt.getDefaultValue().toString() : null,
+                            null)));
         }
 
         List<OptionInfo> endpointOptions = new ArrayList<>();
         if (model.getEndpointOptions() != null) {
-            model.getEndpointOptions().forEach(opt -> endpointOptions.add(new 
OptionInfo(
-                    opt.getName(),
-                    opt.getDescription(),
-                    opt.getType(),
-                    opt.isRequired(),
-                    opt.getDefaultValue() != null ? 
opt.getDefaultValue().toString() : null,
-                    opt.getGroup())));
+            model.getEndpointOptions().stream()
+                    .filter(filter)
+                    .forEach(opt -> endpointOptions.add(new OptionInfo(
+                            opt.getName(),
+                            opt.getDescription(),
+                            opt.getType(),
+                            opt.isRequired(),
+                            opt.getDefaultValue() != null ? 
opt.getDefaultValue().toString() : null,
+                            opt.getGroup())));
         }
 
         return new ComponentDetailResult(
@@ -407,9 +458,6 @@ public class CatalogTools {
                 model.getLabel(),
                 model.isDeprecated(),
                 model.getSupportLevel() != null ? 
model.getSupportLevel().name() : null,
-                model.getGroupId(),
-                model.getArtifactId(),
-                model.getVersion(),
                 model.getSyntax(),
                 model.isAsync(),
                 model.isConsumerOnly(),
@@ -418,6 +466,49 @@ public class CatalogTools {
                 endpointOptions);
     }
 
+    private static Predicate<BaseOptionModel> nameFilter(String optionsFilter) 
{
+        if (optionsFilter == null || optionsFilter.isBlank()) {
+            return opt -> true;
+        }
+        String lower = optionsFilter.toLowerCase();
+        return opt -> opt.getName() != null && 
opt.getName().toLowerCase().contains(lower);
+    }
+
+    private static boolean isAdvanced(BaseOptionModel opt) {
+        String label = opt.getLabel();
+        return label != null && label.contains("advanced");
+    }
+
+    /**
+     * Subset of options to include in {@code camel_catalog_component_doc} 
responses.
+     */
+    private enum OptionScope {
+        REQUIRED,
+        COMMON,
+        ALL;
+
+        Predicate<BaseOptionModel> asPredicate() {
+            return switch (this) {
+                case REQUIRED -> BaseOptionModel::isRequired;
+                case COMMON -> opt -> !opt.isDeprecated() && !isAdvanced(opt);
+                case ALL -> opt -> true;
+            };
+        }
+
+        static OptionScope parse(String value) {
+            if (value == null || value.isBlank()) {
+                return COMMON;
+            }
+            return switch (value.trim().toLowerCase()) {
+                case "required" -> REQUIRED;
+                case "common" -> COMMON;
+                case "all" -> ALL;
+                default -> throw new ToolCallException(
+                        "Invalid includeOptions value: '" + value + "'. 
Expected one of: required, common, all.", null);
+            };
+        }
+    }
+
     private DataFormatInfo toDataFormatInfo(DataFormatModel model) {
         return new DataFormatInfo(
                 model.getName(),
@@ -523,11 +614,14 @@ public class CatalogTools {
     }
 
     public record ComponentDetailResult(String name, String title, String 
description, String label,
-            boolean deprecated, String supportLevel, String groupId, String 
artifactId,
-            String version, String syntax, boolean async, boolean 
consumerOnly, boolean producerOnly,
+            boolean deprecated, String supportLevel, String syntax,
+            boolean async, boolean consumerOnly, boolean producerOnly,
             List<OptionInfo> componentOptions, List<OptionInfo> 
endpointOptions) {
     }
 
+    public record ComponentMavenResult(String name, String groupId, String 
artifactId, String version) {
+    }
+
     public record OptionInfo(String name, String description, String type, 
boolean required,
             String defaultValue, String group) {
     }
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
index 1face11c0145..4f2c3c3a19fd 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
@@ -84,12 +84,89 @@ class CatalogToolsTest {
     void componentDocWithRepos() {
         CatalogTools tools = 
createTools("https://maven.repository.redhat.com/ga/";);
 
-        CatalogTools.ComponentDetailResult result = 
tools.camel_catalog_component_doc("timer", null, null, null);
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("timer", null, null, null, 
null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.name()).isEqualTo("timer");
     }
 
+    @Test
+    void componentDocDefaultsToCommonScope() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult defaultResult
+                = tools.camel_catalog_component_doc("kafka", null, null, null, 
null, null);
+        CatalogTools.ComponentDetailResult allResult
+                = tools.camel_catalog_component_doc("kafka", null, "all", 
null, null, null);
+
+        assertThat(defaultResult.endpointOptions()).isNotEmpty();
+        assertThat(allResult.endpointOptions()).isNotEmpty();
+        assertThat(defaultResult.endpointOptions().size())
+                .as("default 'common' scope must filter out 
advanced/deprecated options")
+                .isLessThan(allResult.endpointOptions().size());
+
+        // 'common' must not include any deprecated option (advanced options 
are not exposed in the
+        // returned OptionInfo, but deprecated is hidden internally via the 
label/deprecated flags)
+        assertThat(defaultResult.endpointOptions())
+                .as("default 'common' scope must not contain options whose 
name starts with 'synchronous'")
+                .noneMatch(o -> "synchronous".equals(o.name()));
+    }
+
+    @Test
+    void componentDocRequiredScopeOnlyReturnsRequiredOptions() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("kafka", null, "required", 
null, null, null);
+
+        
assertThat(result.endpointOptions()).allMatch(CatalogTools.OptionInfo::required);
+    }
+
+    @Test
+    void componentDocOptionsFilterByName() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("kafka", "topic", "all", 
null, null, null);
+
+        assertThat(result.endpointOptions()).isNotEmpty();
+        assertThat(result.endpointOptions())
+                .allMatch(o -> o.name().toLowerCase().contains("topic"));
+    }
+
+    @Test
+    void componentDocInvalidIncludeOptionsThrows() {
+        CatalogTools tools = createTools(null);
+
+        assertThatThrownBy(() -> tools.camel_catalog_component_doc("timer", 
null, "bogus", null, null, null))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("Invalid includeOptions");
+    }
+
+    @Test
+    void componentMavenReturnsCoordinates() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentMavenResult result
+                = tools.camel_catalog_component_maven("timer", null, null, 
null);
+
+        assertThat(result).isNotNull();
+        assertThat(result.name()).isEqualTo("timer");
+        assertThat(result.groupId()).isEqualTo("org.apache.camel");
+        assertThat(result.artifactId()).isEqualTo("camel-timer");
+        assertThat(result.version()).isNotBlank();
+    }
+
+    @Test
+    void componentMavenUnknownComponentThrows() {
+        CatalogTools tools = createTools(null);
+
+        assertThatThrownBy(() -> 
tools.camel_catalog_component_maven("does-not-exist", null, null, null))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("Component not found");
+    }
+
     // platformBom validation tests
 
     @Test
@@ -206,11 +283,11 @@ class CatalogToolsTest {
 
         assertThat(listResult.camelVersion()).isEqualTo(requestedVersion);
 
-        CatalogTools.ComponentDetailResult docResult
-                = tools.camel_catalog_component_doc("timer", "main", 
requestedVersion, null);
+        CatalogTools.ComponentMavenResult mavenResult
+                = tools.camel_catalog_component_maven("timer", "main", 
requestedVersion, null);
 
-        assertThat(docResult.version()).isEqualTo(requestedVersion);
-        assertThat(docResult.version()).isNotEqualTo(BUILTIN_VERSION);
+        assertThat(mavenResult.version()).isEqualTo(requestedVersion);
+        assertThat(mavenResult.version()).isNotEqualTo(BUILTIN_VERSION);
     }
 
     @Test
@@ -226,10 +303,10 @@ class CatalogToolsTest {
 
         assertThat(listResult.camelVersion()).isEqualTo(requestedVersion);
 
-        CatalogTools.ComponentDetailResult docResult
-                = tools.camel_catalog_component_doc("timer", "main", null, 
bom);
+        CatalogTools.ComponentMavenResult mavenResult
+                = tools.camel_catalog_component_maven("timer", "main", null, 
bom);
 
-        assertThat(docResult.version()).isEqualTo(requestedVersion);
-        assertThat(docResult.version()).isNotEqualTo(BUILTIN_VERSION);
+        assertThat(mavenResult.version()).isEqualTo(requestedVersion);
+        assertThat(mavenResult.version()).isNotEqualTo(BUILTIN_VERSION);
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpJsonSerializationTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpJsonSerializationTest.java
index 066504a2ca2c..196b5edfb8b6 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpJsonSerializationTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpJsonSerializationTest.java
@@ -82,7 +82,7 @@ class McpJsonSerializationTest {
         ObjectMapper mapper = newConfiguredObjectMapper();
 
         CatalogTools.ComponentDetailResult detail = new 
CatalogTools.ComponentDetailResult(
-                "timer", "Timer", null, null, false, null, null, null, null, 
null,
+                "timer", "Timer", null, null, false, null, null,
                 false, false, false, null, null);
 
         String json = mapper.writeValueAsString(detail);
@@ -96,6 +96,21 @@ class McpJsonSerializationTest {
         assertThat(json).doesNotContain("\"endpointOptions\"");
     }
 
+    @Test
+    void componentMavenResultSerializesNonNullFields() throws Exception {
+        ObjectMapper mapper = newConfiguredObjectMapper();
+
+        CatalogTools.ComponentMavenResult maven = new 
CatalogTools.ComponentMavenResult(
+                "kafka", "org.apache.camel", "camel-kafka", "4.21.0");
+
+        String json = mapper.writeValueAsString(maven);
+
+        assertThat(json).contains("\"name\":\"kafka\"");
+        assertThat(json).contains("\"groupId\":\"org.apache.camel\"");
+        assertThat(json).contains("\"artifactId\":\"camel-kafka\"");
+        assertThat(json).contains("\"version\":\"4.21.0\"");
+    }
+
     private static ObjectMapper newConfiguredObjectMapper() {
         ObjectMapper mapper = new ObjectMapper();
         // Mirrors the configuration applied by Quarkus' 
ConfigurationCustomizer when

Reply via email to