Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurearea/serverbugSomething isn't workingSomething isn't workingwontfixThis will not be worked onThis will not be worked on
Description
Summary
HttpServletStreamableServerTransportProvider intermittently answers -32603 "Stream unavailable for session <id>" (wrapped as JSON-RPC internal error / HTTP 500) when it attempts to push a server-initiated request (e.g. roots/list) to a client whose GET /mcp SSE stream has not been registered yet.
Observed failure
- Test:
HttpServletStreamableIntegrationTests.testRootsNotificationWithEmptyRootsList(String)[1] - CI run: https://github.com/modelcontextprotocol/java-sdk/actions/runs/33068920136 (Build and Test)
- Client-side surfacing:
java.lang.RuntimeException: Failed to send message: AggregateResponseEvent[...]wrapping
jsonRpcError: {"code":-32603,"message":"Error processing message: Stream unavailable for session 82454020-4c10-4276-9c30-57c15d49810e"}
Repro shape
- Client declares the
rootscapability - Client sends
initialize; server accepts and creates the session - Server immediately pushes
roots/listover the session'sGET /mcpstream - POST traffic racing the first stream registration loses →
-32603, stream unavailable
Why we believe it is a race (not environment-specific)
- The same commit passed the parallel "Jackson 2 Integration Tests" job minutes later
- Full local suite (embedded Tomcat 11, JDK 17, macOS) passed repeatedly; only 1 of 43 parameterizations in the class failed on CI
- Related history: revert
fb029c8c"Clean up AbstractMcpClientServerIntegrationTests",HttpServletStreamableServerTransportProviderremoves session on non-fatal failure #952 (session removed on non-fatal failure), and Stateful streamable server calls listRoots() unconditionally on roots/list_changed, blocking a servlet thread until requestTimeout when no roots consumer is registered #1061 (same roots-push seam in the stateful streamable provider, blocking variant)
Suggested directions
- Buffer server→client messages per session until a stream registers (short grace window), or
- Single retry on "stream unavailable" for capability-prompted pushes
A test-side await cannot fix this, since the failure occurs inside the server's push path.
Environment
- main @
b31841ee(via PR feat(transport): add SEP-2243 Mcp-Method / Mcp-Name header mirroring with MCP-Protocol-Version validation #1112 CI), JDK 17, CI ubuntu-latest + local macOS
Metadata
Metadata
Assignees
Labels
P2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featurearea/serverbugSomething isn't workingSomething isn't workingwontfixThis will not be worked onThis will not be worked on
Activity
Ben-Home commentedon Aug 28, 2026
Kehrlann commentedon Aug 28, 2026
Unable to reproduce locally, even running the tests 1000 times. It might not be the exact problem described here. The test in question does the following:
POST/initialize`GET /mcp(opens a stream), in a fire-and-forgetPOST notifications/initializedPOST notications/roots/list_changedroots/list.It is very unlikely that the
GETis under contention for two full posts. I can reproduce by adding latency to the servlet transport'sdoGet, though.The underlying issue (race condition between server notifications and
GET) might be a problem under some conditions (network, client from a different ecosystem).(There is also a separate issue at play here - the
clientcan't really wait for the stream to be established before proceeding further, which would fix the test without fixing the race condition. But that's a whole other can of worms.)Kehrlann commentedon Aug 28, 2026
Please let's discuss server-side fixes in this issue before jumping into an implementation.
slachiewicz commentedon Aug 28, 2026
Fresh occurrence in CI, on a branch that only touches a test file: Jackson 2 job of #1113 at 8fccf33,
HttpServletStreamableIntegrationTests.testRootsSuccess(String)[1]→-32603 "Error processing message: Stream unavailable for session f79d57db-…"fromHttpServletStreamableServerTransportProvider.doPost:564(run 33192309921). Same seam astestRootsNotificationWithEmptyRootsList; the sibling Jackson 3 job passed the same class.This comment was created with AI assistance.
CryoThrust commentedon Aug 31, 2026
I can help investigate this race. The current discussion suggests the server-side contract needs to distinguish a session that exists from a stream that is not registered yet. Before proposing code, would maintainers prefer buffering pending server-to-client messages per session (with bounded lifetime) or a retryable send result? I can prepare a deterministic delayed-GET integration test once the preferred invariant is confirmed.
Kehrlann commentedon Aug 31, 2026
Looking around at what the other SDKs do, CSharp, TypeScript and Python suffer the same race condition and don't try to buffer or replay. I don't plan on changing the current behavior, especially with the new spec around the corner that has most of our focus.
I'm open to stabilizing the tests though.
The issue is with server-sent notifications, so possibly
roots,elicitiationandsampling. They seem to mostly be happening inroots, so I'll add the explicit await there.slachiewicz commentedon Aug 31, 2026
Fine with stabilizing the tests and leaving the server behavior as-is, given parity with the C#/TS/Python SDKs and the upcoming spec revision. Since #1119 does not change the runtime race, I would suggest closing this as not-planned rather than via "Fixes", so the decision stays discoverable when the 2026-07-28 transport work touches this seam.
This comment was created with AI assistance.
Fix integration tests for `roots/list`
Fix integration tests for `roots/list`
Fix integration tests for `roots/list`