Add HttpServerSessionMode for hybrid stateful/stateless HTTP serving - #1796
Open
saicharanpardhu wants to merge 3 commits into
Open
Add HttpServerSessionMode for hybrid stateful/stateless HTTP serving#1796saicharanpardhu wants to merge 3 commits into
saicharanpardhu wants to merge 3 commits into
Conversation
Introduces `HttpServerSessionMode` so a single Streamable HTTP endpoint can
serve `initialize`-handshake clients with full sessions while serving
`2026-07-28` and later clients statelessly.
- Add `HttpServerSessionMode { Stateless, Stateful, StatefulForInitializeClients }`
and `HttpServerTransportOptions.SessionMode` (defaults to `Stateless`).
- Obsolete `HttpServerTransportOptions.Stateless` (MCP9008) and keep it as a
compatibility proxy over `SessionMode`: `true` maps to `Stateless`, `false`
maps to `Stateful`, hybrid reads as `false`. Assigning both is allowed and the
last assignment wins.
- Decide the effective mode per request in `StreamableHttpHandler`:
`2026-07-28` requests are refused only in `Stateful` mode, and
`StartNewSessionAsync`/`CreateSessionAsync` now take an explicit
`serveStatelessly` flag so DI and callback lifetimes follow the request.
- Keep GET/DELETE endpoints mapped, legacy SSE permitted, and idle tracking
running in hybrid mode.
Fixes modelcontextprotocol#1777
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject undefined HttpServerSessionMode values through options validation, cover RunSessionHandler lifetimes in hybrid mode, and repair documentation examples and terminology for the three configuration values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
saicharanpardhu
marked this pull request as ready for review
August 5, 2026 21:26
halter73
reviewed
Aug 6, 2026
| ```csharp | ||
| builder.Services.AddMcpServer() | ||
| .WithHttpTransport(o => o.Stateless = true) | ||
| .WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless) |
Contributor
There was a problem hiding this comment.
Now that we've made the change to default Stateless = true, I wonder if we should update our examples/samples to just call .WithHttpTransport() and not explicitly configure the SessionMode. The reason we originally set this explicitly in all the samples is we new we were going to change the default in 2.0. What do you think @jeffhandley?
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.
Closes #1777.
Implements the design @halter73 described, finishing the work from the draft in saicharanpardhu#1.
Problem
Starting with
2026-07-28, Streamable HTTP has no sessions (SEP-2567 removedMcp-Session-Id, SEP-2575 removed theinitializehandshake). Today an ASP.NET Core server must pick one era for the whole endpoint:Stateless = true— no sessions for anyone, so legacy clients lose unsolicited notifications, resource subscriptions, and server-to-client requests.Stateless = false—2026-07-28requests are refused with-32022 UnsupportedProtocolVersionto force a downgrade, so servers can't adopt the new revision until every client has migrated (or is willing to downgrade).Change
New three-value
HttpServerSessionModeonHttpServerTransportOptions:initializeclients (2025-11-25and earlier)2026-07-28and later clientsStateless(default)Stateful-32022so dual-path clients downgradeStatefulForInitializeClientsUndefined
HttpServerSessionModevalues are rejected through options validation rather than silently falling through to hybrid behavior.In hybrid mode the effective mode is decided per request, so lifetimes follow the request rather than the endpoint:
2026-07-28requests resolve fromHttpContext.RequestServiceswith request scoping disabled;ConfigureSessionOptionsandRunSessionHandlerrun per request.initialize-handshake sessions resolve from the application provider with per-request scoping;ConfigureSessionOptionsandRunSessionHandlerrun once per session.GET/DELETEstay mapped, legacy SSE stays permitted, and idle tracking keeps running — but only legacy sessions can use them;2026-07-28GET/DELETEget405.Statelessis now obsolete (MCP9008)bool Statelesscan't express the third state, so it's obsoleted and reduced to a compatibility proxy overSessionMode, exactly as requested:Stateless = true→SessionMode = Stateless;Stateless = false→SessionMode = StatefulStatelessreturnstrueonly forSessionMode == Stateless(hybrid reads asfalse)Internal call sites, samples, docs samples, and tests were migrated off the obsolete property (the build runs with
TreatWarningsAsErrors).Tests
July2026ProtocolHybridSessionModeTestscovers the pre-merge checklist from the issue:ModernAndLegacyClients_ShareOneEndpoint_AndModernDoesNotDowngrade2026-07-28instead of downgradingLegacyClient_OnHybridServer_StillSupportsServerToClientElicitationModernRequests_UseRequestScopedServices_WhileLegacySessionsUseApplicationServicesConfigureSessionOptions_RunsPerRequestForModernClients_AndOncePerSessionForLegacyClientsConfigureSessionOptionslifetimes match the per-request modeRunSessionHandler_RunsPerRequestForModernClients_AndOncePerSessionForLegacyClientsRunSessionHandlerfollows the same per-request vs. per-session lifetimeModernPost_DoesNotMintSessionId_WhileLegacyInitializeDoes2026-07-28ModernPost_IgnoresMcpSessionIdHeaderMcp-Session-Iddoesn't attach a modern request to a sessionLegacyGetAndDelete_RemainAvailable_WhileModernGetAndDeleteReturn405GET/DELETEstill work while modern ones return405HttpServerTransportOptionsTestscovers theStateless↔SessionModeproxy semantics, including that assigning both doesn't throw and the last assignment wins.HttpMcpServerBuilderExtensionsTestsverifies undefined enum values fail options validation.Docs
docs/concepts/stateless/stateless.md— new "Hybrid mode (sessions for initialize clients only)" section,SessionModein the property reference, and all samples/prose migrated.docs/concepts/mrtr/mrtr.md,elicitation.md,sampling.md,roots.md— updated the statements that a stateful HTTP endpoint always refuses2026-07-28; hybrid mode serves it statelessly (so those clients use MRTR while legacy sessions keep theinitialize-era flows).docs/list-of-diagnostics.md— documentsMCP9008and rewordsMCP9006.ModelContextProtocol.AspNetCore, and references to “both session modes” now distinguish the threeSessionModeconfigurations from the two effective request behaviors.Validation
dotnet build— 0 warnings, 0 errorsdotnet test— all targets pass:Note
This pull request description and the accompanying changes were drafted with GitHub Copilot.