|
21 | 21 | import java.util.function.BiConsumer; |
22 | 22 | import java.util.function.BiFunction; |
23 | 23 |
|
| 24 | +import com.fasterxml.jackson.annotation.JsonInclude; |
| 25 | +import com.fasterxml.jackson.databind.DeserializationFeature; |
24 | 26 | import com.fasterxml.jackson.databind.ObjectMapper; |
| 27 | +import com.fasterxml.jackson.databind.SerializationFeature; |
| 28 | +import com.fasterxml.jackson.databind.json.JsonMapper; |
25 | 29 | import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper; |
26 | 30 | import io.modelcontextprotocol.server.McpAsyncServer; |
27 | 31 | import io.modelcontextprotocol.server.McpAsyncServerExchange; |
|
50 | 54 |
|
51 | 55 | import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties; |
52 | 56 | import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; |
| 57 | +import org.springframework.ai.util.JacksonUtils; |
53 | 58 | import org.springframework.beans.factory.ObjectProvider; |
54 | 59 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
55 | 60 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
@@ -91,12 +96,42 @@ public class McpServerAutoConfiguration { |
91 | 96 |
|
92 | 97 | private static final LogAccessor logger = new LogAccessor(McpServerAutoConfiguration.class); |
93 | 98 |
|
| 99 | + /** |
| 100 | + * Creates a configured ObjectMapper for MCP server JSON serialization. |
| 101 | + * <p> |
| 102 | + * This ObjectMapper is specifically configured for MCP protocol compliance with: |
| 103 | + * <ul> |
| 104 | + * <li>Lenient deserialization that doesn't fail on unknown properties</li> |
| 105 | + * <li>Proper handling of empty beans during serialization</li> |
| 106 | + * <li>Exclusion of null values from JSON output</li> |
| 107 | + * <li>Standard Jackson modules for Java 8, JSR-310, and Kotlin support</li> |
| 108 | + * </ul> |
| 109 | + * <p> |
| 110 | + * This bean can be overridden by providing a custom ObjectMapper bean with the name |
| 111 | + * "mcpServerObjectMapper". |
| 112 | + * @return configured ObjectMapper instance for MCP server operations |
| 113 | + */ |
| 114 | + @Bean(name = "mcpServerObjectMapper") |
| 115 | + @ConditionalOnMissingBean(name = "mcpServerObjectMapper") |
| 116 | + public ObjectMapper mcpServerObjectMapper() { |
| 117 | + return JsonMapper.builder() |
| 118 | + // Deserialization configuration |
| 119 | + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) |
| 120 | + .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) |
| 121 | + // Serialization configuration |
| 122 | + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) |
| 123 | + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) |
| 124 | + .serializationInclusion(JsonInclude.Include.NON_NULL) |
| 125 | + // Register standard modules (Jdk8, JavaTime, ParameterNames, Kotlin if |
| 126 | + // available) |
| 127 | + .addModules(JacksonUtils.instantiateAvailableModules()) |
| 128 | + .build(); |
| 129 | + } |
| 130 | + |
94 | 131 | @Bean |
95 | 132 | @ConditionalOnMissingBean |
96 | | - public McpServerTransportProviderBase stdioServerTransport(ObjectProvider<ObjectMapper> objectMapperProvider) { |
97 | | - ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); |
98 | | - |
99 | | - return new StdioServerTransportProvider(new JacksonMcpJsonMapper(objectMapper)); |
| 133 | + public McpServerTransportProviderBase stdioServerTransport(ObjectMapper mcpServerObjectMapper) { |
| 134 | + return new StdioServerTransportProvider(new JacksonMcpJsonMapper(mcpServerObjectMapper)); |
100 | 135 | } |
101 | 136 |
|
102 | 137 | @Bean |
|
0 commit comments