Skip to content

Commit 8eb4023

Browse files
authored
Add stateless doc, prefer stateless mode and disable legacy SSE by default (#1468)
1 parent 49bf170 commit 8eb4023

File tree

46 files changed

+1440
-73
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1440
-73
lines changed

docs/concepts/cancellation/cancellation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ MCP supports [cancellation] of in-flight requests. Either side can cancel a prev
1212
[cancellation]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
1313
[task cancellation]: https://learn.microsoft.com/dotnet/standard/parallel-programming/task-cancellation
1414

15+
> [!NOTE]
16+
> The source and lifetime of the `CancellationToken` provided to server handlers depends on the transport and session mode. In [stateless mode](xref:stateless#stateless-mode-recommended), the token is tied to the HTTP request — if the client disconnects, the handler is cancelled. In [stateful mode](xref:stateless#stateful-mode-sessions), the token is tied to the session lifetime. See [Cancellation and disposal](xref:stateless#cancellation-and-disposal) for details.
17+
1518
### How cancellation maps to MCP notifications
1619

1720
When a `CancellationToken` passed to a client method (such as <xref:ModelContextProtocol.Client.McpClient.CallToolAsync*>) is cancelled, a `notifications/cancelled` notification is sent to the server with the request ID. On the server side, the `CancellationToken` provided to the tool method is then triggered, allowing the handler to stop work gracefully. This same mechanism works in reverse for server-to-client requests.

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()
29+
.WithHttpTransport(o => o.Stateless = true)
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
@@ -172,7 +172,7 @@ Here's an example implementation of how a console application might handle elici
172172

173173
### URL Elicitation Required Error
174174

175-
When a tool cannot proceed without first completing a URL-mode elicitation (for example, when third-party OAuth authorization is needed), and calling `ElicitAsync` is not practical (for example in <xref: ModelContextProtocol.AspNetCore.HttpServerTransportOptions.Stateless> is enabled disabling server-to-client requests), the server may throw a <xref:ModelContextProtocol.UrlElicitationRequiredException>. This is a specialized error (JSON-RPC error code `-32042`) that signals to the client that one or more URL-mode elicitations must be completed before the original request can be retried.
175+
When a tool cannot proceed without first completing a URL-mode elicitation (for example, when third-party OAuth authorization is needed), and calling `ElicitAsync` is not practical (for example in [stateless](xref:stateless) mode where server-to-client requests are disabled), the server may throw a <xref:ModelContextProtocol.UrlElicitationRequiredException>. This is a specialized error (JSON-RPC error code `-32042`) that signals to the client that one or more URL-mode elicitations must be completed before the original request can be retried.
176176

177177
#### Throwing UrlElicitationRequiredException on the Server
178178

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66

77
builder.Services.AddMcpServer()
88
.WithHttpTransport(options =>
9-
options.IdleTimeout = Timeout.InfiniteTimeSpan // Never timeout
10-
)
9+
{
10+
// 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;
13+
})
1114
.WithTools<InteractiveTools>();
1215

1316
builder.Logging.AddConsole(options =>

docs/concepts/filters.md

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

402402
```csharp
403403
services.AddMcpServer()
404-
.WithHttpTransport()
404+
.WithHttpTransport(o => o.Stateless = true)
405405
.AddAuthorizationFilters() // Enable authorization filter support
406406
.WithTools<WeatherTools>();
407407
```
@@ -501,7 +501,7 @@ This allows you to implement logging, metrics, or other cross-cutting concerns t
501501

502502
```csharp
503503
services.AddMcpServer()
504-
.WithHttpTransport()
504+
.WithHttpTransport(o => o.Stateless = true)
505505
.WithRequestFilters(requestFilters =>
506506
{
507507
requestFilters.AddListToolsFilter(next => async (context, cancellationToken) =>
@@ -544,7 +544,10 @@ builder.Services.AddAuthentication("Bearer")
544544
builder.Services.AddAuthorization();
545545

546546
builder.Services.AddMcpServer()
547-
.WithHttpTransport()
547+
.WithHttpTransport(options =>
548+
{
549+
options.Stateless = true;
550+
})
548551
.AddAuthorizationFilters() // Required for authorization support
549552
.WithTools<WeatherTools>()
550553
.WithRequestFilters(requestFilters =>

docs/concepts/getting-started.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,13 @@ using System.ComponentModel;
7979

8080
var builder = WebApplication.CreateBuilder(args);
8181
builder.Services.AddMcpServer()
82-
.WithHttpTransport()
82+
.WithHttpTransport(options =>
83+
{
84+
// Stateless mode is recommended for servers that don't need
85+
// server-to-client requests like sampling or elicitation.
86+
// See the Sessions documentation for details.
87+
options.Stateless = true;
88+
})
8389
.WithToolsFromAssembly();
8490
var app = builder.Build();
8591

docs/concepts/httpcontext/httpcontext.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,16 @@ The following code snippet shows the `ContextTools` class accepting an [IHttpCon
2929
and the `GetHttpHeaders` method accessing the current [HttpContext] to retrieve the HTTP headers from the current request.
3030

3131
[!code-csharp[](samples/Tools/ContextTools.cs?name=snippet_AccessHttpContext)]
32+
33+
### SSE transport and stale HttpContext
34+
35+
When using the legacy SSE transport, be aware that the `HttpContext` returned by `IHttpContextAccessor` references the long-lived SSE connection request — not the individual `POST` request that triggered the tool call. This means:
36+
37+
- The `HttpContext.User` may contain stale claims if the client's token was refreshed after the SSE connection was established.
38+
- Request headers, query strings, and other per-request metadata will reflect the initial SSE connection, not the current operation.
39+
40+
The Streamable HTTP transport does not have this issue because each tool call is its own HTTP request, so `IHttpContextAccessor.HttpContext` always reflects the current request. In [stateless](xref:stateless) mode, this is guaranteed since every request creates a fresh server context.
41+
42+
<!-- mlc-disable-next-line -->
43+
> [!NOTE]
44+
> The server validates that the user identity has not changed between the session-initiating request and subsequent requests (using the `sub`, `NameIdentifier`, or `UPN` claim). If the user identity changes, the request is rejected with `403 Forbidden`. However, other claims (roles, permissions, custom claims) are not re-validated and may become stale over the lifetime of a session.

docs/concepts/httpcontext/samples/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
// Add services to the container.
66

77
builder.Services.AddMcpServer()
8-
.WithHttpTransport()
8+
.WithHttpTransport(options =>
9+
{
10+
options.Stateless = true;
11+
})
912
.WithTools<ContextTools>();
1013

1114
// <snippet_AddHttpContextAccessor>

docs/concepts/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ Install the SDK and build your first MCP client and server.
3636
| [Prompts](prompts/prompts.md) | Learn how to implement and consume reusable prompt templates with rich content types. |
3737
| [Completions](completions/completions.md) | Learn how to implement argument auto-completion for prompts and resource templates. |
3838
| [Logging](logging/logging.md) | Learn how to implement logging in MCP servers and how clients can consume log messages. |
39+
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
3940
| [HTTP Context](httpcontext/httpcontext.md) | Learn how to access the underlying `HttpContext` for a request. |
4041
| [MCP Server Handler Filters](filters.md) | Learn how to add filters to the handler pipeline. Filters let you wrap the original handler with additional functionality. |

docs/concepts/logging/logging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ MCP servers that implement the Logging utility must declare this in the capabili
4646
[Initialization]: https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization
4747

4848
Servers built with the C# SDK always declare the logging capability. Doing so does not obligate the server
49-
to send log messages&mdash;only allows it. Note that stateless MCP servers might not be capable of sending log
49+
to send log messages&mdash;only allows it. Note that [stateless](xref:stateless) MCP servers might not be capable of sending log
5050
messages as there might not be an open connection to the client on which the log messages could be sent.
5151

5252
The C# SDK provides an extension method <xref:Microsoft.Extensions.DependencyInjection.McpServerBuilderExtensions.WithSetLoggingLevelHandler*> on <xref:Microsoft.Extensions.DependencyInjection.IMcpServerBuilder> to allow the

0 commit comments

Comments
 (0)