Skip to content

Commit c6a97d9

Browse files
saicharanpardhuCopilotjeffhandley
authored
Add HttpServerSessionMode for hybrid stateful/stateless HTTP serving (#1796)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com>
1 parent bba45c4 commit c6a97d9

40 files changed

Lines changed: 764 additions & 150 deletions

docs/concepts/completions/completions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Register a completion handler when building the server. The handler receives a r
2626

2727
```csharp
2828
builder.Services.AddMcpServer()
29-
.WithHttpTransport(o => o.Stateless = true)
29+
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
3030
.WithPrompts<MyPrompts>()
3131
.WithResources<MyResources>()
3232
.WithCompleteHandler(async (ctx, ct) =>

docs/concepts/elicitation/elicitation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Here's an example implementation of how a console application might handle elici
175175
[MRTR](xref:mrtr) is the SEP-2322 mechanism for server-driven input requests, finalized in protocol revision `2026-07-28`. In that revision, the server-to-client `elicitation/create` request method is removed; the recommended way to ask the user for input from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
176176

177177
> [!IMPORTANT]
178-
> `ElicitAsync` throws `InvalidOperationException("Elicitation is not supported in stateless mode.")` whenever the server is running stateless — including Streamable HTTP requests served under `2026-07-28` with `Stateless = true`. Stdio servers and initialize-handshake stateful Streamable HTTP sessions continue to work via the initialize-era server-to-client `elicitation/create` request flow; an HTTP server set to `Stateless = false` refuses `2026-07-28` so dual-path clients can fall back before using that flow. For code that needs to run on stateless servers — including `2026-07-28` Streamable HTTP — throw `InputRequiredException` from your handler instead. It works under both protocols and both session modes.
178+
> `ElicitAsync` throws `InvalidOperationException("Elicitation is not supported in stateless mode.")` whenever the server is running stateless — including every Streamable HTTP request served under `2026-07-28`. Stdio servers and initialize-handshake stateful Streamable HTTP sessions continue to work via the initialize-era server-to-client `elicitation/create` request flow; an HTTP server set to `SessionMode = HttpServerSessionMode.Stateful` refuses `2026-07-28` so dual-path clients can fall back before using that flow, while `HttpServerSessionMode.StatefulForInitializeClients` instead serves `2026-07-28` statelessly on the same endpoint (see [hybrid mode](xref:stateless#hybrid-mode-sessions-for-initialize-clients-only)), so those requests use MRTR while `initialize`-handshake sessions keep this flow. For code that needs to run on stateless servers — including `2026-07-28` Streamable HTTP — throw `InputRequiredException` from your handler instead. It works across both protocol eras and all three HTTP `SessionMode` configurations.
179179
180180
For example:
181181

docs/concepts/elicitation/samples/server/Program.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Elicitation.Tools;
2+
using ModelContextProtocol.AspNetCore;
23

34
var builder = WebApplication.CreateBuilder(args);
45

@@ -8,8 +9,8 @@
89
.WithHttpTransport(options =>
910
{
1011
// Elicitation requires stateful mode because it sends server-to-client requests.
11-
// Set Stateless = false explicitly for forward compatibility in case the default changes.
12-
options.Stateless = false;
12+
// Set SessionMode = HttpServerSessionMode.Stateful since it's required.
13+
options.SessionMode = HttpServerSessionMode.Stateful;
1314
})
1415
.WithTools<InteractiveTools>();
1516

docs/concepts/filters.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ To enable authorization support, call `AddAuthorizationFilters()` when configuri
411411

412412
```csharp
413413
services.AddMcpServer()
414-
.WithHttpTransport(o => o.Stateless = true)
414+
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
415415
.AddAuthorizationFilters() // Enable authorization filter support
416416
.WithTools<WeatherTools>();
417417
```
@@ -511,7 +511,7 @@ This allows you to implement logging, metrics, or other cross-cutting concerns t
511511

512512
```csharp
513513
services.AddMcpServer()
514-
.WithHttpTransport(o => o.Stateless = true)
514+
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
515515
.WithRequestFilters(requestFilters =>
516516
{
517517
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
@@ -546,6 +546,8 @@ services.AddMcpServer()
546546
To use authorization features, you must configure authentication and authorization in your ASP.NET Core application and call `AddAuthorizationFilters()`:
547547

548548
```csharp
549+
using ModelContextProtocol.AspNetCore;
550+
549551
var builder = WebApplication.CreateBuilder(args);
550552

551553
builder.Services.AddAuthentication("Bearer")
@@ -556,7 +558,7 @@ builder.Services.AddAuthorization();
556558
builder.Services.AddMcpServer()
557559
.WithHttpTransport(options =>
558560
{
559-
options.Stateless = true;
561+
options.SessionMode = HttpServerSessionMode.Stateless;
560562
})
561563
.AddAuthorizationFilters() // Required for authorization support
562564
.WithTools<WeatherTools>()

docs/concepts/getting-started.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,18 @@ dotnet add package ModelContextProtocol.AspNetCore
7878
And add the following code:
7979

8080
```csharp
81+
using ModelContextProtocol.AspNetCore;
8182
using ModelContextProtocol.Server;
8283
using System.ComponentModel;
8384

8485
var builder = WebApplication.CreateBuilder(args);
8586
builder.Services.AddMcpServer()
8687
.WithHttpTransport(options =>
8788
{
88-
// Stateless mode is recommended for servers that don't need
89-
// server-to-client requests like sampling or elicitation.
90-
// See the Stateless and Stateful documentation for details.
91-
options.Stateless = true;
89+
// Stateless mode is the default and recommended for servers that
90+
// don't need server-to-client requests like sampling or elicitation.
91+
// See the Stateless and Stateful documentation for details.
92+
options.SessionMode = HttpServerSessionMode.Stateless;
9293
})
9394
.WithToolsFromAssembly();
9495
var app = builder.Build();

docs/concepts/httpcontext/samples/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using HttpContext.Tools;
2+
using ModelContextProtocol.AspNetCore;
23

34
var builder = WebApplication.CreateBuilder(args);
45

@@ -7,7 +8,7 @@
78
builder.Services.AddMcpServer()
89
.WithHttpTransport(options =>
910
{
10-
options.Stateless = true;
11+
options.SessionMode = HttpServerSessionMode.Stateless;
1112
})
1213
.WithTools<ContextTools>();
1314

docs/concepts/logging/samples/server/Program.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Logging.Tools;
2+
using ModelContextProtocol.AspNetCore;
23

34
var builder = WebApplication.CreateBuilder(args);
45

@@ -8,8 +9,8 @@
89
.WithHttpTransport(options =>
910
{
1011
// Log streaming requires stateful mode because the server pushes log notifications
11-
// to clients. Set Stateless = false explicitly for forward compatibility.
12-
options.Stateless = false;
12+
// to clients. Set SessionMode = HttpServerSessionMode.Stateful since it's required.
13+
options.SessionMode = HttpServerSessionMode.Stateful;
1314
})
1415
.WithTools<LoggingTools>();
1516
// .WithSetLoggingLevelHandler(async (ctx, ct) => new EmptyResult());

docs/concepts/mrtr/mrtr.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ MRTR is useful when:
2929

3030
## Opting in
3131

32-
MRTR activates when both peers negotiate protocol revision **`2026-07-28`**. The C# SDK client prefers `2026-07-28` by default — it probes with `server/discover` and falls back to an `initialize` handshake only when the server doesn't support it. Stateless HTTP servers accept `2026-07-28` automatically when a client offers it; HTTP servers configured with `Stateless = false` refuse that revision with `UnsupportedProtocolVersion` so dual-path clients can fall back to a session-capable revision. No experimental flags are required; pinning `ProtocolVersion` to an initialize-capable revision opts back out.
32+
MRTR activates when both peers negotiate protocol revision **`2026-07-28`**. The C# SDK client prefers `2026-07-28` by default — it probes with `server/discover` and falls back to an `initialize` handshake only when the server doesn't support it. Stateless HTTP servers accept `2026-07-28` automatically when a client offers it; HTTP servers configured with `SessionMode = HttpServerSessionMode.Stateful` refuse that revision with `UnsupportedProtocolVersion` so dual-path clients can fall back to a session-capable revision. `HttpServerSessionMode.StatefulForInitializeClients` ([hybrid mode](xref:stateless#hybrid-mode-sessions-for-initialize-clients-only)) accepts `2026-07-28` statelessly — and therefore enables MRTR — while still issuing sessions to `initialize`-handshake clients on the same endpoint. No experimental flags are required; pinning `ProtocolVersion` to an initialize-capable revision opts back out.
3333

3434
```csharp
3535
// Client — the SDK prefers 2026-07-28 (and therefore MRTR) by default.
@@ -373,7 +373,7 @@ public static string CloseSupportTicket(
373373
374374
## Compatibility
375375

376-
The SDK supports `InputRequiredException` across two protocol revisions and two session modes:
376+
The SDK supports `InputRequiredException` across protocol eras and effective request modes:
377377

378378
| Negotiated protocol | Session mode | Behavior |
379379
|----------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -391,4 +391,4 @@ The SDK supports `InputRequiredException` across two protocol revisions and two
391391

392392
Under `2025-11-25` and earlier, stdio and stateful Streamable HTTP keep `ClientCapabilities` populated, so the legacy methods work normally and remain the recommended way to do one-shot client interactions. Under `2026-07-28`, the spec removes those request methods from Streamable HTTP entirely; the SDK still allows the legacy methods on `2026-07-28` stdio sessions because stdio is implicitly single-process / stateful and the client handler is wired up regardless of negotiated revision. `InputRequiredException` is the way to write tools that work on every supported configuration.
393393

394-
Because `2026-07-28` removes `Mcp-Session-Id` (SEP-2567) and the `initialize` handshake (SEP-2575), Streamable HTTP can serve that revision only through the stateless path. The `Stateful` row for `2026-07-28` in the compatibility matrix above therefore applies to stdio and other non-HTTP stateful sessions; an HTTP server explicitly set to `Stateless = false` refuses `2026-07-28` with `UnsupportedProtocolVersion` and creates a session only when an older client falls back to `initialize`.
394+
Because `2026-07-28` removes `Mcp-Session-Id` (SEP-2567) and the `initialize` handshake (SEP-2575), Streamable HTTP can serve that revision only through the stateless path. The `Stateful` row for `2026-07-28` in the compatibility matrix above therefore applies to stdio and other non-HTTP stateful sessions; an HTTP server explicitly set to `SessionMode = HttpServerSessionMode.Stateful` refuses `2026-07-28` with `UnsupportedProtocolVersion` and creates a session only when an older client falls back to `initialize`. `HttpServerSessionMode.StatefulForInitializeClients` ([hybrid mode](xref:stateless#hybrid-mode-sessions-for-initialize-clients-only)) serves `2026-07-28` requests through the stateless path — the `2026-07-28` / `Stateless` row — while `initialize`-handshake clients on the same endpoint follow the `2025-11-25` / `Stateful` row.

docs/concepts/pagination/pagination.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ When implementing custom list handlers on the server, pagination is supported by
7070

7171
```csharp
7272
builder.Services.AddMcpServer()
73-
.WithHttpTransport(o => o.Stateless = true)
73+
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
7474
.WithListResourcesHandler(async (ctx, ct) =>
7575
{
7676
const int pageSize = 10;

docs/concepts/progress/samples/server/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ModelContextProtocol.AspNetCore;
12
using Progress.Tools;
23

34
var builder = WebApplication.CreateBuilder(args);
@@ -7,7 +8,7 @@
78
builder.Services.AddMcpServer()
89
.WithHttpTransport(options =>
910
{
10-
options.Stateless = true;
11+
options.SessionMode = HttpServerSessionMode.Stateless;
1112
})
1213
.WithTools<LongRunningTools>();
1314

0 commit comments

Comments
 (0)