spring-projects / spring-projects/spring-ai
McpStatelessSyncServer not initialized under spring.main.lazy-initialization=true, causing "MCP handler not configured" on every request
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 9.5k
- Forks
- 2.9k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 6
Description
Summary
When spring.main.lazy-initialization=true is active, McpStatelessSyncServer is never eagerly constructed. Because setMcpHandler is only called from the server's constructor/build phase, WebMvcStatelessServerTransport.mcpHandler stays null permanently. Every incoming MCP request then hits the null-check at WebMvcStatelessServerTransport.handlePost (line ~146) and returns HTTP 500 with JSON-RPC error -32603:
{
"jsonRpcError": {
"code": -32603,
"message": "MCP handler not configured"
}
}
The transport bean and its RouterFunction are created eagerly (because Spring MVC's handler mapping requires them at context refresh), so the endpoint is reachable — but the handler wire is never made.
Related issue
#3868 describes the same root cause on the SSE transport (WebMvcSseServerTransportProvider). This issue covers the stateless transport path, which is a separate code path introduced later.
Environment
- Spring AI:
2.0.0-M5 spring-ai-starter-mcp-server-webmvcspring.ai.mcp.server.protocol=STATELESS,spring.ai.mcp.server.type=SYNCspring.main.lazy-initialization=true- Spring Boot 4.1.0-RC1 / Tomcat (WebMVC)
Stack trace (trimmed)
io.modelcontextprotocol.spec.McpError$Builder.build(McpError.java:72)
org.springframework.ai.mcp.server.webmvc.transport.WebMvcStatelessServerTransport.handlePost(WebMvcStatelessServerTransport.java:150)
org.springframework.web.servlet.function.support.HandlerFunctionAdapter.handle(HandlerFunctionAdapter.java:106)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
...
Root cause
McpServerStatelessAutoConfiguration.mcpStatelessSyncServer() has no @Lazy(false) guard. Under global lazy init, the bean is deferred until first programmatic access, which never happens via the normal request path. The transport's setMcpHandler is therefore never called.
Suggested fix
Add @Lazy(false) to the mcpStatelessSyncServer bean definition in McpServerStatelessAutoConfiguration:
@Bean
@Lazy(false)
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
matchIfMissing = true)
public McpStatelessSyncServer mcpStatelessSyncServer(McpStatelessServerTransport statelessTransport, ...) {
...
}
This mirrors the fix that should also apply to the SSE transport variant in #3868.
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 in McpServerStatelessAutoConfiguration.mcpStatelessSyncServer() and trace how it initializes WebMvcStatelessServerTransport; inspect WebMvcStatelessServerTransport.handlePost() for the null-handler path. Reproduce with the listed lazy-initialization and stateless SYNC settings, then verify MCP requests no longer return HTTP 500 with “MCP handler not configured.”
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
- 70/100