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 bbf45d17d6bc CAMEL-23793: Expose component headers from catalog in MCP
server
bbf45d17d6bc is described below
commit bbf45d17d6bc19c6b8b099ebf2d576602255aa98
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jun 18 14:56:55 2026 +0200
CAMEL-23793: Expose component headers from catalog in MCP server
Add an optional includeHeaders parameter (default false) to the
camel_catalog_component_doc MCP tool. When 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.
Closes #24114
---
.../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