From b688da7b5efe6a17d414a1f2d53d5d9366cff6cd Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Mon, 3 Nov 2025 13:22:58 +0100 Subject: [PATCH 1/3] refactor(mcp): qualify ObjectMapper and upgrade jsonschema to 4.38.0" This reverts commit 132d50b3ecb48e5c1b8ed77da68630803e80b123. Signed-off-by: Eric Bottard --- .../autoconfigure/McpServerSseWebFluxAutoConfiguration.java | 4 +++- .../McpServerStatelessWebFluxAutoConfiguration.java | 4 +++- .../McpServerStreamableHttpWebFluxAutoConfiguration.java | 4 +++- .../autoconfigure/McpServerSseWebMvcAutoConfiguration.java | 4 +++- .../McpServerStatelessWebMvcAutoConfiguration.java | 4 +++- .../McpServerStreamableHttpWebMvcAutoConfiguration.java | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java index f4b9a1ef30c..52f573c9fff 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -82,7 +83,8 @@ public class McpServerSseWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean - public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider objectMapperProvider, + public WebFluxSseServerTransportProvider webFluxTransport( + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, McpServerSseProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java index 16a3cd61e56..6ed95f43391 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -47,7 +48,8 @@ public class McpServerStatelessWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStatelessServerTransport webFluxStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + McpServerStreamableHttpProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java index dea9b89ffbe..54a2d01b133 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -48,7 +49,8 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + McpServerStreamableHttpProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java index f9c14b140c6..90a0c8d3adb 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -76,7 +77,8 @@ public class McpServerSseWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerSseProperties serverProperties) { + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + McpServerSseProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java index c9e00c848c1..2d85a8eb33a 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -48,7 +49,8 @@ public class McpServerStatelessWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStatelessServerTransport webMvcStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + McpServerStreamableHttpProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java index 3d7a840a9f5..18748421a29 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java @@ -26,6 +26,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -49,7 +50,8 @@ public class McpServerStreamableHttpWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { + @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + McpServerStreamableHttpProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); From 23f7d88a26816d08c7c7b16ae77db43feafe8fbb Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Mon, 3 Nov 2025 13:23:39 +0100 Subject: [PATCH 2/3] Introduce McpServerObjectMapperFactory for consistent ObjectMapper configuration" This reverts commit fdf21f4eeda5013c73185cc0857e421cb060a600. Signed-off-by: Eric Bottard --- .../McpServerAutoConfiguration.java | 27 ++- .../McpServerObjectMapperFactory.java | 105 +++++++++ .../autoconfigure/McpToolWithStdioIT.java | 214 ++++++++++++++++++ .../McpServerSseWebMvcAutoConfiguration.java | 3 +- ...erverStatelessWebMvcAutoConfiguration.java | 3 +- ...StreamableHttpWebMvcAutoConfiguration.java | 3 +- 6 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java create mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpToolWithStdioIT.java diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java index ef10a199ae3..c82409f3097 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java @@ -91,12 +91,31 @@ public class McpServerAutoConfiguration { private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class); + /** + * Creates a configured ObjectMapper for MCP server JSON serialization. + *

+ * This ObjectMapper is specifically configured for MCP protocol compliance with: + *

    + *
  • Lenient deserialization that doesn't fail on unknown properties
  • + *
  • Proper handling of empty beans during serialization
  • + *
  • Exclusion of null values from JSON output
  • + *
  • Standard Jackson modules for Java 8, JSR-310, and Kotlin support
  • + *
+ *

+ * This bean can be overridden by providing a custom ObjectMapper bean with the name + * "mcpServerObjectMapper". + * @return configured ObjectMapper instance for MCP server operations + */ + @Bean(name = "mcpServerObjectMapper") + @ConditionalOnMissingBean(name = "mcpServerObjectMapper") + public ObjectMapper mcpServerObjectMapper() { + return McpServerObjectMapperFactory.createObjectMapper(); + } + @Bean @ConditionalOnMissingBean - public McpServerTransportProviderBase stdioServerTransport(ObjectProvider objectMapperProvider) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); - - return new StdioServerTransportProvider(new JacksonMcpJsonMapper(objectMapper)); + public McpServerTransportProviderBase stdioServerTransport(ObjectMapper mcpServerObjectMapper) { + return new StdioServerTransportProvider(new JacksonMcpJsonMapper(mcpServerObjectMapper)); } @Bean diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java new file mode 100644 index 00000000000..65f2989c721 --- /dev/null +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java @@ -0,0 +1,105 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.mcp.server.common.autoconfigure; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; + +import org.springframework.ai.util.JacksonUtils; + +/** + * Factory class for creating properly configured {@link ObjectMapper} instances for MCP + * server operations. + *

+ * This factory ensures consistent JSON serialization/deserialization configuration across + * all MCP server transport types (STDIO, SSE, Streamable-HTTP, Stateless). The + * configuration is optimized for MCP protocol compliance and handles common edge cases + * that can cause serialization failures. + *

+ * Key configuration features: + *

    + *
  • Lenient Deserialization: Does not fail on unknown JSON properties, allowing + * forward compatibility
  • + *
  • Empty Bean Handling: Does not fail when serializing beans without + * properties
  • + *
  • Null Value Exclusion: Excludes null values from JSON output for cleaner + * messages
  • + *
  • Date/Time Formatting: Uses ISO-8601 format instead of timestamps
  • + *
  • Jackson Modules: Registers standard modules for Java 8, JSR-310, parameter + * names, and Kotlin (if available)
  • + *
+ * + * @author Spring AI Team + */ +public final class McpServerObjectMapperFactory { + + private McpServerObjectMapperFactory() { + // Utility class - prevent instantiation + } + + /** + * Creates a new {@link ObjectMapper} instance configured for MCP server operations. + *

+ * This method creates a fresh ObjectMapper with standard configuration suitable for + * MCP protocol serialization/deserialization. Each call creates a new instance, so + * callers may want to cache the result if creating multiple instances. + * @return a properly configured ObjectMapper instance + */ + public static ObjectMapper createObjectMapper() { + return JsonMapper.builder() + // Deserialization configuration + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) + // Serialization configuration + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .serializationInclusion(JsonInclude.Include.NON_NULL) + // Register standard Jackson modules (Jdk8, JavaTime, ParameterNames, Kotlin) + .addModules(JacksonUtils.instantiateAvailableModules()) + .build(); + } + + /** + * Retrieves an ObjectMapper from the provided provider, or creates a configured + * default if none is available. + *

+ * This method is designed for use in Spring auto-configuration classes where an + * ObjectMapper may optionally be provided by the user. If no ObjectMapper bean is + * available, this method ensures a properly configured instance is used rather than a + * vanilla ObjectMapper. + *

+ * Example usage in auto-configuration: + * + *

{@code
+	 * @Bean
+	 * public TransportProvider transport(ObjectProvider objectMapperProvider) {
+	 *     ObjectMapper mapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider);
+	 *     return new TransportProvider(mapper);
+	 * }
+	 * }
+ * @param objectMapperProvider the Spring ObjectProvider for ObjectMapper beans + * @return the provided ObjectMapper, or a newly configured default instance + */ + public static ObjectMapper getOrCreateObjectMapper( + org.springframework.beans.factory.ObjectProvider objectMapperProvider) { + return objectMapperProvider.getIfAvailable(McpServerObjectMapperFactory::createObjectMapper); + } + +} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpToolWithStdioIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpToolWithStdioIT.java new file mode 100644 index 00000000000..034aded7a35 --- /dev/null +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/test/java/org/springframework/ai/mcp/server/common/autoconfigure/McpToolWithStdioIT.java @@ -0,0 +1,214 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.mcp.server.common.autoconfigure; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpAsyncServer; +import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.server.transport.StdioServerTransportProvider; +import io.modelcontextprotocol.spec.McpSchema; +import io.modelcontextprotocol.spec.McpServerTransportProviderBase; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springaicommunity.mcp.annotation.McpToolParam; + +import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.stereotype.Component; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for @McpTool annotations with STDIO transport. + */ +public class McpToolWithStdioIT { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(McpServerAutoConfiguration.class, McpServerAnnotationScannerAutoConfiguration.class, + McpServerSpecificationFactoryAutoConfiguration.class)); + + /** + * Verifies that a configured ObjectMapper bean is created for MCP server operations. + */ + @Test + void shouldCreateConfiguredObjectMapperForMcpServer() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(ObjectMapper.class); + ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class); + + assertThat(objectMapper).isNotNull(); + + // Verify that the ObjectMapper is properly configured + String emptyBeanJson = objectMapper.writeValueAsString(new EmptyBean()); + assertThat(emptyBeanJson).isEqualTo("{}"); // Should not fail on empty beans + + String nullValueJson = objectMapper.writeValueAsString(new BeanWithNull()); + assertThat(nullValueJson).doesNotContain("null"); // Should exclude null + // values + }); + } + + /** + * Verifies that STDIO transport uses the configured ObjectMapper. + */ + @Test + void stdioTransportShouldUseConfiguredObjectMapper() { + this.contextRunner.run(context -> { + assertThat(context).hasSingleBean(McpServerTransportProviderBase.class); + assertThat(context.getBean(McpServerTransportProviderBase.class)) + .isInstanceOf(StdioServerTransportProvider.class); + + // Verify that the MCP server was created successfully + assertThat(context).hasSingleBean(McpSyncServer.class); + }); + } + + /** + * Verifies that @McpTool annotated methods are successfully registered with STDIO + * transport and that tool specifications can be properly serialized to JSON without + * errors. + */ + @Test + @SuppressWarnings("unchecked") + void mcpToolAnnotationsShouldWorkWithStdio() { + this.contextRunner.withBean(TestCalculatorTools.class).run(context -> { + // Verify the server was created + assertThat(context).hasSingleBean(McpSyncServer.class); + McpSyncServer syncServer = context.getBean(McpSyncServer.class); + + // Get the async server from sync server (internal structure) + McpAsyncServer asyncServer = (McpAsyncServer) ReflectionTestUtils.getField(syncServer, "asyncServer"); + assertThat(asyncServer).isNotNull(); + + // Verify that tools were registered + CopyOnWriteArrayList tools = (CopyOnWriteArrayList) ReflectionTestUtils + .getField(asyncServer, "tools"); + + assertThat(tools).isNotEmpty(); + assertThat(tools).hasSize(3); + + // Verify tool names + List toolNames = tools.stream().map(spec -> spec.tool().name()).toList(); + assertThat(toolNames).containsExactlyInAnyOrder("add", "subtract", "multiply"); + + // Verify that each tool has a valid inputSchema that can be serialized + ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class); + + for (AsyncToolSpecification spec : tools) { + McpSchema.Tool tool = spec.tool(); + + // Verify basic tool properties + assertThat(tool.name()).isNotBlank(); + assertThat(tool.description()).isNotBlank(); + + // Verify inputSchema can be serialized to JSON without errors + if (tool.inputSchema() != null) { + String schemaJson = objectMapper.writeValueAsString(tool.inputSchema()); + assertThat(schemaJson).isNotBlank(); + + // Should be valid JSON + objectMapper.readTree(schemaJson); + } + } + }); + } + + /** + * Verifies that tools with complex parameter types work correctly. + */ + @Test + @SuppressWarnings("unchecked") + void mcpToolWithComplexParametersShouldWorkWithStdio() { + this.contextRunner.withBean(TestComplexTools.class).run(context -> { + assertThat(context).hasSingleBean(McpSyncServer.class); + McpSyncServer syncServer = context.getBean(McpSyncServer.class); + + McpAsyncServer asyncServer = (McpAsyncServer) ReflectionTestUtils.getField(syncServer, "asyncServer"); + + CopyOnWriteArrayList tools = (CopyOnWriteArrayList) ReflectionTestUtils + .getField(asyncServer, "tools"); + + assertThat(tools).hasSize(1); + + AsyncToolSpecification spec = tools.get(0); + assertThat(spec.tool().name()).isEqualTo("processData"); + + // Verify the tool can be serialized + ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class); + String toolJson = objectMapper.writeValueAsString(spec.tool()); + assertThat(toolJson).isNotBlank(); + }); + } + + // Test components + + @Component + static class TestCalculatorTools { + + @McpTool(name = "add", description = "Add two numbers") + public int add(@McpToolParam(description = "First number", required = true) int a, + @McpToolParam(description = "Second number", required = true) int b) { + return a + b; + } + + @McpTool(name = "subtract", description = "Subtract two numbers") + public int subtract(@McpToolParam(description = "First number", required = true) int a, + @McpToolParam(description = "Second number", required = true) int b) { + return a - b; + } + + @McpTool(name = "multiply", description = "Multiply two numbers") + public int multiply(@McpToolParam(description = "First number", required = true) int a, + @McpToolParam(description = "Second number", required = true) int b) { + return a * b; + } + + } + + @Component + static class TestComplexTools { + + @McpTool(name = "processData", description = "Process complex data") + public String processData(@McpToolParam(description = "Input data", required = true) String input, + @McpToolParam(description = "Options", required = false) String options) { + return "Processed: " + input + " with options: " + options; + } + + } + + // Test beans for ObjectMapper configuration verification + + static class EmptyBean { + + } + + static class BeanWithNull { + + public String value = null; + + public String anotherValue = "test"; + + } + +} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java index 90a0c8d3adb..aa219b1e4f2 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java @@ -22,6 +22,7 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.beans.factory.ObjectProvider; @@ -80,7 +81,7 @@ public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, McpServerSseProperties serverProperties) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); return WebMvcSseServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java index 2d85a8eb33a..e42219cd68c 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java @@ -21,6 +21,7 @@ import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; @@ -52,7 +53,7 @@ public WebMvcStatelessServerTransport webMvcStatelessServerTransport( @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); return WebMvcStatelessServerTransport.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java index 18748421a29..9b6fef3792a 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java @@ -22,6 +22,7 @@ import io.modelcontextprotocol.spec.McpSchema; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; @@ -53,7 +54,7 @@ public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportPr @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); return WebMvcStreamableServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) From b625e88867630e15856c63841fe6193ac7b52ce6 Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Mon, 3 Nov 2025 16:05:52 +0100 Subject: [PATCH 3/3] Simplify MCP Server ObjectMapper injection Adjust tests Closes #4730 Signed-off-by: Eric Bottard --- .../McpServerAutoConfiguration.java | 25 +---- ...cpServerObjectMapperAutoConfiguration.java | 73 ++++++++++++ .../McpServerObjectMapperFactory.java | 105 ------------------ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../McpServerSseWebFluxAutoConfiguration.java | 6 +- ...rverStatelessWebFluxAutoConfiguration.java | 6 +- ...treamableHttpWebFluxAutoConfiguration.java | 5 +- ...cpServerSseWebFluxAutoConfigurationIT.java | 6 +- ...erverSseWebFluxAutoConfigurationTests.java | 6 +- ...erStatelessWebFluxAutoConfigurationIT.java | 4 +- ...eamableHttpWebFluxAutoConfigurationIT.java | 4 +- .../SseWebClientWebFluxServerIT.java | 7 +- .../StatelessWebClientWebFluxServerIT.java | 3 +- .../StreamableMcpAnnotations2IT.java | 4 +- .../StreamableMcpAnnotationsIT.java | 4 +- .../StreamableMcpAnnotationsManualIT.java | 4 +- .../StreamableMcpAnnotationsWithLLMIT.java | 10 +- .../StreamableWebClientWebFluxServerIT.java | 4 +- .../McpServerSseWebMvcAutoConfiguration.java | 7 +- ...erverStatelessWebMvcAutoConfiguration.java | 7 +- ...StreamableHttpWebMvcAutoConfiguration.java | 6 +- ...McpServerSseWebMvcAutoConfigurationIT.java | 10 +- ...verStatelessWebMvcAutoConfigurationIT.java | 4 +- ...reamableHttpWebMvcAutoConfigurationIT.java | 4 +- 24 files changed, 132 insertions(+), 183 deletions(-) create mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperAutoConfiguration.java delete mode 100644 auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java index c82409f3097..ba09aca1a98 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java @@ -51,6 +51,7 @@ import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.AllNestedConditions; @@ -91,30 +92,10 @@ public class McpServerAutoConfiguration { private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class); - /** - * Creates a configured ObjectMapper for MCP server JSON serialization. - *

- * This ObjectMapper is specifically configured for MCP protocol compliance with: - *

    - *
  • Lenient deserialization that doesn't fail on unknown properties
  • - *
  • Proper handling of empty beans during serialization
  • - *
  • Exclusion of null values from JSON output
  • - *
  • Standard Jackson modules for Java 8, JSR-310, and Kotlin support
  • - *
- *

- * This bean can be overridden by providing a custom ObjectMapper bean with the name - * "mcpServerObjectMapper". - * @return configured ObjectMapper instance for MCP server operations - */ - @Bean(name = "mcpServerObjectMapper") - @ConditionalOnMissingBean(name = "mcpServerObjectMapper") - public ObjectMapper mcpServerObjectMapper() { - return McpServerObjectMapperFactory.createObjectMapper(); - } - @Bean @ConditionalOnMissingBean - public McpServerTransportProviderBase stdioServerTransport(ObjectMapper mcpServerObjectMapper) { + public McpServerTransportProviderBase stdioServerTransport( + @Qualifier("mcpServerObjectMapper") ObjectMapper mcpServerObjectMapper) { return new StdioServerTransportProvider(new JacksonMcpJsonMapper(mcpServerObjectMapper)); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperAutoConfiguration.java new file mode 100644 index 00000000000..2bd59ecc665 --- /dev/null +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperAutoConfiguration.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.ai.mcp.server.common.autoconfigure; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import io.modelcontextprotocol.spec.McpSchema; + +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; +import org.springframework.ai.util.JacksonUtils; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnClass(McpSchema.class) +@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", + matchIfMissing = true) +@ConditionalOnMissingBean(name = "mcpServerObjectMapper") +public class McpServerObjectMapperAutoConfiguration { + + /** + * Creates a configured ObjectMapper for MCP server JSON serialization. + *

+ * This ObjectMapper is specifically configured for MCP protocol compliance with: + *

    + *
  • Lenient deserialization that doesn't fail on unknown properties
  • + *
  • Proper handling of empty beans during serialization
  • + *
  • Exclusion of null values from JSON output
  • + *
  • Standard Jackson modules for Java 8, JSR-310, and Kotlin support
  • + *
+ *

+ * This bean can be overridden by providing a custom ObjectMapper bean with the name + * "mcpServerObjectMapper". + * @return configured ObjectMapper instance for MCP server operations + */ + // NOTE: defaultCandidate=false prevents this MCP specific mapper from being injected + // in code that doesn't explicitly qualify injection point by name. + @Bean(name = "mcpServerObjectMapper", defaultCandidate = false) + public ObjectMapper mcpServerObjectMapper() { + return JsonMapper.builder() + // Deserialization configuration + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) + // Serialization configuration + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .serializationInclusion(JsonInclude.Include.NON_NULL) + // Register standard Jackson modules (Jdk8, JavaTime, ParameterNames, Kotlin) + .addModules(JacksonUtils.instantiateAvailableModules()) + .build(); + } + +} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java deleted file mode 100644 index 65f2989c721..00000000000 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2025-2025 the original author or authors. - * - * Licensed 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 - * - * https://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.springframework.ai.mcp.server.common.autoconfigure; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; - -import org.springframework.ai.util.JacksonUtils; - -/** - * Factory class for creating properly configured {@link ObjectMapper} instances for MCP - * server operations. - *

- * This factory ensures consistent JSON serialization/deserialization configuration across - * all MCP server transport types (STDIO, SSE, Streamable-HTTP, Stateless). The - * configuration is optimized for MCP protocol compliance and handles common edge cases - * that can cause serialization failures. - *

- * Key configuration features: - *

    - *
  • Lenient Deserialization: Does not fail on unknown JSON properties, allowing - * forward compatibility
  • - *
  • Empty Bean Handling: Does not fail when serializing beans without - * properties
  • - *
  • Null Value Exclusion: Excludes null values from JSON output for cleaner - * messages
  • - *
  • Date/Time Formatting: Uses ISO-8601 format instead of timestamps
  • - *
  • Jackson Modules: Registers standard modules for Java 8, JSR-310, parameter - * names, and Kotlin (if available)
  • - *
- * - * @author Spring AI Team - */ -public final class McpServerObjectMapperFactory { - - private McpServerObjectMapperFactory() { - // Utility class - prevent instantiation - } - - /** - * Creates a new {@link ObjectMapper} instance configured for MCP server operations. - *

- * This method creates a fresh ObjectMapper with standard configuration suitable for - * MCP protocol serialization/deserialization. Each call creates a new instance, so - * callers may want to cache the result if creating multiple instances. - * @return a properly configured ObjectMapper instance - */ - public static ObjectMapper createObjectMapper() { - return JsonMapper.builder() - // Deserialization configuration - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) - // Serialization configuration - .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .serializationInclusion(JsonInclude.Include.NON_NULL) - // Register standard Jackson modules (Jdk8, JavaTime, ParameterNames, Kotlin) - .addModules(JacksonUtils.instantiateAvailableModules()) - .build(); - } - - /** - * Retrieves an ObjectMapper from the provided provider, or creates a configured - * default if none is available. - *

- * This method is designed for use in Spring auto-configuration classes where an - * ObjectMapper may optionally be provided by the user. If no ObjectMapper bean is - * available, this method ensures a properly configured instance is used rather than a - * vanilla ObjectMapper. - *

- * Example usage in auto-configuration: - * - *

{@code
-	 * @Bean
-	 * public TransportProvider transport(ObjectProvider objectMapperProvider) {
-	 *     ObjectMapper mapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider);
-	 *     return new TransportProvider(mapper);
-	 * }
-	 * }
- * @param objectMapperProvider the Spring ObjectProvider for ObjectMapper beans - * @return the provided ObjectMapper, or a newly configured default instance - */ - public static ObjectMapper getOrCreateObjectMapper( - org.springframework.beans.factory.ObjectProvider objectMapperProvider) { - return objectMapperProvider.getIfAvailable(McpServerObjectMapperFactory::createObjectMapper); - } - -} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 96f267fd7b1..743fa101a6f 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -14,6 +14,7 @@ # limitations under the License. # org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration +org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration org.springframework.ai.mcp.server.common.autoconfigure.StatelessToolCallbackConverterAutoConfiguration diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java index 52f573c9fff..54ebbaccd65 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java @@ -24,7 +24,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -84,10 +83,7 @@ public class McpServerSseWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxSseServerTransportProvider webFluxTransport( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, - McpServerSseProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerSseProperties serverProperties) { return WebFluxSseServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java index 6ed95f43391..cd931375940 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java @@ -24,7 +24,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -48,15 +47,12 @@ public class McpServerStatelessWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStatelessServerTransport webFluxStatelessServerTransport( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); - return WebFluxStatelessServerTransport.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) .messageEndpoint(serverProperties.getMcpEndpoint()) - // .disallowDelete(serverProperties.isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java index 54a2d01b133..6bec43ea985 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java @@ -25,7 +25,6 @@ import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -49,11 +48,9 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); - return WebFluxStreamableServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) .messageEndpoint(serverProperties.getMcpEndpoint()) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java index bec86bec0ba..c081f3a06b4 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -33,8 +34,9 @@ class McpServerSseWebFluxAutoConfigurationIT { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( - AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class, McpServerAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class, + McpServerAutoConfiguration.class, McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java index 72fcafbcec6..01022e7a611 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java @@ -20,9 +20,9 @@ import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Configuration; @@ -34,7 +34,7 @@ class McpServerSseWebFluxAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class, - JacksonAutoConfiguration.class, TestConfiguration.class)); + McpServerObjectMapperAutoConfiguration.class, TestConfiguration.class)); @Test void shouldConfigureWebFluxTransportWithCustomObjectMapper() { @@ -43,7 +43,7 @@ void shouldConfigureWebFluxTransportWithCustomObjectMapper() { assertThat(context).hasSingleBean(RouterFunction.class); assertThat(context).hasSingleBean(McpServerProperties.class); - ObjectMapper objectMapper = context.getBean(ObjectMapper.class); + ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class); // Verify that the ObjectMapper is configured to ignore unknown properties assertThat(objectMapper.getDeserializationConfig() diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java index 50ca43381f9..42fd4021d61 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java @@ -21,6 +21,7 @@ import io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -35,7 +36,8 @@ class McpServerStatelessWebFluxAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STATELESS") - .withConfiguration(AutoConfigurations.of(McpServerStatelessWebFluxAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(McpServerStatelessWebFluxAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfigurationIT.java index c03061eb854..d0f82f28739 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfigurationIT.java @@ -21,6 +21,7 @@ import io.modelcontextprotocol.server.transport.WebFluxStreamableServerTransportProvider; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.reactive.function.server.RouterFunction; @@ -33,7 +34,8 @@ class McpServerStreamableHttpWebFluxAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") - .withConfiguration(AutoConfigurations.of(McpServerStreamableHttpWebFluxAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(McpServerStreamableHttpWebFluxAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/SseWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/SseWebClientWebFluxServerIT.java index ef08721191b..a833402c1d1 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/SseWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/SseWebClientWebFluxServerIT.java @@ -65,6 +65,7 @@ import org.springframework.ai.mcp.client.webflux.autoconfigure.SseWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.beans.factory.ObjectProvider; @@ -88,9 +89,9 @@ public class SseWebClientWebFluxServerIT { private static final JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(new ObjectMapper()); - private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerSseWebFluxAutoConfiguration.class)); + private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner().withConfiguration( + AutoConfigurations.of(McpServerAutoConfiguration.class, McpServerObjectMapperAutoConfiguration.class, + ToolCallbackConverterAutoConfiguration.class, McpServerSseWebFluxAutoConfiguration.class)); private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(McpToolCallbackAutoConfiguration.class, diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java index 6f4edeaed80..a4eb89181cd 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StatelessWebClientWebFluxServerIT.java @@ -51,6 +51,7 @@ import org.springframework.ai.mcp.client.common.autoconfigure.McpToolCallbackAutoConfiguration; import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.StatelessToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; @@ -78,7 +79,7 @@ public class StatelessWebClientWebFluxServerIT { private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STATELESS") .withConfiguration(AutoConfigurations.of(McpServerStatelessAutoConfiguration.class, - StatelessToolCallbackConverterAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class, StatelessToolCallbackConverterAutoConfiguration.class, McpServerStatelessWebFluxAutoConfiguration.class)); private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner() diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotations2IT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotations2IT.java index 808ae9ddd39..35f1d67937e 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotations2IT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotations2IT.java @@ -75,6 +75,7 @@ import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration; import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; @@ -100,7 +101,8 @@ public class StreamableMcpAnnotations2IT { private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class, + McpServerStreamableHttpWebFluxAutoConfiguration.class, McpServerAnnotationScannerAutoConfiguration.class, McpServerSpecificationFactoryAutoConfiguration.class)); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java index e782e23dcf9..4a0da8b3ac7 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsIT.java @@ -76,6 +76,7 @@ import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration; import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; @@ -101,7 +102,8 @@ public class StreamableMcpAnnotationsIT { private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class, + McpServerStreamableHttpWebFluxAutoConfiguration.class, McpServerAnnotationScannerAutoConfiguration.class, McpServerSpecificationFactoryAutoConfiguration.class)); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java index 2de4dad3fb5..1f6c2490267 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsManualIT.java @@ -78,6 +78,7 @@ import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration; import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; @@ -109,7 +110,8 @@ public class StreamableMcpAnnotationsManualIT { .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") .withConfiguration(AutoConfigurations.of(McpServerAnnotationScannerAutoConfiguration.class, McpServerSpecificationFactoryAutoConfiguration.class, McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class)); + McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class, + McpServerStreamableHttpWebFluxAutoConfiguration.class)); private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(McpToolCallbackAutoConfiguration.class, diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java index 596f9cb20c3..9403b2e0bf4 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java @@ -54,6 +54,7 @@ import org.springframework.ai.mcp.client.common.autoconfigure.annotations.McpClientSpecificationFactoryAutoConfiguration; import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerAnnotationScannerAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.annotations.McpServerSpecificationFactoryAutoConfiguration; @@ -88,7 +89,8 @@ public class StreamableMcpAnnotationsWithLLMIT { private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class, + McpServerStreamableHttpWebFluxAutoConfiguration.class, McpServerAnnotationScannerAutoConfiguration.class, McpServerSpecificationFactoryAutoConfiguration.class)); @@ -106,7 +108,7 @@ private static AutoConfigurations anthropicAutoConfig(Class... additional) { return AutoConfigurations.of(all); } - private static AtomicInteger toolCouter = new AtomicInteger(0); + private static AtomicInteger toolCounter = new AtomicInteger(0); @Test void clientServerCapabilities() { @@ -162,7 +164,7 @@ void clientServerCapabilities() { assertThat(cResponse).isNotEmpty(); assertThat(cResponse).contains("22"); - assertThat(toolCouter.get()).isEqualTo(1); + assertThat(toolCounter.get()).isEqualTo(1); // PROGRESS TestMcpClientConfiguration.TestContext testContext = clientContext @@ -234,7 +236,7 @@ public static class McpServerHandlers { @McpTool(description = "Provides weather information by city name") public String weather(McpSyncRequestContext ctx, @McpToolParam String cityName) { - toolCouter.incrementAndGet(); + toolCounter.incrementAndGet(); ctx.info("Weather called!"); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java index 83c2e8dc5f2..8ee43cf8d07 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableWebClientWebFluxServerIT.java @@ -66,6 +66,7 @@ import org.springframework.ai.mcp.client.webflux.autoconfigure.StreamableHttpWebFluxTransportAutoConfiguration; import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; @@ -93,7 +94,8 @@ public class StreamableWebClientWebFluxServerIT { private final ApplicationContextRunner serverContextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") .withConfiguration(AutoConfigurations.of(McpServerAutoConfiguration.class, - ToolCallbackConverterAutoConfiguration.class, McpServerStreamableHttpWebFluxAutoConfiguration.class)); + McpServerObjectMapperAutoConfiguration.class, ToolCallbackConverterAutoConfiguration.class, + McpServerStreamableHttpWebFluxAutoConfiguration.class)); private final ApplicationContextRunner clientApplicationContext = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(McpToolCallbackAutoConfiguration.class, diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java index aa219b1e4f2..55b805740e4 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java @@ -22,10 +22,8 @@ import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; -import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -78,10 +76,7 @@ public class McpServerSseWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, - McpServerSseProperties serverProperties) { - - ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerSseProperties serverProperties) { return WebMvcSseServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java index e42219cd68c..5ad4e203018 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java @@ -21,11 +21,9 @@ import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport; import io.modelcontextprotocol.spec.McpSchema; -import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -50,15 +48,12 @@ public class McpServerStatelessWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStatelessServerTransport webMvcStatelessServerTransport( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); - return WebMvcStatelessServerTransport.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) .messageEndpoint(serverProperties.getMcpEndpoint()) - // .disallowDelete(serverProperties.isDisallowDelete()) .build(); } diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java index 9b6fef3792a..e7a03122add 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java @@ -22,11 +22,9 @@ import io.modelcontextprotocol.spec.McpSchema; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; -import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperFactory; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -51,11 +49,9 @@ public class McpServerStreamableHttpWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider( - @Qualifier("mcpServerObjectMapper") ObjectProvider objectMapperProvider, + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerStreamableHttpProperties serverProperties) { - ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider); - return WebMvcStreamableServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) .mcpEndpoint(serverProperties.getMcpEndpoint()) diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java index 8857c96a72c..67e1236bfd3 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfigurationIT.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -37,8 +38,9 @@ class McpServerSseWebMvcAutoConfigurationIT { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( - AutoConfigurations.of(McpServerSseWebMvcAutoConfiguration.class, McpServerAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(McpServerSseWebMvcAutoConfiguration.class, + McpServerAutoConfiguration.class, McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() { @@ -109,8 +111,8 @@ void servletEnvironmentConfiguration() { public ConfigurableEnvironment getEnvironment() { return new StandardServletEnvironment(); } - }).withConfiguration( - AutoConfigurations.of(McpServerSseWebMvcAutoConfiguration.class, McpServerAutoConfiguration.class)) + }).withConfiguration(AutoConfigurations.of(McpServerSseWebMvcAutoConfiguration.class, + McpServerAutoConfiguration.class, McpServerObjectMapperAutoConfiguration.class)) .run(context -> { var mcpSyncServer = context.getBean(McpSyncServer.class); var field = ReflectionUtils.findField(McpSyncServer.class, "immediateExecution"); diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java index 5b82e51d104..2d2af219b8d 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfigurationIT.java @@ -21,6 +21,7 @@ import io.modelcontextprotocol.server.transport.WebMvcStatelessServerTransport; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.servlet.function.RouterFunction; @@ -33,7 +34,8 @@ class McpServerStatelessWebMvcAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STATELESS") - .withConfiguration(AutoConfigurations.of(McpServerStatelessWebMvcAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(McpServerStatelessWebMvcAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() { diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfigurationIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfigurationIT.java index 07169e4031e..3e204e0fd8e 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfigurationIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfigurationIT.java @@ -21,6 +21,7 @@ import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider; import org.junit.jupiter.api.Test; +import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.web.servlet.function.RouterFunction; @@ -33,7 +34,8 @@ class McpServerStreamableHttpWebMvcAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mcp.server.protocol=STREAMABLE") - .withConfiguration(AutoConfigurations.of(McpServerStreamableHttpWebMvcAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(McpServerStreamableHttpWebMvcAutoConfiguration.class, + McpServerObjectMapperAutoConfiguration.class)); @Test void defaultConfiguration() {