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>
This commit is contained in:
parent
bba45c44bf
commit
c6a97d94d6
40 changed files with 764 additions and 150 deletions
|
|
@ -26,7 +26,7 @@ Register a completion handler when building the server. The handler receives a r
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithPrompts<MyPrompts>()
|
.WithPrompts<MyPrompts>()
|
||||||
.WithResources<MyResources>()
|
.WithResources<MyResources>()
|
||||||
.WithCompleteHandler(async (ctx, ct) =>
|
.WithCompleteHandler(async (ctx, ct) =>
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ Here's an example implementation of how a console application might handle elici
|
||||||
[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.
|
[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.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> `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.
|
> `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.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Elicitation.Tools;
|
using Elicitation.Tools;
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
@ -8,8 +9,8 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Elicitation requires stateful mode because it sends server-to-client requests.
|
// Elicitation requires stateful mode because it sends server-to-client requests.
|
||||||
// Set Stateless = false explicitly for forward compatibility in case the default changes.
|
// Set SessionMode = HttpServerSessionMode.Stateful since it's required.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
})
|
})
|
||||||
.WithTools<InteractiveTools>();
|
.WithTools<InteractiveTools>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,7 @@ To enable authorization support, call `AddAuthorizationFilters()` when configuri
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.AddAuthorizationFilters() // Enable authorization filter support
|
.AddAuthorizationFilters() // Enable authorization filter support
|
||||||
.WithTools<WeatherTools>();
|
.WithTools<WeatherTools>();
|
||||||
```
|
```
|
||||||
|
|
@ -511,7 +511,7 @@ This allows you to implement logging, metrics, or other cross-cutting concerns t
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
services.AddMcpServer()
|
services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithRequestFilters(requestFilters =>
|
.WithRequestFilters(requestFilters =>
|
||||||
{
|
{
|
||||||
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
|
||||||
|
|
@ -546,6 +546,8 @@ services.AddMcpServer()
|
||||||
To use authorization features, you must configure authentication and authorization in your ASP.NET Core application and call `AddAuthorizationFilters()`:
|
To use authorization features, you must configure authentication and authorization in your ASP.NET Core application and call `AddAuthorizationFilters()`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddAuthentication("Bearer")
|
builder.Services.AddAuthentication("Bearer")
|
||||||
|
|
@ -556,7 +558,7 @@ builder.Services.AddAuthorization();
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.AddAuthorizationFilters() // Required for authorization support
|
.AddAuthorizationFilters() // Required for authorization support
|
||||||
.WithTools<WeatherTools>()
|
.WithTools<WeatherTools>()
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ dotnet add package ModelContextProtocol.AspNetCore
|
||||||
And add the following code:
|
And add the following code:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
|
@ -85,10 +86,10 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Stateless mode is recommended for servers that don't need
|
// Stateless mode is the default and recommended for servers that
|
||||||
// server-to-client requests like sampling or elicitation.
|
// don't need server-to-client requests like sampling or elicitation.
|
||||||
// See the Stateless and Stateful documentation for details.
|
// See the Stateless and Stateful documentation for details.
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.WithToolsFromAssembly();
|
.WithToolsFromAssembly();
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using HttpContext.Tools;
|
using HttpContext.Tools;
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
@ -7,7 +8,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.WithTools<ContextTools>();
|
.WithTools<ContextTools>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Logging.Tools;
|
using Logging.Tools;
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
|
@ -8,8 +9,8 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Log streaming requires stateful mode because the server pushes log notifications
|
// Log streaming requires stateful mode because the server pushes log notifications
|
||||||
// to clients. Set Stateless = false explicitly for forward compatibility.
|
// to clients. Set SessionMode = HttpServerSessionMode.Stateful since it's required.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
})
|
})
|
||||||
.WithTools<LoggingTools>();
|
.WithTools<LoggingTools>();
|
||||||
// .WithSetLoggingLevelHandler(async (ctx, ct) => new EmptyResult());
|
// .WithSetLoggingLevelHandler(async (ctx, ct) => new EmptyResult());
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ MRTR is useful when:
|
||||||
|
|
||||||
## Opting in
|
## Opting in
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// Client — the SDK prefers 2026-07-28 (and therefore MRTR) by default.
|
// Client — the SDK prefers 2026-07-28 (and therefore MRTR) by default.
|
||||||
|
|
@ -373,7 +373,7 @@ public static string CloseSupportTicket(
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
The SDK supports `InputRequiredException` across two protocol revisions and two session modes:
|
The SDK supports `InputRequiredException` across protocol eras and effective request modes:
|
||||||
|
|
||||||
| Negotiated protocol | Session mode | Behavior |
|
| Negotiated protocol | Session mode | Behavior |
|
||||||
|----------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
|----------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
|
@ -391,4 +391,4 @@ The SDK supports `InputRequiredException` across two protocol revisions and two
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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`.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ When implementing custom list handlers on the server, pagination is supported by
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithListResourcesHandler(async (ctx, ct) =>
|
.WithListResourcesHandler(async (ctx, ct) =>
|
||||||
{
|
{
|
||||||
const int pageSize = 10;
|
const int pageSize = 10;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
using Progress.Tools;
|
using Progress.Tools;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
@ -7,7 +8,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.WithTools<LongRunningTools>();
|
.WithTools<LongRunningTools>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Register prompt types when building the server:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithPrompts<MyPrompts>()
|
.WithPrompts<MyPrompts>()
|
||||||
.WithPrompts<CodePrompts>();
|
.WithPrompts<CodePrompts>();
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ Register resource types when building the server:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithResources<MyResources>()
|
.WithResources<MyResources>()
|
||||||
.WithResources<DocumentResources>();
|
.WithResources<DocumentResources>();
|
||||||
```
|
```
|
||||||
|
|
@ -209,8 +209,8 @@ Register subscription handlers when building the server:
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
// Subscriptions require stateful mode because the server pushes change notifications
|
// Subscriptions require stateful mode because the server pushes change notifications
|
||||||
// to clients. Set Stateless = false explicitly for forward compatibility.
|
// to clients. Set SessionMode = HttpServerSessionMode.Stateful since sessions are required.
|
||||||
.WithHttpTransport(o => o.Stateless = false)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateful)
|
||||||
.WithResources<MyResources>()
|
.WithResources<MyResources>()
|
||||||
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ server.RegisterNotificationHandler(
|
||||||
[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 `roots/list` request method is removed; the recommended way to ask the client for its roots from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
|
[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 `roots/list` request method is removed; the recommended way to ask the client for its roots from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> `RequestRootsAsync` throws `InvalidOperationException("Roots are 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 `roots/list` 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.
|
> `RequestRootsAsync` throws `InvalidOperationException("Roots are 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 `roots/list` 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.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ Sampling requires the client to advertise the `sampling` capability. This is han
|
||||||
[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 `sampling/createMessage` request method is removed; the recommended way to ask the client to sample from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
|
[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 `sampling/createMessage` request method is removed; the recommended way to ask the client to sample from a server handler is to throw <xref:ModelContextProtocol.Protocol.InputRequiredException> and let the SDK emit an <xref:ModelContextProtocol.Protocol.InputRequiredResult> on the wire.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> `SampleAsync` and `AsSamplingChatClient` throw `InvalidOperationException("Sampling 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 `sampling/createMessage` 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.
|
> `SampleAsync` and `AsSamplingChatClient` throw `InvalidOperationException("Sampling 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 `sampling/createMessage` 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.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ uid: stateless
|
||||||
|
|
||||||
# Stateless and stateful mode
|
# Stateless and stateful mode
|
||||||
|
|
||||||
The MCP [Streamable HTTP transport] uses an `Mcp-Session-Id` HTTP header to associate multiple requests with a single logical session. However, **we recommend most servers disable sessions entirely by setting <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> to `true`**. Stateless mode avoids the complexity, memory overhead, and deployment constraints that come with sessions. Sessions are only necessary when the server needs to push [unsolicited notifications](#how-streamable-http-delivers-messages), maintain per-client state across requests, or send requests _to_ clients that don't support [MRTR](xref:mrtr).
|
The MCP [Streamable HTTP transport] uses an `Mcp-Session-Id` HTTP header to associate multiple requests with a single logical session. However, **we recommend most servers disable sessions entirely by setting <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.SessionMode> to <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.Stateless>**. Stateless mode avoids the complexity, memory overhead, and deployment constraints that come with sessions. Sessions are only necessary when the server needs to push [unsolicited notifications](#how-streamable-http-delivers-messages), maintain per-client state across requests, or send requests _to_ clients that don't support [MRTR](xref:mrtr).
|
||||||
|
|
||||||
When sessions are enabled (`Stateless = false`), the server creates and tracks an in-memory session for each client, while the client automatically includes the session ID in subsequent requests. The [MCP specification requires](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) that clients use sessions when a server's `initialize` response includes an `Mcp-Session-Id` header — this is not optional for the client. Session expiry detection and reconnection are the responsibility of the application using the client SDK (see [Client-side session behavior](#client-side-session-behavior)).
|
When sessions are enabled (`SessionMode = HttpServerSessionMode.Stateful`), the server creates and tracks an in-memory session for each client, while the client automatically includes the session ID in subsequent requests. The [MCP specification requires](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) that clients use sessions when a server's `initialize` response includes an `Mcp-Session-Id` header — this is not optional for the client. Session expiry detection and reconnection are the responsibility of the application using the client SDK (see [Client-side session behavior](#client-side-session-behavior)).
|
||||||
|
|
||||||
[Streamable HTTP transport]: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
[Streamable HTTP transport]: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http
|
||||||
|
|
||||||
|
|
@ -20,29 +20,36 @@ When sessions are enabled (`Stateless = false`), the server creates and tracks a
|
||||||
- Do you need to support clients that only speak the [legacy SSE transport](#legacy-sse-transport)? → **Use stateful** with <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> (disabled by default due to [backpressure concerns](#request-backpressure)).
|
- Do you need to support clients that only speak the [legacy SSE transport](#legacy-sse-transport)? → **Use stateful** with <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> (disabled by default due to [backpressure concerns](#request-backpressure)).
|
||||||
- Does your server manage per-client state that concurrent agents must not share (isolated environments, parallel workspaces)? → **Use stateful.**
|
- Does your server manage per-client state that concurrent agents must not share (isolated environments, parallel workspaces)? → **Use stateful.**
|
||||||
- Are you debugging a typically-stdio server over HTTP and want editors to be able to reset state by reconnecting? → **Use stateful.**
|
- Are you debugging a typically-stdio server over HTTP and want editors to be able to reset state by reconnecting? → **Use stateful.**
|
||||||
- Otherwise → **Use stateless** (`options.Stateless = true`).
|
- Does your server need sessions for existing clients but must also serve `2026-07-28` clients natively on the same endpoint? → **Use [hybrid mode](#hybrid-mode-sessions-for-initialize-clients-only)** (`options.SessionMode = HttpServerSessionMode.StatefulForInitializeClients`).
|
||||||
|
- Otherwise → **Use stateless** (`options.SessionMode = HttpServerSessionMode.Stateless`).
|
||||||
|
|
||||||
<!-- mlc-disable-next-line -->
|
<!-- mlc-disable-next-line -->
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> **Why is stateless now the default?** Earlier versions of the SDK defaulted to stateful, but not because the `2025-11-25` (and older) protocol revisions ever required a server to use the `Mcp-Session-Id` header. They didn't. The original SSE transport could only operate statefully, and keeping Streamable HTTP stateful by default let server-to-client requests (elicitation, sampling, roots) keep working on `2025-11-25` the way they always had. A client was required to echo a server-assigned `Mcp-Session-Id` on later requests, but whether to assign one was always the server's choice. The `2026-07-28` protocol revision removes the header (SEP-2567) and the `initialize` handshake (SEP-2575) from the wire format entirely, and server-to-client requests now run through [MRTR](xref:mrtr), so the SDK now defaults <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> to `true` to match the new wire format. You can still opt back into sessions with `Stateless = false` for [unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, per-client isolation, or server-to-client requests against clients that don't support [MRTR](xref:mrtr) — see [Stateful mode (sessions)](#stateful-mode-sessions).
|
> **Why is stateless now the default?** Earlier versions of the SDK defaulted to stateful, but not because the `2025-11-25` (and older) protocol revisions ever required a server to use the `Mcp-Session-Id` header. They didn't. The original SSE transport could only operate statefully, and keeping Streamable HTTP stateful by default let server-to-client requests (elicitation, sampling, roots) keep working on `2025-11-25` the way they always had. A client was required to echo a server-assigned `Mcp-Session-Id` on later requests, but whether to assign one was always the server's choice. The `2026-07-28` protocol revision removes the header (SEP-2567) and the `initialize` handshake (SEP-2575) from the wire format entirely, and server-to-client requests now run through [MRTR](xref:mrtr), so the SDK now defaults <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.SessionMode> to <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.Stateless> to match the new wire format. You can still opt back into sessions with `SessionMode = HttpServerSessionMode.Stateful` for [unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, per-client isolation, or server-to-client requests against clients that don't support [MRTR](xref:mrtr) — see [Stateful mode (sessions)](#stateful-mode-sessions).
|
||||||
|
|
||||||
## Forward and backward compatibility
|
## Forward and backward compatibility
|
||||||
|
|
||||||
The `Stateless` property is the single most important setting for forward-proofing your MCP server. The default is now `Stateless = true` (sessions disabled), which is the forward-compatible setting for the `2026-07-28` protocol revision and beyond. Stateless servers still respond to clients on `2025-11-25` and earlier — the SDK keeps the `initialize` + `Mcp-Session-Id` handshake available for those clients — but they cannot use the session-dependent features ([unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, per-client isolation). Server-to-client requests are the exception: [elicitation](xref:elicitation) — and the now-deprecated [sampling](xref:sampling) and [roots](xref:roots) — can run statelessly through [MRTR](xref:mrtr) when both peers support `2026-07-28`. We recommend every server set `Stateless` explicitly rather than relying on the default:
|
The `SessionMode` property is the single most important setting for forward-proofing your MCP server. The default is now <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.Stateless> (sessions disabled), which is the forward-compatible setting for the `2026-07-28` protocol revision and beyond. Stateless servers still respond to clients on `2025-11-25` and earlier — the SDK keeps the `initialize` + `Mcp-Session-Id` handshake available for those clients — but they cannot use the session-dependent features ([unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, per-client isolation). Server-to-client requests are the exception: [elicitation](xref:elicitation) — and the now-deprecated [sampling](xref:sampling) and [roots](xref:roots) — can run statelessly through [MRTR](xref:mrtr) when both peers support `2026-07-28`. We recommend every server set `SessionMode` explicitly rather than relying on the default:
|
||||||
|
|
||||||
- **`Stateless = true`** — the current default and the forward-compatible choice. Your server opts out of sessions entirely and the `Mcp-Session-Id` header is never sent or used. The `2026-07-28` protocol revision drops the `initialize` handshake and `Mcp-Session-Id` from the wire format entirely, so this is the only configuration that lets the server respond to `2026-07-28` clients without falling back to initialize-handshake handling. If you don't need [unsolicited notifications](#how-streamable-http-delivers-messages), server-to-client requests, or session-scoped state, this is the setting to use today.
|
- **`HttpServerSessionMode.Stateless`** — the current default and the forward-compatible choice. Your server opts out of sessions entirely and the `Mcp-Session-Id` header is never sent or used. The `2026-07-28` protocol revision drops the `initialize` handshake and `Mcp-Session-Id` from the wire format entirely, so this configuration lets the server respond to `2026-07-28` clients without falling back to initialize-handshake handling. If you don't need [unsolicited notifications](#how-streamable-http-delivers-messages), server-to-client requests, or session-scoped state, this is the setting to use today.
|
||||||
|
|
||||||
- **`Stateless = false`** — the right choice when your server depends on sessions for [unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, or per-client isolation, none of which work without a session. Setting this explicitly protects your server from a future default change, and the [MCP specification requires](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) that clients use sessions when a server's `initialize` response includes an `Mcp-Session-Id` header, so compliant clients always honor your server's session. Server-to-client requests no longer force a session: [elicitation](xref:elicitation) — and the now-deprecated [sampling](xref:sampling) and [roots](xref:roots) — can run statelessly through [MRTR](xref:mrtr) (see [Stateless alternatives for server-to-client interactions](#stateless-alternatives-for-server-to-client-interactions)). Keep a session if you need server-to-client requests against clients that do not support `2026-07-28`. Note that with `Stateless = false`, a `2026-07-28` request is refused with `UnsupportedProtocolVersion`; the stateful path activates only when a client falls back to an initialize-capable revision.
|
- **`HttpServerSessionMode.Stateful`** — the right choice when your server depends on sessions for [unsolicited notifications](#how-streamable-http-delivers-messages), resource subscriptions, or per-client isolation, none of which work without a session. Setting this explicitly protects your server from a future default change, and the [MCP specification requires](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http) that clients use sessions when a server's `initialize` response includes an `Mcp-Session-Id` header, so compliant clients always honor your server's session. Server-to-client requests no longer force a session: [elicitation](xref:elicitation) — and the now-deprecated [sampling](xref:sampling) and [roots](xref:roots) — can run statelessly through [MRTR](xref:mrtr) (see [Stateless alternatives for server-to-client interactions](#stateless-alternatives-for-server-to-client-interactions)). Keep a session if you need server-to-client requests against clients that do not support `2026-07-28`. Note that in this mode a `2026-07-28` request is refused with `UnsupportedProtocolVersion`; the stateful path activates only when a client falls back to an initialize-capable revision.
|
||||||
|
|
||||||
|
- **`HttpServerSessionMode.StatefulForInitializeClients`** — the migration mode. `initialize`-handshake clients get full sessions while `2026-07-28` and later clients are served statelessly on the same endpoint, so you can adopt the new revision progressively instead of waiting for every client to migrate. See [Hybrid mode](#hybrid-mode-sessions-for-initialize-clients-only).
|
||||||
|
|
||||||
<!-- mlc-disable-next-line -->
|
<!-- mlc-disable-next-line -->
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you're not sure which to pick, leave the default (`Stateless = true`). You can switch to `Stateless = false` later if you discover you need unsolicited notifications or resource subscriptions. Either way, setting the property explicitly means your server's behavior won't silently change when the SDK default is updated.
|
> If you're not sure which to pick, leave the default (`HttpServerSessionMode.Stateless`). You can switch to `HttpServerSessionMode.Stateful` later if you discover you need unsolicited notifications or resource subscriptions. Either way, setting the property explicitly means your server's behavior won't silently change when the SDK default is updated.
|
||||||
|
|
||||||
|
<!-- mlc-disable-next-line -->
|
||||||
|
> [!NOTE]
|
||||||
|
> The `bool` <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> property remains a convenient shorthand for the two most common modes: assigning `true` selects `Stateless` and assigning `false` selects `Stateful`, while reading it returns `true` only for `Stateless`. Use `SessionMode` when you need `StatefulForInitializeClients`. Both properties update the same underlying value, so the last assignment wins.
|
||||||
|
|
||||||
### The 2026-07-28 protocol revision
|
### The 2026-07-28 protocol revision
|
||||||
|
|
||||||
The `2026-07-28` protocol revision goes further than `Stateless = true`: it removes the `initialize` handshake (SEP-2575) and the `Mcp-Session-Id` header (SEP-2567) from the wire format entirely. Clients bootstrap by sending `server/discover` instead, and every request carries the negotiated protocol version in the `MCP-Protocol-Version` HTTP header (HTTP transport) or the `_meta.io.modelcontextprotocol/protocolVersion` JSON-RPC field (every transport).
|
The `2026-07-28` protocol revision goes further than stateless mode: it removes the `initialize` handshake (SEP-2575) and the `Mcp-Session-Id` header (SEP-2567) from the wire format entirely. Clients bootstrap by sending `server/discover` instead, and every request carries the negotiated protocol version in the `MCP-Protocol-Version` HTTP header (HTTP transport) or the `_meta.io.modelcontextprotocol/protocolVersion` JSON-RPC field (every transport).
|
||||||
|
|
||||||
**Server side.** With `Stateless = true` (the default), the SDK already meets `2026-07-28` on the wire. Any HTTP `POST` that arrives with the `2026-07-28` `MCP-Protocol-Version` header is routed through the stateless path automatically — no session is created, no `Mcp-Session-Id` is returned, and the `GET` and `DELETE` endpoints aren't mapped. Clients that still send `initialize` on the same endpoint continue to work in stateless mode for the lifetime of that single POST. With `Stateless = false`, the server still creates HTTP sessions when the client speaks `2025-11-25` or earlier — but a `2026-07-28` request on a stateful server is refused with a `-32022 UnsupportedProtocolVersion` error, so a dual-path client downgrades to the `initialize` handshake and obtains a session. If a `2026-07-28` request carries an `Mcp-Session-Id`, the server ignores the header and still does not echo or mint a session ID for that request.
|
**Server side.** With `SessionMode = HttpServerSessionMode.Stateless` (the default), the SDK already meets `2026-07-28` on the wire. Any HTTP `POST` that arrives with the `2026-07-28` `MCP-Protocol-Version` header is routed through the stateless path automatically — no session is created, no `Mcp-Session-Id` is returned, and the `GET` and `DELETE` endpoints aren't mapped. Clients that still send `initialize` on the same endpoint continue to work in stateless mode for the lifetime of that single POST. With `SessionMode = HttpServerSessionMode.Stateful`, the server still creates HTTP sessions when the client speaks `2025-11-25` or earlier — but a `2026-07-28` request on a stateful server is refused with a `-32022 UnsupportedProtocolVersion` error, so a dual-path client downgrades to the `initialize` handshake and obtains a session. `SessionMode = HttpServerSessionMode.StatefulForInitializeClients` opts out of that downgrade and serves the `2026-07-28` request statelessly instead — see [Hybrid mode](#hybrid-mode-sessions-for-initialize-clients-only). If a `2026-07-28` request carries an `Mcp-Session-Id`, the server ignores the header and still does not echo or mint a session ID for that request, in every mode.
|
||||||
|
|
||||||
**Stateful options marked obsolete.** Because Streamable HTTP no longer supports sessions starting with the `2026-07-28` revision, the stateful-only knobs on <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions> — `IdleTimeout`, `MaxIdleSessionCount`, `EventStreamStore`, `SessionMigrationHandler`, and `PerSessionExecutionContext` — are now marked `[Obsolete]` with diagnostic `MCP9006` to signal that they only apply to initialize-handshake back-compat. You can still set them — the warning is informational — and they continue to govern stateful behavior for initialize-capable clients.
|
**Stateful options marked obsolete.** Because Streamable HTTP no longer supports sessions starting with the `2026-07-28` revision, the stateful-only knobs on <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions> — `IdleTimeout`, `MaxIdleSessionCount`, `EventStreamStore`, `SessionMigrationHandler`, and `PerSessionExecutionContext` — are now marked `[Obsolete]` with diagnostic `MCP9006` to signal that they only apply to initialize-handshake back-compat. You can still set them — the warning is informational — and they continue to govern stateful behavior for initialize-capable clients.
|
||||||
|
|
||||||
|
|
@ -80,7 +87,7 @@ With the default <xref:ModelContextProtocol.Client.HttpTransportMode.AutoDetect>
|
||||||
|
|
||||||
**Server-side migration.** If you previously relied on `/sse` being mapped automatically, you now need `EnableLegacySse = true` (suppressing the `MCP9004` warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove `EnableLegacySse`.
|
**Server-side migration.** If you previously relied on `/sse` being mapped automatically, you now need `EnableLegacySse = true` (suppressing the `MCP9004` warning) to keep serving those endpoints. The recommended path is to migrate all clients to Streamable HTTP and then remove `EnableLegacySse`.
|
||||||
|
|
||||||
**Transition period.** If some clients still need SSE while others have already migrated to Streamable HTTP, set `EnableLegacySse = true` with `Stateless = false`. Both transports are served simultaneously by `MapMcp()` — Streamable HTTP on the root endpoint and SSE on `/sse` and `/message`. Once all clients have migrated, remove `EnableLegacySse` and optionally switch to `Stateless = true`.
|
**Transition period.** If some clients still need SSE while others have already migrated to Streamable HTTP, set `EnableLegacySse = true` with `SessionMode = HttpServerSessionMode.Stateful`. Both transports are served simultaneously by `MapMcp()` — Streamable HTTP on the root endpoint and SSE on `/sse` and `/message`. Once all clients have migrated, remove `EnableLegacySse` and optionally switch to `SessionMode = HttpServerSessionMode.Stateless`.
|
||||||
|
|
||||||
## Stateless mode (recommended)
|
## Stateless mode (recommended)
|
||||||
|
|
||||||
|
|
@ -89,12 +96,14 @@ Stateless mode is the recommended default for HTTP-based MCP servers. When enabl
|
||||||
### Enabling stateless mode
|
### Enabling stateless mode
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
|
|
||||||
|
|
@ -105,7 +114,7 @@ app.Run();
|
||||||
|
|
||||||
### What stateless mode changes
|
### What stateless mode changes
|
||||||
|
|
||||||
When <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> is `true`:
|
When <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.SessionMode> is <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.Stateless>:
|
||||||
|
|
||||||
- <xref:ModelContextProtocol.McpSession.SessionId> is `null`, and the `Mcp-Session-Id` header is not sent or expected
|
- <xref:ModelContextProtocol.McpSession.SessionId> is `null`, and the `Mcp-Session-Id` header is not sent or expected
|
||||||
- Each HTTP request creates a fresh server context — no state carries over between requests
|
- Each HTTP request creates a fresh server context — no state carries over between requests
|
||||||
|
|
@ -145,7 +154,7 @@ This means servers that need user confirmation ([elicitation](xref:elicitation))
|
||||||
|
|
||||||
## Stateful mode (sessions)
|
## Stateful mode (sessions)
|
||||||
|
|
||||||
When <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> is `false`, the server assigns an `Mcp-Session-Id` to each client during the `initialize` handshake when the client speaks the `2025-11-25` (or earlier) protocol revision. The client must include this header in all subsequent requests. The server maintains an in-memory session for each connected client, enabling:
|
When <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.SessionMode> is <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.Stateful>, the server assigns an `Mcp-Session-Id` to each client during the `initialize` handshake when the client speaks the `2025-11-25` (or earlier) protocol revision. The client must include this header in all subsequent requests. The server maintains an in-memory session for each connected client, enabling:
|
||||||
|
|
||||||
- Server-to-client requests (sampling, elicitation, roots) via an open HTTP response stream
|
- Server-to-client requests (sampling, elicitation, roots) via an open HTTP response stream
|
||||||
- [Unsolicited notifications](#how-streamable-http-delivers-messages) (resource updates, logging messages) via the `GET` stream
|
- [Unsolicited notifications](#how-streamable-http-delivers-messages) (resource updates, logging messages) via the `GET` stream
|
||||||
|
|
@ -166,6 +175,42 @@ Use stateful mode when your server needs one or more of:
|
||||||
|
|
||||||
The [deployment considerations](#deployment-considerations) section lists real concerns for production, internet-facing services — but many MCP servers don't run in that context. For single-instance servers, internal tools, and dev/test clusters, session affinity and memory overhead are less of a concern, and sessions provide the richest feature set.
|
The [deployment considerations](#deployment-considerations) section lists real concerns for production, internet-facing services — but many MCP servers don't run in that context. For single-instance servers, internal tools, and dev/test clusters, session affinity and memory overhead are less of a concern, and sessions provide the richest feature set.
|
||||||
|
|
||||||
|
## Hybrid mode (sessions for initialize clients only)
|
||||||
|
|
||||||
|
<xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode.StatefulForInitializeClients> serves both eras on a single endpoint: clients that send the `initialize` handshake (`2025-11-25` and earlier) get a full stateful session, while clients using `2026-07-28` and later are served statelessly, per request. It exists so an existing stateful server can adopt the new protocol revision progressively instead of waiting for every client to migrate first.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
builder.Services.AddMcpServer()
|
||||||
|
.WithHttpTransport(options =>
|
||||||
|
{
|
||||||
|
options.SessionMode = HttpServerSessionMode.StatefulForInitializeClients;
|
||||||
|
})
|
||||||
|
.WithTools<MyTools>();
|
||||||
|
```
|
||||||
|
|
||||||
|
### What each client sees
|
||||||
|
|
||||||
|
| Client | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| `2025-11-25` and earlier (sends `initialize`) | Full stateful session, `Mcp-Session-Id` issued and echoed, `GET` and `DELETE` available, server-to-client requests supported |
|
||||||
|
| `2026-07-28` and later (sends `server/discover`) | Served per request with no session ID minted or echoed; `GET` and `DELETE` return `405 Method Not Allowed`; no downgrade to `initialize` |
|
||||||
|
|
||||||
|
Without hybrid mode, `HttpServerSessionMode.Stateful` refuses a `2026-07-28` request with `-32022 UnsupportedProtocolVersion` so that dual-path clients fall back to `initialize`. Hybrid mode removes that refusal for clients that don't want (or can't perform) the downgrade.
|
||||||
|
|
||||||
|
### What stays unavailable to `2026-07-28` clients
|
||||||
|
|
||||||
|
A `2026-07-28` request has no session even on a hybrid endpoint, so all of the [stateless mode restrictions](#what-stateless-mode-changes) still apply to it: no [unsolicited notifications](#how-streamable-http-delivers-messages), no resource subscriptions, no server-initiated ping, and no per-client isolation. Use [MRTR](xref:mrtr) for elicitation (and the deprecated sampling and roots) on that half of the endpoint. Legacy sessions on the same endpoint keep all of those features.
|
||||||
|
|
||||||
|
### Lifetimes
|
||||||
|
|
||||||
|
Because the effective mode is decided per request, DI and callback lifetimes follow the request rather than the endpoint:
|
||||||
|
|
||||||
|
- `2026-07-28` requests resolve services from `HttpContext.RequestServices` with request scoping disabled, exactly like [stateless HTTP](#stateless-http).
|
||||||
|
- `initialize`-handshake sessions resolve services from the application provider and scope each request, exactly like [stateful HTTP](#stateful-http).
|
||||||
|
- <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.ConfigureSessionOptions> runs once per session for `initialize`-handshake clients and once per HTTP request for `2026-07-28` clients.
|
||||||
|
|
||||||
|
Stateful-only options (`IdleTimeout`, `EventStreamStore`, `SessionMigrationHandler`, and so on) continue to govern the session half of the endpoint and are ignored for `2026-07-28` requests.
|
||||||
|
|
||||||
## Comparison
|
## Comparison
|
||||||
|
|
||||||
| Consideration | Stateless | Stateful |
|
| Consideration | Stateless | Stateful |
|
||||||
|
|
@ -183,6 +228,9 @@ The [deployment considerations](#deployment-considerations) section lists real c
|
||||||
| **State reset on reconnect** | No concept of reconnection — every request stands alone | Client reconnection starts a new session with a clean slate |
|
| **State reset on reconnect** | No concept of reconnection — every request stands alone | Client reconnection starts a new session with a clean slate |
|
||||||
| **[Tasks](xref:tasks)** | Supported — shared task store, no per-session isolation | Supported — task store scoped per session |
|
| **[Tasks](xref:tasks)** | Supported — shared task store, no per-session isolation | Supported — task store scoped per session |
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> [Hybrid mode](#hybrid-mode-sessions-for-initialize-clients-only) doesn't add a third column: each request follows the **Stateless** column when the client negotiated `2026-07-28` and the **Stateful** column when the client used the `initialize` handshake.
|
||||||
|
|
||||||
## Transports and sessions
|
## Transports and sessions
|
||||||
|
|
||||||
### Streamable HTTP
|
### Streamable HTTP
|
||||||
|
|
@ -390,7 +438,7 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Recommended for servers that don't need sessions.
|
// Recommended for servers that don't need sessions.
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
|
|
||||||
// --- Options below only apply to stateful (non-stateless) mode ---
|
// --- Options below only apply to stateful (non-stateless) mode ---
|
||||||
|
|
||||||
|
|
@ -417,7 +465,8 @@ builder.Services.AddMcpServer()
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
| Property | Type | Default | Description |
|
||||||
|----------|------|---------|-------------|
|
|----------|------|---------|-------------|
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> | `bool` | `true` | Enables stateless mode. No sessions, no `Mcp-Session-Id` header, no server-to-client requests on the legacy protocol. Required by the `2026-07-28` protocol revision. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.SessionMode> | <xref:ModelContextProtocol.AspNetCore.HttpServerSessionMode> | `Stateless` | Selects how the server tracks state between requests: `Stateless` (no sessions), `Stateful` (sessions for every client, `2026-07-28` refused), or `StatefulForInitializeClients` ([hybrid](#hybrid-mode-sessions-for-initialize-clients-only)). |
|
||||||
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> | `bool` | `true` | Convenience proxy over `SessionMode`: `true` maps to `Stateless`, `false` maps to `Stateful`, and hybrid mode reads as `false`. Use `SessionMode` to select hybrid mode. |
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.IdleTimeout> | `TimeSpan` | 2 hours | _Stateful only (`MCP9006`)._ Duration of inactivity before a session is closed. Checked every 5 seconds. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.IdleTimeout> | `TimeSpan` | 2 hours | _Stateful only (`MCP9006`)._ Duration of inactivity before a session is closed. Checked every 5 seconds. |
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.MaxIdleSessionCount> | `int` | 10,000 | _Stateful only (`MCP9006`)._ Maximum idle sessions before the oldest are forcibly terminated. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.MaxIdleSessionCount> | `int` | 10,000 | _Stateful only (`MCP9006`)._ Maximum idle sessions before the oldest are forcibly terminated. |
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.ConfigureSessionOptions> | `Func<HttpContext, McpServerOptions, CancellationToken, Task>?` | `null` | Per-session callback to customize `McpServerOptions` with access to `HttpContext`. In stateless mode (including all `2026-07-28` requests), this runs on every HTTP request. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.ConfigureSessionOptions> | `Func<HttpContext, McpServerOptions, CancellationToken, Task>?` | `null` | Per-session callback to customize `McpServerOptions` with access to `HttpContext`. In stateless mode (including all `2026-07-28` requests), this runs on every HTTP request. |
|
||||||
|
|
@ -426,7 +475,7 @@ builder.Services.AddMcpServer()
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EventStreamStore> | `ISseEventStreamStore?` | `null` | _Stateful only (`MCP9006`)._ Stores SSE events for session resumability via `Last-Event-ID`. Can also be registered in DI. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EventStreamStore> | `ISseEventStreamStore?` | `null` | _Stateful only (`MCP9006`)._ Stores SSE events for session resumability via `Last-Event-ID`. Can also be registered in DI. |
|
||||||
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.PerSessionExecutionContext> | `bool` | `false` | _Stateful only (`MCP9006`)._ Uses a single `ExecutionContext` for the entire session instead of per-request. Enables session-scoped `AsyncLocal<T>` values but prevents `IHttpContextAccessor` from working in handlers. |
|
| <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.PerSessionExecutionContext> | `bool` | `false` | _Stateful only (`MCP9006`)._ Uses a single `ExecutionContext` for the entire session instead of per-request. Enables session-scoped `AsyncLocal<T>` values but prevents `IHttpContextAccessor` from working in handlers. |
|
||||||
|
|
||||||
The properties marked _Stateful only_ above carry diagnostic [`MCP9006`](xref:list-of-diagnostics#obsolete-apis) because they have no effect when the request is served without a session (every `2026-07-28` request, plus every request on a server with `Stateless = true`). They remain available as back-compat knobs for the legacy stateful Streamable HTTP path.
|
The properties marked _Stateful only_ above carry diagnostic [`MCP9006`](xref:list-of-diagnostics#obsolete-apis) because they have no effect when the request is served without a session (every `2026-07-28` request, plus every request on a server with `SessionMode = HttpServerSessionMode.Stateless`). They remain available as back-compat knobs for the legacy stateful Streamable HTTP path.
|
||||||
|
|
||||||
### ConfigureSessionOptions
|
### ConfigureSessionOptions
|
||||||
|
|
||||||
|
|
@ -457,7 +506,7 @@ In **stateless mode**, `ConfigureSessionOptions` is called on **every HTTP reque
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
options.Stateless = true;
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
options.ConfigureSessionOptions = (httpContext, mcpServerOptions, cancellationToken) =>
|
options.ConfigureSessionOptions = (httpContext, mcpServerOptions, cancellationToken) =>
|
||||||
{
|
{
|
||||||
// This runs on every request in stateless mode, so you can use the
|
// This runs on every request in stateless mode, so you can use the
|
||||||
|
|
@ -746,7 +795,7 @@ In stateless mode, each HTTP request is its own "session", so `mcp.server.sessio
|
||||||
The legacy [SSE (Server-Sent Events)](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) transport is also supported by `MapMcp()` and always uses stateful mode. Legacy SSE endpoints (`/sse` and `/message`) are **disabled by default** due to [backpressure concerns](#request-backpressure). To enable them, set <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> to `true` — this property is marked `[Obsolete]` with a diagnostic warning (`MCP9004`) to signal that it should only be used when you need to support legacy SSE-only clients and understand the backpressure implications. Alternatively, set the `ModelContextProtocol.AspNetCore.EnableLegacySse` [AppContext switch](https://learn.microsoft.com/dotnet/api/system.appcontext) to `true`.
|
The legacy [SSE (Server-Sent Events)](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse) transport is also supported by `MapMcp()` and always uses stateful mode. Legacy SSE endpoints (`/sse` and `/message`) are **disabled by default** due to [backpressure concerns](#request-backpressure). To enable them, set <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> to `true` — this property is marked `[Obsolete]` with a diagnostic warning (`MCP9004`) to signal that it should only be used when you need to support legacy SSE-only clients and understand the backpressure implications. Alternatively, set the `ModelContextProtocol.AspNetCore.EnableLegacySse` [AppContext switch](https://learn.microsoft.com/dotnet/api/system.appcontext) to `true`.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Setting `EnableLegacySse = true` while `Stateless = true` throws an `InvalidOperationException` at startup, because SSE requires in-memory session state shared between the `GET` and `POST` requests.
|
> Setting `EnableLegacySse = true` while `SessionMode = HttpServerSessionMode.Stateless` throws an `InvalidOperationException` at startup, because SSE requires in-memory session state shared between the `GET` and `POST` requests.
|
||||||
|
|
||||||
### How SSE sessions work
|
### How SSE sessions work
|
||||||
|
|
||||||
|
|
@ -778,7 +827,7 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Session migration is a stateful-mode feature.
|
// Session migration is a stateful-mode feature.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
options.SessionMigrationHandler = new MySessionMigrationHandler();
|
options.SessionMigrationHandler = new MySessionMigrationHandler();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
@ -806,7 +855,7 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Session resumability is a stateful-mode feature.
|
// Session resumability is a stateful-mode feature.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
options.EventStreamStore = new MyEventStreamStore();
|
options.EventStreamStore = new MyEventStreamStore();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Register the tool type when building the server:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(o => o.Stateless = true)
|
.WithHttpTransport(o => o.SessionMode = HttpServerSessionMode.Stateless)
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,13 +168,16 @@ await using var client = await McpClient.ResumeSessionAsync(transport, new Resum
|
||||||
Use the `ModelContextProtocol.AspNetCore` package to host an MCP server over HTTP. The <xref:Microsoft.AspNetCore.Builder.McpEndpointRouteBuilderExtensions.MapMcp*> method maps the Streamable HTTP endpoint at the specified route (root by default).
|
Use the `ModelContextProtocol.AspNetCore` package to host an MCP server over HTTP. The <xref:Microsoft.AspNetCore.Builder.McpEndpointRouteBuilderExtensions.MapMcp*> method maps the Streamable HTTP endpoint at the specified route (root by default).
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Recommended for servers that don't need server-to-client requests.
|
// Stateless mode is the default and recommended for
|
||||||
options.Stateless = true;
|
// servers that don't need server-to-client requests.
|
||||||
|
options.SessionMode = HttpServerSessionMode.Stateless;
|
||||||
})
|
})
|
||||||
.WithTools<MyTools>();
|
.WithTools<MyTools>();
|
||||||
|
|
||||||
|
|
@ -183,7 +186,7 @@ app.MapMcp();
|
||||||
app.Run();
|
app.Run();
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the HTTP transport runs **statelessly** — the server does not assign an `Mcp-Session-Id` or track transport session state in memory. This simplifies deployment, enables horizontal scaling without session affinity, and matches the `2026-07-28` Streamable HTTP wire format. Set `Stateless = false` explicitly when your server needs stateful sessions for unsolicited notifications, resource subscriptions, or per-client isolation. For a detailed guide on when to use stateless vs. stateful mode, configure session options, and understand [cancellation and disposal](xref:stateless#cancellation-and-disposal) behavior during shutdown, see [Stateless and Stateful](xref:stateless).
|
By default, the HTTP transport runs **statelessly** — the server does not assign an `Mcp-Session-Id` or track transport session state in memory. This simplifies deployment, enables horizontal scaling without session affinity, and matches the `2026-07-28` Streamable HTTP wire format. Set `SessionMode = HttpServerSessionMode.Stateful` explicitly when your server needs stateful sessions for unsolicited notifications, resource subscriptions, or per-client isolation. For a detailed guide on when to use stateless vs. stateful mode, configure session options, and understand [cancellation and disposal](xref:stateless#cancellation-and-disposal) behavior during shutdown, see [Stateless and Stateful](xref:stateless).
|
||||||
|
|
||||||
#### Host name validation
|
#### Host name validation
|
||||||
|
|
||||||
|
|
@ -297,20 +300,22 @@ SSE-specific configuration options:
|
||||||
|
|
||||||
#### SSE server (ASP.NET Core)
|
#### SSE server (ASP.NET Core)
|
||||||
|
|
||||||
The ASP.NET Core integration supports SSE transport alongside Streamable HTTP. Legacy SSE endpoints (`/sse` and `/message`) are **disabled by default** and <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> is marked `[Obsolete]` (diagnostic `MCP9004`). SSE always requires stateful mode; legacy SSE endpoints are never mapped when `Stateless = true`.
|
The ASP.NET Core integration supports SSE transport alongside Streamable HTTP. Legacy SSE endpoints (`/sse` and `/message`) are **disabled by default** and <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> is marked `[Obsolete]` (diagnostic `MCP9004`). SSE always requires stateful mode; legacy SSE endpoints are never mapped when `SessionMode = HttpServerSessionMode.Stateless`.
|
||||||
|
|
||||||
**Why SSE is disabled by default.** The SSE transport separates request and response channels: clients `POST` JSON-RPC messages to `/message` and receive all responses through a long-lived `GET` SSE stream on `/sse`. Because the `POST` endpoint returns `202 Accepted` immediately — before the handler even runs — there is **no HTTP-level backpressure** on handler concurrency. A client (or attacker) can flood the server with tool calls without waiting for prior requests to complete. In contrast, Streamable HTTP holds each `POST` response open until the handler finishes, providing natural backpressure. For a detailed comparison and mitigations if you must use SSE, see [Request backpressure](xref:stateless#request-backpressure).
|
**Why SSE is disabled by default.** The SSE transport separates request and response channels: clients `POST` JSON-RPC messages to `/message` and receive all responses through a long-lived `GET` SSE stream on `/sse`. Because the `POST` endpoint returns `202 Accepted` immediately — before the handler even runs — there is **no HTTP-level backpressure** on handler concurrency. A client (or attacker) can flood the server with tool calls without waiting for prior requests to complete. In contrast, Streamable HTTP holds each `POST` response open until the handler finishes, providing natural backpressure. For a detailed comparison and mitigations if you must use SSE, see [Request backpressure](xref:stateless#request-backpressure).
|
||||||
|
|
||||||
To enable legacy SSE, set `EnableLegacySse` to `true`:
|
To enable legacy SSE, set `EnableLegacySse` to `true`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddMcpServer()
|
builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// SSE requires stateful mode; opt in explicitly because stateless mode is the default.
|
// SSE requires stateful mode; opt in explicitly because stateless mode is the default.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
|
|
||||||
#pragma warning disable MCP9004 // EnableLegacySse is obsolete
|
#pragma warning disable MCP9004 // EnableLegacySse is obsolete
|
||||||
// Enable legacy SSE endpoints for clients that don't support Streamable HTTP.
|
// Enable legacy SSE endpoints for clients that don't support Streamable HTTP.
|
||||||
|
|
|
||||||
|
|
@ -44,5 +44,5 @@ When APIs are marked as obsolete, a diagnostic is emitted to warn users that the
|
||||||
| `MCP9003` | In place | The `RequestContext<TParams>(McpServer, JsonRpcRequest)` constructor is obsolete. Use the overload that accepts a `parameters` argument: `RequestContext<TParams>(McpServer, JsonRpcRequest, TParams)`. |
|
| `MCP9003` | In place | The `RequestContext<TParams>(McpServer, JsonRpcRequest)` constructor is obsolete. Use the overload that accepts a `parameters` argument: `RequestContext<TParams>(McpServer, JsonRpcRequest, TParams)`. |
|
||||||
| `MCP9004` | In place | <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> opts into the legacy SSE transport which has no built-in HTTP-level backpressure. Use Streamable HTTP instead. See [Stateless and Stateful — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details. |
|
| `MCP9004` | In place | <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.EnableLegacySse> opts into the legacy SSE transport which has no built-in HTTP-level backpressure. Use Streamable HTTP instead. See [Stateless and Stateful — Legacy SSE transport](xref:stateless#legacy-sse-transport) for details. |
|
||||||
| `MCP9005` | In place | The Roots, Sampling, and Logging features are deprecated as of specification version 2026-07-28 and may be removed in a future version. See [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) for more information. |
|
| `MCP9005` | In place | The Roots, Sampling, and Logging features are deprecated as of specification version 2026-07-28 and may be removed in a future version. See [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) for more information. |
|
||||||
| `MCP9006` | In place | The stateful Streamable HTTP configuration knobs on <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions> — `EventStreamStore`, `SessionMigrationHandler`, `PerSessionExecutionContext`, `IdleTimeout`, and `MaxIdleSessionCount` — only apply when `Stateless = false`. Starting with the `2026-07-28` protocol revision, Streamable HTTP no longer supports sessions, and the SDK now defaults `Stateless` to `true`. These knobs remain available for back-compat with the legacy stateful Streamable HTTP transport but new code should target the stateless path. |
|
| `MCP9006` | In place | The stateful Streamable HTTP configuration knobs on <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions> — `EventStreamStore`, `SessionMigrationHandler`, `PerSessionExecutionContext`, `IdleTimeout`, and `MaxIdleSessionCount` — only apply when the request is served with a session. Starting with the `2026-07-28` protocol revision, Streamable HTTP no longer supports sessions, and the SDK now defaults `SessionMode` to `HttpServerSessionMode.Stateless`. These options remain available for back-compat with the legacy stateful Streamable HTTP transport but new code should target the stateless path. |
|
||||||
| `MCP9007` | In place | `AuthorizationRedirectDelegate` and `ClientOAuthOptions.AuthorizationRedirectDelegate` are retained for source and binary compatibility but cannot provide the authorization-response state or RFC 9207 issuer. State and issuer validation are skipped when these APIs are used. Use `ClientOAuthOptions.AuthorizationCallbackHandler` for response-bound, issuer-aware authorization flows. |
|
| `MCP9007` | In place | `AuthorizationRedirectDelegate` and `ClientOAuthOptions.AuthorizationRedirectDelegate` are retained for source and binary compatibility but cannot provide the authorization-response state or RFC 9207 issuer. State and issuer validation are skipped when these APIs are used. Use `ClientOAuthOptions.AuthorizationCallbackHandler` for response-bound, issuer-aware authorization flows. |
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using AspNetCoreMcpPerSessionTools.Tools;
|
using AspNetCoreMcpPerSessionTools.Tools;
|
||||||
|
using ModelContextProtocol.AspNetCore;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -14,8 +15,8 @@ builder.Services.AddMcpServer()
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// This sample demonstrates per-session tool filtering, which requires stateful mode.
|
// This sample demonstrates per-session tool filtering, which requires stateful mode.
|
||||||
// Set Stateless = false explicitly for forward compatibility in case the default changes.
|
// Set SessionMode = HttpServerSessionMode.Stateful since sessions are required.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
|
|
||||||
// Configure per-session options to filter tools based on route category
|
// Configure per-session options to filter tools based on route category
|
||||||
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
|
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,9 @@ The key technique is using `ConfigureSessionOptions` to modify the tool collecti
|
||||||
```csharp
|
```csharp
|
||||||
.WithHttpTransport(options =>
|
.WithHttpTransport(options =>
|
||||||
{
|
{
|
||||||
// Per-session tool filtering requires stateful mode. Set Stateless = false
|
// Set SessionMode = HttpServerSessionMode.Stateful since per-session
|
||||||
// explicitly for forward compatibility in case the default changes.
|
// tool filtering requires sessions.
|
||||||
options.Stateless = false;
|
options.SessionMode = HttpServerSessionMode.Stateful;
|
||||||
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
|
options.ConfigureSessionOptions = async (httpContext, mcpOptions, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var toolCategory = GetToolCategoryFromRoute(httpContext);
|
var toolCategory = GetToolCategoryFromRoute(httpContext);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue