## 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#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.