MCP Stateless Spec 2026: Migrate Your Self-Hosted Servers

mcpselfhostedn8nopen-webuiprotocol

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 nowPin old versionsPut a gateway in front
Best forServers you maintain and expose remotelyFragile stacks you can’t touch this quarterFleets of third-party servers you don’t control
CostA refactor day per serverZero today, growing debtOne more moving part to run
The catchSDK churn is still settlingClients drop old-spec support over the next yearThe 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-Id as 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):

RemovedWhat 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 / unsubscribeSubscription state via io.modelcontextprotocol/subscriptionId in _meta
SSE resumability + Last-Event-ID redeliveryGone — 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:

  1. Grep for the session ID. grep -ri "session" server/ — every read of Mcp-Session-Id or a session dict keyed on it is a refactor site.
  2. Move context into the request. Anything you looked up per-session (user config, negotiated version, feature flags) now comes from _meta on each request, or from your own auth token.
  3. Mint handles for genuine cross-call state. Return {"handle": "scan-7f3a"} from the tool that starts work; accept handle as an argument on the tool that continues it. Expire handles server-side.
  4. Stop requiring initialize. A compliant 2026-07-28 client will never send it. Your server should answer a bare tools/call cold.
  5. Emit ttlMs and cacheScope on list results. Even a conservative "ttlMs": 60000, "cacheScope": "private" cuts redundant tools/list traffic.
  6. 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 /sse endpoint, 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 _meta handling.

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

Was this article helpful?