fix: route server-initiated requests via GET SSE in responseStream by armando-sepulveda · Pull Request #890 · modelcontextprotocol/java-sdk

@BRM10213

When a client sends a POST request to initiate a tool/handler, the server
uses ServerResponse.sse() (WebMVC) which calls request.startAsync(),
putting Tomcat's OutputBuffer into 'suspended' async mode. While the
request handler is executing (e.g. waiting for a sampling/createMessage
response), any writes to the POST SSE transport's OutputBuffer are
buffered and never flushed to the network — because Tomcat only flushes
the buffer once the async context dispatches (i.e. when the consumer
lambda returns).

This causes a deadlock for server-initiated requests such as MCP Sampling
(sampling/createMessage) and Elicitation: the handler blocks waiting for
the client's response, but the client never receives the request because
it is stuck in the suspended buffer.

The GET SSE stream (listeningStreamRef) does not have this problem: its
consumer lambda exits immediately, so Tomcat releases the OutputBuffer
and writes from any thread reach the socket immediately.

Fix: In responseStream(), construct the McpAsyncServerExchange using
McpStreamableServerSession.this (the outer session) as the session
instead of the inner McpStreamableServerSessionStream tied to the POST
SSE transport. McpStreamableServerSession.sendRequest() already delegates
to listeningStreamRef (GET SSE), so server-initiated requests are now
correctly sent through the GET SSE channel.

Existing integration tests in AbstractMcpClientServerIntegrationTests
(testCreateMessageSuccess, testCreateMessageWithRequestTimeoutSuccess)
cover this scenario for HttpServletStreamableIntegrationTests.