Backport #800: fix error behavior on unregistered handlers in stateless server handler - #1104
Open
olsavmic wants to merge 1 commit into
Open
Conversation
Backport of modelcontextprotocol#800 to the 0.18.x line. Instead of returning Mono.error with an McpError, DefaultMcpStatelessServerHandler now returns a JSON-RPC method not found error (-32601), aligned with the stateful server session handler. The early return meant the error never reached the onErrorResume below it, so it escaped to the transport. HttpServletStatelessServerTransport and the Spring WebMvc/WebFlux stateless transports all map an escaping handler error to HTTP 500, so any request for a method with no registered handler was answered with a server error rather than a JSON-RPC error response. Clients probing for optional methods -- OpenAI's hosted connector sends server/discover before tools/list -- therefore drove 5xx traffic against otherwise healthy servers. Adds the unit test from modelcontextprotocol#800 and an integration test that asserts both the HTTP status and the JSON-RPC error body, since the status is what regressed. See modelcontextprotocol#1085
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #800 to the 0.18.x line. See #1085.
Why
DefaultMcpStatelessServerHandler.handleRequestreturnsMono.error(...)for a method with no registered handler. That is an early return, so the error never reaches theonErrorResumea few lines below it that maps errors onto a JSON-RPC error response — it escapes to the transport instead, and every stateless transport maps an escaping handler error to HTTP 500:HttpServletStatelessServerTransportWebMvcStatelessServerTransport(io.modelcontextprotocol.sdk:mcp-spring-webmvc)WebFluxStatelessServerTransport(io.modelcontextprotocol.sdk:mcp-spring-webflux)Requests for registered methods are unaffected —
tools/callwith an unknown tool name already returns a proper-32602, because that path does run throughonErrorResume.The practical impact is the one described in #1072: OpenAI's hosted MCP connector sends
server/discoverbeforetools/list, so a healthy server answers a routine capability probe with a 5xx. On our side that madePOST /mcpthe only 5xx source in the service and burned the availability SLO budget; OpenAI surfaces the same 500 as HTTP 424external_connector_error, which aborts the whole response.Why a backport
The fix is on
mainand shipped inmcp-core2.0.1, but 2.0.x is not reachable for Spring AI users on Spring Boot 3:io.modelcontextprotocol.sdk:mcp-spring-webmvcdoes not exist at 2.0.x — the Spring transports moved toorg.springframework.ai— somcp-corecannot be bumped on its own.spring-ai2.0'sspring-ai-starter-mcp-server-webmvcpullsspring-boot-starter-web:4.1.0, making the upgrade a Spring Framework 7 migration rather than a version bump.spring-ai1.1.x pins the 0.18.x line, and 0.18.4 (the latest published) still has the early return.That is the situation #1085 describes, with two other reporters confirming it on 0.17.0 and 0.18.3.
Change
Three lines in
DefaultMcpStatelessServerHandler, identical in effect to #800: return aJSONRPCResponsecarrying-32601 Method not found: <method>instead of failing theMono.Tests
DefaultMcpStatelessServerHandlerTests— the unit test from Fix error behavior on unregistered handlers in stateless server handler #800, verbatim.HttpServletStatelessIntegrationTests#testMissingHandlerReturnsMethodNotFoundError— asserts the HTTP status is 200 as well as the JSON-RPC error body. Fix error behavior on unregistered handlers in stateless server handler #800's integration test goes through an MCP client and so only sees the JSON-RPC layer; since the regression people actually hit is the status code, this one drives the transport directly withMockHttpServletRequest(the same style as the neighbouringtestThrownMcpErrorAndJsonRpcError) so a future transport-level regression is caught too../mvnw -pl mcp-core test -Dtest=DefaultMcpStatelessServerHandlerTests→ 1/1 green../mvnw -pl mcp-test test -Dtest=HttpServletStatelessIntegrationTests→ 13/13 green.Note on HTTP status
This restores parity with the stateful session handler and with 2.0.x: unknown method → HTTP 200 with a JSON-RPC
-32601body. The 2026-07-28 Streamable HTTP revision asks for HTTP 404 with-32601, which is what #1083 adds forHttpServletStatelessServerTransporton 2.0.x. That status change is deliberately left out here — it is a separate concern from the 500, the Spring transports each carry their own copy of the status logic and would need the same treatment, and 200 +-32601is already enough for clients to fall back to the legacyinitializeflow.Happy to open the equivalent PRs against
1.0.xand1.1.x— both carry the same early return (viaMcpError.builder(...)), and I have the same change prepared and green on both.