Skip to content

Commit 0f72bba

Browse files
committed
Simplify MCP Server ObjectMapper injection
Adjust tests Closes spring-projects#4730 Signed-off-by: Eric Bottard <[email protected]>
1 parent 57da669 commit 0f72bba

File tree

24 files changed

+132
-183
lines changed

24 files changed

+132
-183
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerAutoConfiguration.java

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties;
5252
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
5353
import org.springframework.beans.factory.ObjectProvider;
54+
import org.springframework.beans.factory.annotation.Qualifier;
5455
import org.springframework.boot.autoconfigure.AutoConfiguration;
5556
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5657
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
@@ -91,30 +92,10 @@ public class McpServerAutoConfiguration {
9192

9293
private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class);
9394

94-
/**
95-
* Creates a configured ObjectMapper for MCP server JSON serialization.
96-
* <p>
97-
* This ObjectMapper is specifically configured for MCP protocol compliance with:
98-
* <ul>
99-
* <li>Lenient deserialization that doesn't fail on unknown properties</li>
100-
* <li>Proper handling of empty beans during serialization</li>
101-
* <li>Exclusion of null values from JSON output</li>
102-
* <li>Standard Jackson modules for Java 8, JSR-310, and Kotlin support</li>
103-
* </ul>
104-
* <p>
105-
* This bean can be overridden by providing a custom ObjectMapper bean with the name
106-
* "mcpServerObjectMapper".
107-
* @return configured ObjectMapper instance for MCP server operations
108-
*/
109-
@Bean(name = "mcpServerObjectMapper")
110-
@ConditionalOnMissingBean(name = "mcpServerObjectMapper")
111-
public ObjectMapper mcpServerObjectMapper() {
112-
return McpServerObjectMapperFactory.createObjectMapper();
113-
}
114-
11595
@Bean
11696
@ConditionalOnMissingBean
117-
public McpServerTransportProviderBase stdioServerTransport(ObjectMapper mcpServerObjectMapper) {
97+
public McpServerTransportProviderBase stdioServerTransport(
98+
@Qualifier("mcpServerObjectMapper") ObjectMapper mcpServerObjectMapper) {
11899
return new StdioServerTransportProvider(new JacksonMcpJsonMapper(mcpServerObjectMapper));
119100
}
120101

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.mcp.server.common.autoconfigure;
18+
19+
import com.fasterxml.jackson.annotation.JsonInclude;
20+
import com.fasterxml.jackson.databind.DeserializationFeature;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.SerializationFeature;
23+
import com.fasterxml.jackson.databind.json.JsonMapper;
24+
import io.modelcontextprotocol.spec.McpSchema;
25+
26+
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
27+
import org.springframework.ai.util.JacksonUtils;
28+
import org.springframework.boot.autoconfigure.AutoConfiguration;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
32+
import org.springframework.context.annotation.Bean;
33+
34+
@AutoConfiguration
35+
@ConditionalOnClass(McpSchema.class)
36+
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
37+
matchIfMissing = true)
38+
@ConditionalOnMissingBean(name = "mcpServerObjectMapper")
39+
public class McpServerObjectMapperAutoConfiguration {
40+
41+
/**
42+
* Creates a configured ObjectMapper for MCP server JSON serialization.
43+
* <p>
44+
* This ObjectMapper is specifically configured for MCP protocol compliance with:
45+
* <ul>
46+
* <li>Lenient deserialization that doesn't fail on unknown properties</li>
47+
* <li>Proper handling of empty beans during serialization</li>
48+
* <li>Exclusion of null values from JSON output</li>
49+
* <li>Standard Jackson modules for Java 8, JSR-310, and Kotlin support</li>
50+
* </ul>
51+
* <p>
52+
* This bean can be overridden by providing a custom ObjectMapper bean with the name
53+
* "mcpServerObjectMapper".
54+
* @return configured ObjectMapper instance for MCP server operations
55+
*/
56+
// NOTE: defaultCandidate=false prevents this MCP specific mapper from being injected
57+
// in code that doesn't explicitly qualify injection point by name.
58+
@Bean(name = "mcpServerObjectMapper", defaultCandidate = false)
59+
public ObjectMapper mcpServerObjectMapper() {
60+
return JsonMapper.builder()
61+
// Deserialization configuration
62+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
63+
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
64+
// Serialization configuration
65+
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
66+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
67+
.serializationInclusion(JsonInclude.Include.NON_NULL)
68+
// Register standard Jackson modules (Jdk8, JavaTime, ParameterNames, Kotlin)
69+
.addModules(JacksonUtils.instantiateAvailableModules())
70+
.build();
71+
}
72+
73+
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/java/org/springframework/ai/mcp/server/common/autoconfigure/McpServerObjectMapperFactory.java

Lines changed: 0 additions & 105 deletions
This file was deleted.

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# limitations under the License.
1515
#
1616
org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration
17+
org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration
1718
org.springframework.ai.mcp.server.common.autoconfigure.ToolCallbackConverterAutoConfiguration
1819
org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration
1920
org.springframework.ai.mcp.server.common.autoconfigure.StatelessToolCallbackConverterAutoConfiguration

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration;
2525
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
27-
import org.springframework.beans.factory.ObjectProvider;
2827
import org.springframework.beans.factory.annotation.Qualifier;
2928
import org.springframework.boot.autoconfigure.AutoConfiguration;
3029
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -84,10 +83,7 @@ public class McpServerSseWebFluxAutoConfiguration {
8483
@Bean
8584
@ConditionalOnMissingBean
8685
public WebFluxSseServerTransportProvider webFluxTransport(
87-
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
88-
McpServerSseProperties serverProperties) {
89-
90-
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
86+
@Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper, McpServerSseProperties serverProperties) {
9187

9288
return WebFluxSseServerTransportProvider.builder()
9389
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStatelessAutoConfiguration;
2525
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
27-
import org.springframework.beans.factory.ObjectProvider;
2827
import org.springframework.beans.factory.annotation.Qualifier;
2928
import org.springframework.boot.autoconfigure.AutoConfiguration;
3029
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -48,15 +47,12 @@ public class McpServerStatelessWebFluxAutoConfiguration {
4847
@Bean
4948
@ConditionalOnMissingBean
5049
public WebFluxStatelessServerTransport webFluxStatelessServerTransport(
51-
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
50+
@Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper,
5251
McpServerStreamableHttpProperties serverProperties) {
5352

54-
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
55-
5653
return WebFluxStatelessServerTransport.builder()
5754
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
5855
.messageEndpoint(serverProperties.getMcpEndpoint())
59-
// .disallowDelete(serverProperties.isDisallowDelete())
6056
.build();
6157
}
6258

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
2727
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
28-
import org.springframework.beans.factory.ObjectProvider;
2928
import org.springframework.beans.factory.annotation.Qualifier;
3029
import org.springframework.boot.autoconfigure.AutoConfiguration;
3130
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -49,11 +48,9 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration {
4948
@Bean
5049
@ConditionalOnMissingBean
5150
public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider(
52-
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
51+
@Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper,
5352
McpServerStreamableHttpProperties serverProperties) {
5453

55-
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
56-
5754
return WebFluxStreamableServerTransportProvider.builder()
5855
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
5956
.messageEndpoint(serverProperties.getMcpEndpoint())

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationIT.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.jupiter.api.Test;
2323

2424
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerAutoConfiguration;
25+
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration;
2526
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
2627
import org.springframework.boot.autoconfigure.AutoConfigurations;
2728
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -33,8 +34,9 @@
3334

3435
class McpServerSseWebFluxAutoConfigurationIT {
3536

36-
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
37-
AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class, McpServerAutoConfiguration.class));
37+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
38+
.withConfiguration(AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class,
39+
McpServerAutoConfiguration.class, McpServerObjectMapperAutoConfiguration.class));
3840

3941
@Test
4042
void defaultConfiguration() {

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
2121
import org.junit.jupiter.api.Test;
2222

23+
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration;
2324
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
2425
import org.springframework.boot.autoconfigure.AutoConfigurations;
25-
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
2626
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2727
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2828
import org.springframework.context.annotation.Configuration;
@@ -34,7 +34,7 @@ class McpServerSseWebFluxAutoConfigurationTests {
3434

3535
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
3636
.withConfiguration(AutoConfigurations.of(McpServerSseWebFluxAutoConfiguration.class,
37-
JacksonAutoConfiguration.class, TestConfiguration.class));
37+
McpServerObjectMapperAutoConfiguration.class, TestConfiguration.class));
3838

3939
@Test
4040
void shouldConfigureWebFluxTransportWithCustomObjectMapper() {
@@ -43,7 +43,7 @@ void shouldConfigureWebFluxTransportWithCustomObjectMapper() {
4343
assertThat(context).hasSingleBean(RouterFunction.class);
4444
assertThat(context).hasSingleBean(McpServerProperties.class);
4545

46-
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
46+
ObjectMapper objectMapper = context.getBean("mcpServerObjectMapper", ObjectMapper.class);
4747

4848
// Verify that the ObjectMapper is configured to ignore unknown properties
4949
assertThat(objectMapper.getDeserializationConfig()

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfigurationIT.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.modelcontextprotocol.server.transport.WebFluxStatelessServerTransport;
2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerObjectMapperAutoConfiguration;
2425
import org.springframework.boot.autoconfigure.AutoConfigurations;
2526
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2627
import org.springframework.context.annotation.Bean;
@@ -35,7 +36,8 @@ class McpServerStatelessWebFluxAutoConfigurationIT {
3536

3637
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
3738
.withPropertyValues("spring.ai.mcp.server.protocol=STATELESS")
38-
.withConfiguration(AutoConfigurations.of(McpServerStatelessWebFluxAutoConfiguration.class));
39+
.withConfiguration(AutoConfigurations.of(McpServerStatelessWebFluxAutoConfiguration.class,
40+
McpServerObjectMapperAutoConfiguration.class));
3941

4042
@Test
4143
void defaultConfiguration() {

0 commit comments

Comments
 (0)