Isolate Stateless Requests in Ephemeral Sessions per SEP-2567#415
Open
koic wants to merge 1 commit into
Open
Conversation
## Motivation and Context SEP-2567 (modelcontextprotocol/modelcontextprotocol#2567, merged for the 2026-07-28 spec release) makes MCP sessionless: every Streamable HTTP POST must be fully self-contained, with no protocol-level session state shared between requests. `StreamableHTTPTransport` already implements the SEP's transport surface via `stateless: true` (no `Mcp-Session-Id` issued or required, GET returns 405, DELETE is a no-op, server-to-client requests raise), but its dispatch had a state leak: stateless POSTs were handled with `session: nil`, so `Server#init` wrote `@client` and `@client_capabilities` onto the shared `Server` instance. Concurrent stateless requests could therefore observe another client's identity, and the data persisted across requests, which is exactly what SEP-2567 forbids. The TypeScript SDK's stateless prototypes (the closed typescript-sdk#2058/#2131/#2251 stack) solve this with per-request dispatch; this change applies the equivalent fix within the existing architecture: - Stateless `handle_initialization`, `handle_regular_request`, and `dispatch_notification` now run handlers against an ephemeral per-request `ServerSession` (with `session_id: nil`), so client info, logging level, and initialized state live only for the duration of that POST. Repeated `initialize` requests are naturally permitted because each POST gets a fresh, never-initialized session. - `send_notification` in stateless mode now returns `false` (non-delivery) instead of raising. With ephemeral sessions in place, a tool calling `server_context.report_progress` or `notify_log_message` would otherwise route every call into the exception reporter; non-delivery matches how these helpers already degrade when no session exists. `send_request` (server-to-client requests) still raises, as those are genuinely unsupported without a stream. Resolves modelcontextprotocol#388. ## How Has This Been Tested? New tests in `test/mcp/server/transports/streamable_http_transport_test.rb`: - a stateless `initialize` POST leaves `Server#client_capabilities` and the server's `@client` untouched (the leak regression) - repeated `initialize` POSTs both succeed with 200 and no `Mcp-Session-Id` header - a tool calling `server_context.report_progress` under stateless mode returns its result normally and the exception reporter is never invoked - the existing "stateless mode does not support server-sent events" test is updated to assert the new `false` return instead of the removed raise All other existing stateless-mode tests pass unchanged. ## Breaking Changes `StreamableHTTPTransport#send_notification` in stateless mode now returns `false` instead of raising `RuntimeError`. The raise message was not a documented contract, broadcasting in stateless mode was always a non-deliverable operation, and the boolean return matches the method's documented semantics in every other non-delivery case. Default (session-oriented) mode is unchanged.
atesgoral
approved these changes
Jun 16, 2026
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.
Motivation and Context
SEP-2567 (modelcontextprotocol/modelcontextprotocol#2567, merged for the 2026-07-28 spec release) makes MCP sessionless: every Streamable HTTP POST must be fully self-contained, with no protocol-level session state shared between requests.
StreamableHTTPTransportalready implements the SEP's transport surface viastateless: true(noMcp-Session-Idissued or required, GET returns 405, DELETE is a no-op, server-to-client requests raise), but its dispatch had a state leak: stateless POSTs were handled withsession: nil, soServer#initwrote@clientand@client_capabilitiesonto the sharedServerinstance. Concurrent stateless requests could therefore observe another client's identity, and the data persisted across requests, which is exactly what SEP-2567 forbids. The TypeScript SDK's stateless prototypes (the closed typescript-sdk#2058/#2131/#2251 stack) solve this with per-request dispatch; this change applies the equivalent fix within the existing architecture:handle_initialization,handle_regular_request, anddispatch_notificationnow run handlers against an ephemeral per-requestServerSession(withsession_id: nil), so client info, logging level, and initialized state live only for the duration of that POST. Repeatedinitializerequests are naturally permitted because each POST gets a fresh, never-initialized session.send_notificationin stateless mode now returnsfalse(non-delivery) instead of raising. With ephemeral sessions in place, a tool callingserver_context.report_progressornotify_log_messagewould otherwise route every call into the exception reporter; non-delivery matches how these helpers already degrade when no session exists.send_request(server-to-client requests) still raises, as those are genuinely unsupported without a stream.Resolves #388.
How Has This Been Tested?
New tests in
test/mcp/server/transports/streamable_http_transport_test.rb:initializePOST leavesServer#client_capabilitiesand the server's@clientuntouched (the leak regression)initializePOSTs both succeed with 200 and noMcp-Session-Idheaderserver_context.report_progressunder stateless mode returns its result normally and the exception reporter is never invokedfalsereturn instead of the removed raiseAll other existing stateless-mode tests pass unchanged.
Breaking Changes
StreamableHTTPTransport#send_notificationin stateless mode now returnsfalseinstead of raisingRuntimeError. The raise message was not a documented contract, broadcasting in stateless mode was always a non-deliverable operation, and the boolean return matches the method's documented semantics in every other non-delivery case. Default (session-oriented) mode is unchanged.Types of changes
Checklist