spring-projects / spring-projects/spring-ai

MCP server: McpServerStatelessAutoConfiguration does not pass mcpServerObjectMapper into McpServer.sync().jsonMapper()

Open Beginner friendly
#5,894 3 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
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)paramsInitializeRequest: 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.