MCP Stateless Spec 2026: Migrate Your Self-Hosted Servers
TL;DR: The 2026-07-28 MCP specification removes protocol-level sessions, the Mcp-Session-Id header, and the initialize handshake — every request is now self-contained. Self-hosted MCP servers that stash per-session state must refactor; stateless servers barely notice. You have a 12-month deprecation window, so migrate deliberately, not in a panic.
| Migrate your server now | Pin old versions | Put a gateway in front | |
|---|---|---|---|
| Best for | Servers you maintain and expose remotely | Fragile stacks you can’t touch this quarter | Fleets of third-party servers you don’t control |
| Cost | A refactor day per server | Zero today, growing debt | One more moving part to run |
| The catch | SDK churn is still settling | Clients drop old-spec support over the next year | The gateway becomes your new single point of failure |
Honest take: If you wrote your MCP server with FastMCP-style decorators and never touched session state, you’re mostly done already — update the SDK and move on. The people with real work ahead are the ones who used
Mcp-Session-Idas a free database key.
The Model Context Protocol got its largest revision since launch on July 28, 2026. The headline: MCP is now a stateless request-response protocol at its core. For self-hosters running MCP servers behind n8n, Flowise, or Open WebUI, this is the change worth actually reading, because commercial IDE vendors patch their clients automatically — your home-built weather-lookup server does not patch itself.
What the 2026-07-28 spec actually removes
Straight from the official changelog (modelcontextprotocol.io, 2026-07-28 revision):
| Removed | What replaces it |
|---|---|
Protocol-level sessions + Mcp-Session-Id header (SEP-2567) | Explicit, server-minted handles passed as ordinary tool arguments |
initialize / notifications/initialized handshake (SEP-2575) | Self-describing requests: protocol version and capabilities ride in _meta on every call |
ping, logging/setLevel, notifications/roots/list_changed | _meta fields (e.g. io.modelcontextprotocol/logLevel) |
HTTP GET endpoint, resources/subscribe / unsubscribe | Subscription state via io.modelcontextprotocol/subscriptionId in _meta |
SSE resumability + Last-Event-ID redelivery | Gone — design for idempotent calls instead |
Two headers are now required on every Streamable HTTP POST: Mcp-Method and Mcp-Name. That sounds like bureaucracy until you realize what it buys: a plain HTTP load balancer can route MCP traffic without deep packet inspection. Before this revision, remote MCP deployments needed sticky sessions or a shared session store because the session ID lived inside the JSON-RPC body. Now any server instance can answer any request.
List endpoints (tools/list, resources/list, prompts/list) also stopped varying per-connection. Results carry a CacheableResult shape with ttlMs and a cacheScope of "public" or "private", so clients cache your tool catalog instead of re-fetching it every session — which no longer exists anyway.
The spec also adopted a formal deprecation lifecycle: Active → Deprecated → Removed, with a minimum twelve-month window. Roots, Sampling, Logging, and the old HTTP+SSE transport are all marked Deprecated as of this revision. They still work today. They will not work forever.
The wire format, before and after
Old flow — three round trips before the first useful call:
POST /mcp
{"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {...}}}
// → server responds with Mcp-Session-Id: abc123
// → client sends notifications/initialized
// → every later request must carry Mcp-Session-Id: abc123
New flow — one self-contained request:
POST /mcp
Mcp-Method: tools/call
Mcp-Name: get_weather
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {"city": "Berlin"},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {"name": "my-client", "version": "1.0"},
"io.modelcontextprotocol/clientCapabilities": {}
}
}}
No handshake, no session header, no server-side memory of who you are. If your server needs continuity across calls — a database cursor, a shopping cart, a long-running scan — it mints a handle, returns it in the tool result, and the client passes that handle back as a normal argument on the next call. State becomes explicit and inspectable instead of ambient.
Migration checklist for your own MCP servers
Worked through in this order, this is an afternoon for a typical self-hosted server:
- Grep for the session ID.
grep -ri "session" server/— every read ofMcp-Session-Idor a session dict keyed on it is a refactor site. - Move context into the request. Anything you looked up per-session (user config, negotiated version, feature flags) now comes from
_metaon each request, or from your own auth token. - Mint handles for genuine cross-call state. Return
{"handle": "scan-7f3a"}from the tool that starts work; accepthandleas an argument on the tool that continues it. Expire handles server-side. - Stop requiring
initialize. A compliant 2026-07-28 client will never send it. Your server should answer a baretools/callcold. - Emit
ttlMsandcacheScopeon list results. Even a conservative"ttlMs": 60000, "cacheScope": "private"cuts redundanttools/listtraffic. - Retest with a stateless client. The official MCP Inspector against the new revision is the fastest way to catch a hidden session dependency — it will land on your server with zero prior context, exactly like a load-balanced request would.
One thing you should not do: bolt a compatibility shim onto a stateful server and call it migrated. If the process dies between two calls and your tool breaks, you still have ambient state — you’ve just hidden it from the protocol.
What it means for n8n
n8n ships two built-in MCP nodes: the MCP Server Trigger (n8n acts as a server, exposing workflows as tools) and the MCP Client Tool (n8n’s AI Agent calls external MCP servers). Two relevant facts as of September 2026:
- n8n already deprecated the SSE transport on these nodes in favor of Streamable HTTP, which is the transport the stateless revision builds on. If your MCP Client Tool credentials still point at an
/sseendpoint, that’s your first fix — the old HTTP+SSE transport is now formally deprecated at the spec level too. - n8n workflows triggered by the MCP Server Trigger are naturally stateless: each tool call starts a fresh workflow execution. That design ages well under the new spec. Where n8n users get burned is inside the workflow — static data or workflow-level variables used to accumulate context across tool calls. That pattern depended on session affinity that the protocol no longer guarantees. Move that context into the tool arguments (the handle pattern above) or into your own database node.
Check your n8n version’s release notes before assuming full 2026-07-28 client support; during the twelve-month window, servers that still accept initialize remain reachable from older n8n builds, so nothing breaks overnight. Our n8n + Ollama stack guide covers the underlying setup this all rides on.
What it means for Flowise
Flowise exposes MCP servers as tool nodes inside chatflows. A typical Flowise flow already treats each tool invocation as self-contained — the chatflow itself carries conversation memory, not the MCP session — so the stateless core changes little in day-to-day use. The audit point is any custom MCP tool config where you (or a community node) hard-coded a session handshake or an SSE endpoint. Same fix as n8n: Streamable HTTP endpoint, no session assumptions, context in arguments. If you’re choosing between orchestrators right now, our Flowise vs n8n vs LangGraph comparison still holds; this spec change doesn’t move the rankings.
What it means for Open WebUI
Open WebUI gained native MCP support in v0.6.31, and it’s Streamable HTTP only — no stdio, no legacy SSE (the older mcpo proxy remains the workaround for stdio-only servers). That “Streamable HTTP only” decision, mildly annoying in 2025, now looks prescient: Open WebUI connects to external MCP servers, so the stateless migration burden falls on the servers you point it at, not on Open WebUI itself. Practical upshot for a self-hosted Ollama + Open WebUI stack: update Open WebUI on your normal cadence, then work through the server checklist above for each MCP endpoint you’ve registered under your tools settings.
When NOT to migrate yet
- Your stack is air-gapped and version-pinned. If clients and servers upgrade together and nothing external connects, the old spec keeps working indefinitely on your LAN. Migrate when you next touch the code.
- You depend on SSE resumability. Message redelivery is simply gone. If a tool genuinely needs guaranteed delivery of long-stream progress events, you need to redesign around the Tasks extension or polling — don’t half-migrate.
- Your SDK hasn’t cut a stable release for the new revision. The Tier 1 SDKs were updated alongside the spec, but if a community SDK for your language is lagging, waiting a few weeks beats hand-rolling
_metahandling.
The deadline pressure is real but not urgent: twelve months minimum before deprecated features are removed. Spend one of those months migrating properly.
FAQ
Does the stateless spec affect stdio MCP servers?
Far less. Stdio servers are spawned per-client and die with the connection, so they never had the sticky-session scaling problem. The handshake removal and _meta fields still apply, so update your SDK, but there’s no load-balancer story to worry about.
Will my old MCP server stop working with Claude Code or other clients tomorrow? No. The spec’s deprecation policy guarantees a minimum twelve-month window between Deprecated and Removed, and major clients negotiate versions during the transition. But new client features (Tasks, cacheable lists) will only light up against migrated servers.
How do I keep multi-step tool state without sessions? Mint a handle: the first tool call creates server-side state under an ID you generate, returns that ID in the result, and subsequent calls take it as an argument. It’s the same pattern as a database cursor or an upload ID in any REST API — explicit, loggable, and safe behind a round-robin load balancer.
Sources
- Model Context Protocol — Key Changes, 2026-07-28 revision
- MCP Blog — The 2026-07-28 Specification
- InfoQ — MCP Goes Stateless, and Developers Ask Whether That Just Makes It an API Again
- Open WebUI docs — Model Context Protocol
- n8n docs — MCP Server Trigger node
Was this article helpful?
Thanks for the feedback — it helps improve future articles.
Need hands-on help?
I offer 1-on-1 technical consulting for local AI setup, GPU selection, and AI coding tool configuration — same topics covered on this site.
Book a session — $49 / hour →