Skip to content

fix(server): commit the SSE response before the first event on GET - #1194

Open
Lyin-OTTF wants to merge 1 commit into
modelcontextprotocol:mainfrom
Lyin-OTTF:fix/streamable-get-flush
Open

Lyin-OTTF wants to merge 1 commit into
modelcontextprotocol:mainfrom
Lyin-OTTF:fix/streamable-get-flush

Conversation

@Lyin-OTTF

Copy link
Copy Markdown

A GET to the streamable endpoint sets the SSE headers, starts async, and then waits for the session to write something. Nothing commits the response until that first event, so a client that waits for the response before reading blocks: Node's fetch (undici) fails with UND_ERR_HEADERS_TIMEOUT after its 300 s default, mcp-remote retries twice and gives up, and the server is left without a listening stream, which also breaks server-initiated requests (Stream unavailable for session).

response.flushBuffer() right after startAsync() commits the status line and headers. That is the same call HttpServletMcpTransport already makes for the stateless transport.

Verification (Windows, JDK 21):

  • New HttpServletStreamableIntegrationTests#listeningStreamCommitsResponseBeforeFirstEvent opens a listening stream against a provider without keep-alive, so nothing will ever be written to it. It asserts the client gets 200 plus Content-Type: text/event-stream within 5 s. Before the fix the same test times out in sendAsync(...).get(...); with the flush it returns in ~2 s.
  • ./mvnw -pl mcp-test -am test -Dtest='HttpServletStreamableIntegrationTests#listeningStreamCommitsResponseBeforeFirstEvent' passes.
  • spring-javaformat:apply was run, the build's validate phase is clean.

Fixes #1155

The streamable servlet's GET handler set the SSE headers, started async, and then
waited for the session to write something. The container only commits the response
when the first event is written, so a client that waits for the response before it
starts reading - Node's fetch/undici, mcp-remote - gave up on an idle server with
UND_ERR_HEADERS_TIMEOUT, retried twice, and ended up with no listening stream at all.

Flush the response right after startAsync so the status line and headers reach the
client immediately, the same way HttpServletMcpTransport does for the stateless
transport.

Refs modelcontextprotocol#1155

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HttpServletStreamableServerTransportProvider: GET stream sends no status or headers until the first event

1 participant