Skip to content

Commit c8159bb

Browse files
halter73Copilot
andcommitted
Refine backpressure warning with technical nuance
Explain that handler CTS is session-scoped (not request-scoped) in stateful mode, making this a standard persistent-connection concern rather than an MCP-specific safety issue. Clarify that stateless mode avoids this because DisposeAsync awaits handlers within the HTTP request lifetime. Recommend standard HTTP protections alongside process isolation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4763a04 commit c8159bb

File tree

1 file changed

+5
-3
lines changed

1 file changed

+5
-3
lines changed

docs/concepts/sessions/sessions.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ The MCP [Streamable HTTP transport] uses an `Mcp-Session-Id` HTTP header to asso
2525
> **Why isn't stateless the default?** Stateful mode remains the default for backward compatibility and because it is the only HTTP mode with full feature parity with [stdio](xref:transports) (server-to-client requests, unsolicited notifications, subscriptions). Stateless is the recommended choice when you don't need those features. If your server _does_ depend on stateful behavior, consider setting `Stateless = false` explicitly so your code is resilient to a potential future default change once [MRTR](https://github.com/modelcontextprotocol/csharp-sdk/pull/1458) or similar mechanisms bring server-to-client interactions to stateless mode.
2626
2727
> [!WARNING]
28-
> **Stateful sessions are not safe for public internet deployments without additional hardening.** The SDK does not limit how long a handler can run or how many requests can be processed concurrently within a session. A misbehaving or compromised client can flood a stateful session with requests, and each request will spawn a handler that runs to completion. This can lead to thread starvation, GC pressure, or out-of-memory conditions that affect the entire server process — not just the offending session.
28+
> **Stateful sessions require additional protections for public internet deployments.** In stateful mode, handler cancellation tokens are linked to the **session** lifetime, not the individual HTTP request. This means a client can disconnect from a POST request while the handler continues running — and there is no built-in limit on how many concurrent handlers a session can have. A misbehaving client can flood a session with requests, and each one spawns a handler that runs until it completes or the session is terminated. This can lead to thread starvation, memory pressure, or resource exhaustion that affects the entire server process.
2929
>
30-
> Stateless mode is significantly more resilient here because each tool call is a standard HTTP request-response, so Kestrel and IIS connection limits, request timeouts, and rate-limiting middleware all apply naturally.
30+
> This is more exposed than comparable ASP.NET Core protocols. SignalR limits concurrent hub invocations per client (`MaximumParallelInvocationsPerClient`, default: **1**). gRPC unary calls are bounded by HTTP/2 `MaxStreamsPerConnection` (default: **100**). MCP dispatches each incoming JSON-RPC message as a fire-and-forget background task with no concurrency limit, so work accumulates without any built-in backpressure.
3131
>
32-
> If you must deploy a stateful server to the public internet, consider **process-level isolation** (e.g., one process or container per user/session) so that a single abusive session cannot starve the entire service. The <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.IdleTimeout> and <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.MaxIdleSessionCount> settings help protect against non-malicious overuse (e.g., a buggy client creating too many sessions), but they are not a substitute for HTTP-level protections.
32+
> **Stateless mode avoids this entirely.** In stateless mode, each handler's lifetime is the HTTP request's lifetime — `McpServer.DisposeAsync()` awaits all handlers before the POST response completes. This means Kestrel's connection limits, HTTP/2 `MaxStreamsPerConnection` (default: 100), request timeouts, and rate-limiting middleware all apply naturally.
33+
>
34+
> If you must deploy a stateful server to the public internet, apply **HTTP rate-limiting middleware**, **reverse proxy limits**, and consider **process-level isolation** (one process or container per user/session) so a single abusive session cannot starve the service. <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.IdleTimeout> and <xref:ModelContextProtocol.AspNetCore.HttpServerTransportOptions.MaxIdleSessionCount> help with non-malicious overuse (buggy clients creating too many sessions) but are not a substitute for request-level protections.
3335
3436
## Stateless mode (recommended)
3537

0 commit comments

Comments
 (0)