spring-projects / spring-projects/spring-ai
MCP server: McpServerStatelessAutoConfiguration does not pass mcpServerObjectMapper into McpServer.sync().jsonMapper()
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 5
Description
Bug description
McpServerStatelessAutoConfiguration#mcpStatelessSyncServer (and the async equivalent) builds the McpServer like this:
StatelessSyncSpecification serverBuilder = McpServer.sync(statelessTransport).serverInfo(serverInfo);
// … configures tools, capabilities, instructions, requestTimeout …
return serverBuilder.build();
It never calls .jsonMapper(...). So when serverBuilder.build() runs, it falls back to McpJsonMapper.getDefault() — a strict vanilla Jackson ObjectMapper with FAIL_ON_UNKNOWN_PROPERTIES=true. That default is then used inside McpStatelessAsyncServer#asyncInitializeRequestHandler to do the second-stage convertValue(req, InitializeRequest.class).
Meanwhile, the lenient mcpServerObjectMapper bean produced by McpServerObjectMapperAutoConfiguration is wired into the transport layer (WebMvcStatelessServerTransport) for the first-stage envelope deserialization. So:
- Stage 1 (transport) — JSON-RPC envelope →
JSONRPCRequest: uses the lenient mapper ✅ - Stage 2 (handler) —
params→InitializeRequest: uses the strict default ❌
Impact
Any MCP client implementing the 2025-11-25 spec sends capabilities.elicitation.form: {} (or url: {}) on the initialize request. The MCP Java SDK's ClientCapabilities.Elicitation record (≤ 0.17.x) is empty, so the strict default mapper rejects the unknown fields and the handshake fails with:
-32603: Unrecognized field "form" (class io.modelcontextprotocol.spec.McpSchema$ClientCapabilities$Elicitation),
not marked as ignorable (0 known properties: ])
... through reference chain: InitializeRequest["capabilities"]->ClientCapabilities["elicitation"]->Elicitation["form"]
Codex (OpenAI's CLI), and any other client on spec 2025-11-25, can't connect to a Spring AI MCP server because of this.
Fix
McpServer.StatelessSyncSpecification (and StatelessAsyncSpecification) already expose a jsonMapper(McpJsonMapper) builder method. The autoconfigure should call it:
StatelessSyncSpecification serverBuilder = McpServer.sync(statelessTransport)
.serverInfo(serverInfo)
.jsonMapper(new JacksonMcpJsonMapper(mcpServerObjectMapper)); // <-- add this
The same wiring already exists for stdio transport in McpServerAutoConfiguration#stdioServerTransport. The stateless path was missed.
Reproducer
Spring AI 1.1.2 + spring-ai-starter-mcp-server-webmvc, configured with spring.ai.mcp.server.protocol: STATELESS. POST to the MCP endpoint:
{
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": { "elicitation": { "form": {}, "url": {} } },
"clientInfo": { "name": "codex", "version": "0.x" }
}
}
Returns the strict-mode error above. Same payload with the workaround below succeeds.
Workaround
Register a McpJsonMapperSupplier via SPI (META-INF/services/io.modelcontextprotocol.json.McpJsonMapperSupplier) that returns a lenient JacksonMcpJsonMapper. Spring Boot's classloader picks user resources before transitive deps, so this overrides McpJsonMapper.getDefault(). Works but obviously the proper fix is to plumb mcpServerObjectMapper through.
Related
#5178 reports a similar symptom in a different code path (tool call argument deserialization using a static OBJECT_MAPPER). Same class of bug — incomplete plumbing of mcpServerObjectMapper through the MCP stack — different location.
Environment
- Spring AI 1.1.2 (also reproducible on 1.1.5 via the same pinned MCP SDK 0.17.0)
- Spring Boot 4.0.3 / Java 25
- MCP Java SDK 0.17.0 (transitive)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at McpServerStatelessAutoConfiguration#mcpStatelessSyncServer and its async equivalent, then compare their builder setup with McpServerAutoConfiguration#stdioServerTransport. Verify that the stateless builders receive the mcpServerObjectMapper through JacksonMcpJsonMapper, and exercise the provided initialize payload to confirm that elicitation.form and elicitation.url no longer cause strict-mode deserialization failure.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, spring, spring-boot
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100