You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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>
Copy file name to clipboardExpand all lines: docs/concepts/sessions/sessions.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,11 +25,13 @@ The MCP [Streamable HTTP transport] uses an `Mcp-Session-Id` HTTP header to asso
25
25
> **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.
26
26
27
27
> [!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.
29
29
>
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.
31
31
>
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.
0 commit comments