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 7d7ee401c936 CAMEL-23476: camel-jbang-mcp - strip null fields from MCP
JSON responses (#23169)
7d7ee401c936 is described below
commit 7d7ee401c936eee7d5c9876bf451d3486de6fa6a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed May 13 09:10:21 2026 +0200
CAMEL-23476: camel-jbang-mcp - strip null fields from MCP JSON responses
(#23169)
Configure the Quarkus Jackson ObjectMapper for camel-jbang-mcp with
quarkus.jackson.serialization-inclusion=non-null so @Tool result records
serialized via JsonTextContentEncoder no longer emit fields with null
values.
JsonTextContentEncoder (the default content encoder used by the MCP server
for arbitrary objects returned from @Tool methods) injects the default
Quarkus ObjectMapper. Quarkus' ConfigurationCustomizer reads
JacksonBuildTimeConfig.serializationInclusion() and applies it to that
ObjectMapper, so the single property line is enough to strip nulls from
every result the MCP server emits.
The framework's existing McpObjectMapperCustomizer adds a @JsonInclude
mixin only to MCP envelope types (ToolResponse, PromptResponse, etc.),
not to application-level result records like ComponentDetailResult or
OptionInfo, which is the bug filed in the JIRA.
Reduces LLM context-window pressure for every caller of the camel-jbang-mcp
server (31 tools, 6 resources, 3 prompts).
Adds McpJsonSerializationTest covering the property setting and the
omission of null fields from ComponentInfo, OptionInfo and
ComponentDetailResult.
Closes #23169
---
.../src/main/resources/application.properties | 4 +
.../commands/mcp/McpJsonSerializationTest.java | 106 +++++++++++++++++++++
2 files changed, 110 insertions(+)
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
index 07f949764854..8aa02aefe4b5 100644
--- a/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
+++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/resources/application.properties
@@ -32,3 +32,7 @@ quarkus.log.category."io.quarkiverse.mcp".level=INFO
# Disable HTTP server - use STDIO transport only for CLI integration
quarkus.http.host-enabled=false
+
+# Strip null fields from JSON responses to reduce LLM token consumption.
+# Applies to @Tool result records serialized via JsonTextContentEncoder.
+quarkus.jackson.serialization-inclusion=non-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
new file mode 100644
index 000000000000..066504a2ca2c
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/McpJsonSerializationTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.mcp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies the MCP server is configured to strip {@code null} fields from
JSON responses. The Quarkus MCP framework
+ * serializes {@code @Tool} results through the default {@link ObjectMapper}
produced by quarkus-jackson, which honors
+ * the {@code quarkus.jackson.serialization-inclusion} build-time property.
CAMEL-23476.
+ */
+class McpJsonSerializationTest {
+
+ @Test
+ void applicationPropertiesEnforcesNonNullSerialization() throws
IOException {
+ Properties props = new Properties();
+ try (InputStream in =
getClass().getResourceAsStream("/application.properties")) {
+ assertThat(in).as("application.properties on
classpath").isNotNull();
+ props.load(in);
+ }
+
assertThat(props.getProperty("quarkus.jackson.serialization-inclusion"))
+ .as("quarkus.jackson.serialization-inclusion must be
'non-null' to drop null fields from MCP responses")
+ .isEqualToIgnoringCase("non-null");
+ }
+
+ @Test
+ void componentInfoNullFieldsAreOmittedFromJson() throws Exception {
+ ObjectMapper mapper = newConfiguredObjectMapper();
+
+ CatalogTools.ComponentInfo info = new CatalogTools.ComponentInfo(
+ "timer", "Timer", null, null, false, null);
+
+ String json = mapper.writeValueAsString(info);
+
+ assertThat(json).contains("\"name\":\"timer\"");
+ assertThat(json).contains("\"title\":\"Timer\"");
+ assertThat(json).contains("\"deprecated\":false");
+ assertThat(json).doesNotContain("\"description\"");
+ assertThat(json).doesNotContain("\"label\"");
+ assertThat(json).doesNotContain("\"supportLevel\"");
+ }
+
+ @Test
+ void optionInfoNullGroupIsOmittedFromJson() throws Exception {
+ ObjectMapper mapper = newConfiguredObjectMapper();
+
+ // Matches the bug from CAMEL-23476: component options pass null for
group while endpoint options populate it.
+ CatalogTools.OptionInfo opt = new CatalogTools.OptionInfo(
+ "bridgeErrorHandler", "Allows bridging the consumer",
"boolean", false, "false", null);
+
+ String json = mapper.writeValueAsString(opt);
+
+ assertThat(json).contains("\"name\":\"bridgeErrorHandler\"");
+ assertThat(json).contains("\"required\":false");
+ assertThat(json).doesNotContain("\"group\"");
+ }
+
+ @Test
+ void componentDetailNullCollectionsAreOmittedFromJson() throws Exception {
+ ObjectMapper mapper = newConfiguredObjectMapper();
+
+ CatalogTools.ComponentDetailResult detail = new
CatalogTools.ComponentDetailResult(
+ "timer", "Timer", null, null, false, null, null, null, null,
null,
+ false, false, false, null, null);
+
+ String json = mapper.writeValueAsString(detail);
+
+ assertThat(json).contains("\"name\":\"timer\"");
+ assertThat(json).contains("\"async\":false");
+ assertThat(json).doesNotContain("\"description\"");
+ assertThat(json).doesNotContain("\"supportLevel\"");
+ assertThat(json).doesNotContain("\"groupId\"");
+ assertThat(json).doesNotContain("\"componentOptions\"");
+ assertThat(json).doesNotContain("\"endpointOptions\"");
+ }
+
+ private static ObjectMapper newConfiguredObjectMapper() {
+ ObjectMapper mapper = new ObjectMapper();
+ // Mirrors the configuration applied by Quarkus'
ConfigurationCustomizer when
+ // quarkus.jackson.serialization-inclusion=non-null is set in
application.properties.
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ return mapper;
+ }
+}