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..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; @@ -93,10 +94,9 @@ public class McpServerAutoConfiguration { @Bean @ConditionalOnMissingBean - public McpServerTransportProviderBase stdioServerTransport(ObjectProvider objectMapperProvider) { - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); - - return new StdioServerTransportProvider(new JacksonMcpJsonMapper(objectMapper)); + public McpServerTransportProviderBase stdioServerTransport( + @Qualifier("mcpServerObjectMapper") 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/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: + *

+ *

+ * 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/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-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-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..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,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -82,10 +82,8 @@ public class McpServerSseWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean - public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider objectMapperProvider, - McpServerSseProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + public WebFluxSseServerTransportProvider webFluxTransport( + @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 16a3cd61e56..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,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -47,14 +47,12 @@ public class McpServerStatelessWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStatelessServerTransport webFluxStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, + McpServerStreamableHttpProperties serverProperties) { 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 dea9b89ffbe..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,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -48,9 +48,8 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration { @Bean @ConditionalOnMissingBean public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, + McpServerStreamableHttpProperties serverProperties) { return WebFluxStreamableServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) 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 f9c14b140c6..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 @@ -24,7 +24,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -76,9 +76,7 @@ public class McpServerSseWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerSseProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @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 c9e00c848c1..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 @@ -24,7 +24,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -48,14 +48,12 @@ public class McpServerStatelessWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStatelessServerTransport webMvcStatelessServerTransport( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, + McpServerStreamableHttpProperties serverProperties) { 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 3d7a840a9f5..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 @@ -25,7 +25,7 @@ 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; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -49,9 +49,8 @@ public class McpServerStreamableHttpWebMvcAutoConfiguration { @Bean @ConditionalOnMissingBean public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider( - ObjectProvider objectMapperProvider, McpServerStreamableHttpProperties serverProperties) { - - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); + @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, + McpServerStreamableHttpProperties serverProperties) { return WebMvcStreamableServerTransportProvider.builder() .jsonMapper(new JacksonMcpJsonMapper(objectMapper)) 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() {