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

davsclaus pushed a commit to branch feature/CAMEL-23793-mcp-component-headers
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 391f85d390f07d14b3be83d762733bd2c25e7a28
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jun 18 14:31:11 2026 +0200

    CAMEL-23793: Expose component headers from catalog in MCP server
    
    Add an optional includeHeaders parameter to the camel_catalog_component_doc
    MCP tool. When set to true, the response includes message header metadata
    (CamelXxx names, Java constants, types, and consumer/producer group) from
    the component catalog. Headers are excluded by default to keep responses
    lean when agents only need options.
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../dsl/jbang/core/commands/mcp/CatalogTools.java  | 28 ++++++++++++--
 .../jbang/core/commands/mcp/CatalogToolsTest.java  | 44 +++++++++++++++++++---
 .../commands/mcp/McpJsonSerializationTest.java     |  3 +-
 3 files changed, 64 insertions(+), 11 deletions(-)

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 c7773de51fc9..fd0c79aa95cb 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
@@ -93,6 +93,7 @@ public class CatalogTools {
     @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint 
= false, openWorldHint = false),
           description = "Get documentation for a Camel component: URI syntax 
and endpoint options. "
                         + "Default returns common options only (excludes 
deprecated and advanced). "
+                        + "Set includeHeaders=true to also return message 
header metadata (CamelXxx names, constants, types). "
                         + "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,
@@ -101,6 +102,9 @@ public class CatalogTools {
             @ToolArg(description = "Which options to include: required | 
common | all (default: common). "
                                    + "'common' excludes deprecated and 
advanced options.",
                      required = false) String includeOptions,
+            @ToolArg(description = "Whether to include message headers in the 
response (default: false). "
+                                   + "Headers show the CamelXxx header names, 
their Java constants, types, and consumer/producer group.",
+                     required = false) Boolean includeHeaders,
             @ToolArg(description = ToolArgDocs.RUNTIME) String runtime,
             @ToolArg(description = ToolArgDocs.VERSION_QUERY) String 
camelVersion,
             @ToolArg(description = ToolArgDocs.PLATFORM_BOM) String 
platformBom) {
@@ -143,7 +147,7 @@ public class CatalogTools {
                 throw new ToolCallException(hint.toString(), null);
             }
 
-            return toComponentDetailResult(model, scope, optionsFilter);
+            return toComponentDetailResult(model, scope, optionsFilter, 
Boolean.TRUE.equals(includeHeaders));
         } catch (ToolCallException e) {
             throw e;
         } catch (Throwable e) {
@@ -422,7 +426,8 @@ public class CatalogTools {
                 model.getSupportLevel() != null ? 
model.getSupportLevel().name() : null);
     }
 
-    private ComponentDetailResult toComponentDetailResult(ComponentModel 
model, OptionScope scope, String optionsFilter) {
+    private ComponentDetailResult toComponentDetailResult(
+            ComponentModel model, OptionScope scope, String optionsFilter, 
boolean includeHeaders) {
         Predicate<BaseOptionModel> filter = 
scope.asPredicate().and(nameFilter(optionsFilter));
 
         List<OptionInfo> componentOptions = new ArrayList<>();
@@ -451,6 +456,15 @@ public class CatalogTools {
                             opt.getGroup())));
         }
 
+        List<HeaderInfo> headers = List.of();
+        if (includeHeaders && model.getEndpointHeaders() != null) {
+            headers = model.getEndpointHeaders().stream()
+                    .map(h -> new HeaderInfo(
+                            h.getName(), h.getDescription(), h.getJavaType(),
+                            h.getGroup(), h.getConstantName(), h.isRequired()))
+                    .collect(Collectors.toList());
+        }
+
         return new ComponentDetailResult(
                 model.getScheme(),
                 model.getTitle(),
@@ -463,7 +477,8 @@ public class CatalogTools {
                 model.isConsumerOnly(),
                 model.isProducerOnly(),
                 componentOptions,
-                endpointOptions);
+                endpointOptions,
+                headers);
     }
 
     private static Predicate<BaseOptionModel> nameFilter(String optionsFilter) 
{
@@ -640,7 +655,8 @@ public class CatalogTools {
     public record ComponentDetailResult(String name, String title, String 
description, String label,
             boolean deprecated, String supportLevel, String syntax,
             boolean async, boolean consumerOnly, boolean producerOnly,
-            List<OptionInfo> componentOptions, List<OptionInfo> 
endpointOptions) {
+            List<OptionInfo> componentOptions, List<OptionInfo> 
endpointOptions,
+            List<HeaderInfo> headers) {
     }
 
     public record ComponentMavenResult(String name, String groupId, String 
artifactId, String version) {
@@ -650,6 +666,10 @@ public class CatalogTools {
             String defaultValue, String group) {
     }
 
+    public record HeaderInfo(String name, String description, String javaType,
+            String group, String constantName, boolean required) {
+    }
+
     public record DataFormatListResult(int count, List<DataFormatInfo> 
dataFormats) {
     }
 
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 b9c53ce47102..0ef1dbd0ac8f 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
@@ -97,7 +97,7 @@ class CatalogToolsTest {
         CatalogTools tools = 
createTools("https://maven.repository.redhat.com/ga/";);
 
         CatalogTools.ComponentDetailResult result
-                = tools.camel_catalog_component_doc("timer", null, null, null, 
null, null);
+                = tools.camel_catalog_component_doc("timer", null, null, null, 
null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.name()).isEqualTo("timer");
@@ -108,9 +108,9 @@ class CatalogToolsTest {
         CatalogTools tools = createTools(null);
 
         CatalogTools.ComponentDetailResult defaultResult
-                = tools.camel_catalog_component_doc("kafka", null, null, null, 
null, null);
+                = tools.camel_catalog_component_doc("kafka", null, null, null, 
null, null, null);
         CatalogTools.ComponentDetailResult allResult
-                = tools.camel_catalog_component_doc("kafka", null, "all", 
null, null, null);
+                = tools.camel_catalog_component_doc("kafka", null, "all", 
null, null, null, null);
 
         assertThat(defaultResult.endpointOptions()).isNotEmpty();
         assertThat(allResult.endpointOptions()).isNotEmpty();
@@ -130,7 +130,7 @@ class CatalogToolsTest {
         CatalogTools tools = createTools(null);
 
         CatalogTools.ComponentDetailResult result
-                = tools.camel_catalog_component_doc("kafka", null, "required", 
null, null, null);
+                = tools.camel_catalog_component_doc("kafka", null, "required", 
null, null, null, null);
 
         
assertThat(result.endpointOptions()).allMatch(CatalogTools.OptionInfo::required);
     }
@@ -140,7 +140,7 @@ class CatalogToolsTest {
         CatalogTools tools = createTools(null);
 
         CatalogTools.ComponentDetailResult result
-                = tools.camel_catalog_component_doc("kafka", "topic", "all", 
null, null, null);
+                = tools.camel_catalog_component_doc("kafka", "topic", "all", 
null, null, null, null);
 
         assertThat(result.endpointOptions()).isNotEmpty();
         assertThat(result.endpointOptions())
@@ -151,11 +151,43 @@ class CatalogToolsTest {
     void componentDocInvalidIncludeOptionsThrows() {
         CatalogTools tools = createTools(null);
 
-        assertThatThrownBy(() -> tools.camel_catalog_component_doc("timer", 
null, "bogus", null, null, null))
+        assertThatThrownBy(() -> tools.camel_catalog_component_doc("timer", 
null, "bogus", null, null, null, null))
                 .isInstanceOf(ToolCallException.class)
                 .hasMessageContaining("Invalid includeOptions");
     }
 
+    @Test
+    void componentDocDefaultExcludesHeaders() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("kafka", null, null, null, 
null, null, null);
+
+        assertThat(result.headers()).isEmpty();
+    }
+
+    @Test
+    void componentDocIncludeHeadersReturnsHeaders() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("kafka", null, null, true, 
null, null, null);
+
+        assertThat(result.headers()).isNotEmpty();
+        assertThat(result.headers())
+                .anyMatch(h -> "CamelKafkaKey".equals(h.name()) && 
h.constantName() != null);
+    }
+
+    @Test
+    void componentDocIncludeHeadersFalseExcludesHeaders() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentDetailResult result
+                = tools.camel_catalog_component_doc("kafka", null, null, 
false, null, null, null);
+
+        assertThat(result.headers()).isEmpty();
+    }
+
     @Test
     void componentMavenReturnsCoordinates() {
         CatalogTools tools = createTools(null);
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 e312f394e5e8..40f964c7f4e7 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
@@ -85,7 +85,7 @@ class McpJsonSerializationTest {
 
         CatalogTools.ComponentDetailResult detail = new 
CatalogTools.ComponentDetailResult(
                 "timer", "Timer", null, null, false, null, null,
-                false, false, false, null, null);
+                false, false, false, null, null, null);
 
         String json = mapper.writeValueAsString(detail);
 
@@ -96,6 +96,7 @@ class McpJsonSerializationTest {
         assertThat(json).doesNotContain("\"groupId\"");
         assertThat(json).doesNotContain("\"componentOptions\"");
         assertThat(json).doesNotContain("\"endpointOptions\"");
+        assertThat(json).doesNotContain("\"headers\"");
     }
 
     @Test

Reply via email to