MCP stateless HTTP silently disables sampling and elicitation — and stateful mode cannot scale
From Theory Delta | Methodology | Published 2026-02-24
What the docs say
MCP Streamable HTTP (spec 2025-03-26, replacing deprecated SSE) supports two modes: stateless for serverless deployment (Lambda, Workers) and stateful for full-featured servers. The spec implies this is a deployment choice with a clear tradeoff.
What actually happens
The tradeoff is not a dial — it is a hard binary with three production failure modes that are not obvious from the spec:
Failure 1: Sampling silently hangs in stateless mode. FastMCP with stateless_http=True permanently hangs (no error, no timeout) when the server attempts sampling. Maintainers closed the original report (FastMCP #678) as “expected behavior.” Bidirectional communication is architecturally impossible when each request creates and destroys a fresh server instance. FastMCP #1156 later added explicit detection and error messages, but the default experience before that was an infinite hang with no diagnostic output.
Failure 2: Load balancers strip Mcp-Session-Id. Nginx, ALB, and most proxies require explicit configuration to route on this header. MCP clients use fetch() and do not forward cookies, making cookie-based sticky sessions non-functional. Without header-based routing, stateful requests from the same client hit different backend instances and silently lose session context. (python-sdk #880 — labeled P1, documents the horizontal scaling gap)
Failure 3: CORS silences the session ID. The MCP Inspector itself had this bug through v0.18.0 — it stored the session ID but did not attach it to follow-up requests, producing false-negative debugging results. Developers debugging stateful servers with the Inspector see the server appear broken when the failure is in the debugging tool. (Inspector #905)
Additionally: DNS rebinding protection is off by default in both SDKs. Any MCP server running on localhost is vulnerable to malicious webpages without explicit origin validation.
- TypeScript SDK: CVE-2025-66414 / GHSA-w48q-cv73-mx4w — fixed in SDK v1.24.0
- Python SDK: CVE-2025-66416 / GHSA-9h52-p55h-vw2f — fixed in v1.23.0
What to do instead
- If your tools are pure functions (no sampling, no cross-request state, no elicitation): stateless on Cloudflare Workers + Hono is the lowest-friction path. Accept the feature loss explicitly.
- If your server needs to ask the client for information mid-task: you cannot use stateless. Deploy stateful with sticky sessions via load balancer header routing on
Mcp-Session-Id. - Do not test stateful servers with MCP Inspector alone — the Inspector’s session ID bug (#905, unfixed through v0.18.0) produces false negatives. Test with a real client first.
- Enable DNS rebinding protection explicitly:
enableDnsRebindingProtection: true(TypeScript SDK) orTransportSecuritySettings(Python SDK). Bind to127.0.0.1, not0.0.0.0. ValidateOriginandHostheaders. - Expect to refactor. MCP maintainers are redesigning the protocol with per-request capability metadata, targeting June 2026. The current pattern is explicitly a workaround. ^[unlinked — source is MCP maintainer comments in SDK issues, no single canonical post-mortem URL]
Environments tested
| Tool | Version | Result |
|---|---|---|
| MCP TypeScript SDK | v1.x / v2 pre-release | independently-confirmed: DNS rebinding off by default (CVE-2025-66414) |
| MCP Python SDK | v1.x | independently-confirmed: session persistence in-memory only (#880) |
| FastMCP | latest (Feb 2026) | independently-confirmed: sampling hangs in stateless mode (#678) |
| Google ADK | latest (Feb 2026) | docs-reviewed: Streamable HTTP transport reviewed |
| Cloudflare Workers (Hono) | latest (Feb 2026) | source-reviewed: stateless deployment path validated |
| AWS Lambda | latest (Feb 2026) | docs-reviewed: stateless deployment path reviewed |
| MCP Inspector | through v0.18.0 | independently-confirmed: session ID not attached to follow-up requests (#905) |
Confidence and gaps
Confidence: source-reviewed + independently-confirmed — 7 environments reviewed. No commands were executed against running instances. Failure modes independently confirmed by CVEs (CVE-2025-66414, CVE-2025-66416), GHSAs, and GitHub issues linked above. Note: scope_matches=false because the core claim is about “MCP stateless mode” at the protocol level, but primary evidence is from FastMCP (one library) plus independent issue reports. Multiple implementations were reviewed but not executed.
Falsification criterion: This claim would be disproved by demonstrating a stateless MCP HTTP deployment where sampling or elicitation works correctly without persistent session state — i.e., a serverless MCP server (Lambda/Workers) that successfully completes a sampling round-trip.
Unlinked claim: The June 2026 spec redesign timeline is sourced from maintainer comments across multiple SDK issues, not a single canonical URL. If you have a direct link to the post-mortem or redesign announcement, contribute it.
Open questions: Has anyone implemented external session persistence (Redis/DynamoDB) for the MCP Python SDK? Does the proposed June 2026 spec redesign eliminate the stateless/stateful binary? Are there MCP clients that correctly handle Mcp-Session-Id through standard proxies without custom configuration?
Seen different? Contribute your evidence — theory delta is what makes this knowledge base work.
Environments Tested
| Tool | Version | Result |
|---|---|---|
| MCP TypeScript SDK | v1.x / v2 pre-release | DNS rebinding off by default (CVE-2025-66414) |
| MCP Python SDK | v1.x | Session persistence in-memory only (issue #880) |
| jlowin/fastmcp | latest (Feb 2026) | Sampling hangs in stateless mode (issue #678) |
| Google ADK | latest (Feb 2026) | Streamable HTTP transport reviewed |
| Cloudflare Workers (Hono) | latest (Feb 2026) | Stateless deployment path validated |
| AWS Lambda | latest (Feb 2026) | Stateless deployment path reviewed |
| MCP Inspector | through v0.18.0 | Session ID not attached to follow-up requests (issue #905) |