Episode 001 · AI tutorials
Upgrade Your MCP Server to Stateless 2026-07-28
A runnable migration from connection-scoped MCP sessions to per-request protocol context, explicit authorized handles, dual-era compatibility, and a five-test release scorecard.
Field note
To support MCP 2026-07-28, remove protocol-level sessions, Mcp-Session-Id, and the modern initialize dependency. Put protocol version and client capabilities on every request, implement server/discover, and represent necessary business continuity with opaque, authorized handles passed as ordinary tool arguments. Keep any legacy initialize path isolated, then require all five cross-connection and cross-user tests to pass.
What the test tells us
To support MCP 2026-07-28, remove protocol-level sessions, Mcp-Session-Id, and the modern initialize dependency. Put protocol version and client capabilities on every request, implement server/discover, and represent necessary business continuity with opaque, authorized handles passed as ordinary tool arguments. Keep any legacy initialize path isolated, then require all five cross-connection and cross-user tests to pass.
Transcript
If your MCP server still creates a session, returns Mcp-Session-Id, and waits for notifications/initialized, it is implementing the old shape. In the 2026-07-28 specification, the clean upgrade is not to recreate that session somewhere else. It is to make every request understandable on its own.
Here is the finished result we are building. A tool call arrives with the protocol version and client capabilities in _meta. The HTTP request also carries MCP-Protocol-Version. The server validates that version, executes the call, and returns a result without consulting connection-local state. If a workflow genuinely needs continuity, the server returns an explicit handle, and the client passes that handle in the next tool call.
The official changelog makes two breaking changes especially important. First, protocol-level sessions and the Mcp-Session-Id header are removed from Streamable HTTP. Even discovery lists—tools/list, resources/list, and prompts/list—must not change merely because a different connection asked. Second, the initialize and notifications/initialized handshake is removed for this modern revision. Version, capabilities, and identity move to per-request metadata. That is the protocol claim. My implementation advice is: keep business state only when the product needs it, and make its ownership and lifetime explicit.
Let us migrate a small report server. The old handler reads Mcp-Session-Id, looks up a session object, and stores a draft report under that connection. Delete the header parser, session middleware, session table, and initialized flag. Add one shared request validator. It reads _meta.io.modelcontextprotocol/protocolVersion and _meta.io.modelcontextprotocol/clientCapabilities. On HTTP, compare the metadata version with MCP-Protocol-Version. If the requested version is unsupported, return UnsupportedProtocolVersionError with the supported versions rather than silently guessing.
Now change the stateful tool. create_report returns a server-minted reportHandle such as rpt_7K2—not a fake session ID. update_report requires reportHandle as an ordinary argument. Authorize that handle for the caller, set an expiry, and store only the report state the workflow actually needs. This makes retries, logs, load balancing, and security review easier because continuity is visible in the schema. Do not put secrets or raw database keys in the handle; use an opaque, unguessable value.
Add server/discover. The 2026-07-28 changelog says servers must implement this RPC to advertise supported versions, capabilities, and identity. A modern client may call it before other requests. For a staged migration, a dual-era server can recognize modern per-request _meta while keeping a tightly isolated legacy initialize path for older clients. Do not let legacy session state leak into modern list responses.
Our test is simple. Request one: tools/list with valid modern metadata succeeds without initialize and without a session header. Request two: the same list through a new HTTP connection returns the same catalog. Request three: create_report returns a handle; update_report without it fails, while update_report with it succeeds across another connection. Request four: an unsupported version returns the specified version error. Request five: notifications/initialized is not required by the modern path. Finally, run two users in parallel and prove one handle cannot access the other user’s report.
The pass score is five out of five: no Mcp-Session-Id, no modern initialize dependency, per-request version and capabilities, explicit authorized handles, and cross-connection tests. “Stateless” is not “store nothing.” It is “do not hide protocol state in the connection.” Keep the official changelog, transport, versioning, and SEP links beside your migration pull request, and pin your tests to the exact 2026-07-28 contract. If you need help turning this into a production-safe rollout, Optijara can help design the compatibility boundary and evidence-driven test plan.