How to Migrate Your MCP Server to the 2026 Stateless Spec
In short
The final MCP spec ships July 28, 2026, and the release candidate that locked on May 21, 2026 is the largest revision since the protocol launched. The headline change: the initialize handshake and the Mcp-Session-Id header are gone, which means remote servers no longer need sticky sessions. I migrated my own production Node/TypeScript MCP server in about six hours, and this post is the full diff: what changed, whether you have to act at all, the exact before/after code, and the checklist I ran on cutover day.

On this page
- What changed in the 2026-07-28 MCP release candidate?
- Do you actually have to migrate your MCP server?
- How do I migrate a Node.js MCP server step by step?
- How do I move long-running work to the new Tasks model?
- How do I test my server against the release candidate?
- What should be on my migration-day checklist?
- Key takeaways
What changed in the 2026-07-28 MCP release candidate?
The RC makes six changes that matter for server authors: a stateless core that removes the initialize handshake and the Mcp-Session-Id header, a formal Extensions framework, redesigned Tasks for long-running work, MCP Apps for interactive UI, mandatory RFC 9207 iss validation in OAuth flows, and a 12-month deprecation policy. The stateless core is the one that breaks remote servers.
This is not a cosmetic version bump. 15,900+ public mcp-server repos on GitHub are affected by this revision, and the remote HTTP subset of those has real work to do. Here is the one-sentence version of the core change: a stateless MCP server is one any load-balanced instance can serve, because the 2026 spec moved session state out of the protocol.
The six revisions, mapped to what you will actually see in production if you ignore them:
| RC change | Symptom if you ignore it | Fix |
Stateless core: initialize handshake removed | RC clients send tools/call immediately; old servers reply "server not initialized" | Drop the handshake gate; declare capabilities statically, handle any method on any request |
Mcp-Session-Id header removed | Requests land on a different instance behind your load balancer and fail with "session not found" | Delete the session map; make every handler stateless |
| Tasks redesigned | Long tool calls hit client timeouts; legacy progress notifications are ignored by RC clients | Return a task handle and let clients poll tasks/get |
Extensions framework replaces ad hoc experimental capabilities | Custom capability keys are silently dropped by RC clients | Re-declare them under the namespaced extensions field |
RFC 9207 iss validation required in OAuth | Strict RC clients abort the auth flow; you stay exposed to mix-up attacks | Validate the iss parameter on every authorization response |
| 12-month deprecation policy | Nothing today; old protocol versions stop being accepted around mid-2027 | Plan the migration now instead of pinning old SDKs |
| Your server | Breakage class | When to migrate |
| stdio-only, local (Claude Desktop, IDE plugins) | Soft: no HTTP headers involved, SDK shims cover the lifecycle change | At your next SDK major bump |
| Remote HTTP, single instance | Hard at the protocol edge, tolerable during the 12-month window | June or July 2026 |
| Remote HTTP behind a load balancer | Hard: sticky sessions were the only thing keeping you correct | Now |
| Any server with tools running longer than ~30 seconds | Hard: the old progress pattern is deprecated | Together with the transport work |
Most of the servers I build in my AI agents and automation ↗ work sit in the second or third row, and so does mine: the MCP server behind my reputation SaaS handles roughly 900 tool calls a day (fetch reviews, draft AI replies, post replies) across two instances, and before this migration those two instances only worked because nginx pinned each session to one box.

How do I migrate a Node.js MCP server step by step?
The migration is five steps: upgrade the SDK to the RC line, delete your session-keyed transport map, remove sticky sessions from the load balancer, declare tools/list cacheability with ttlMs, and add RFC 9207 issuer validation. My diff for a 15-tool server removed 412 lines and added 168. Statelessness mostly means deleting code.
- Pin the RC SDK. Install the release-candidate line of
@modelcontextprotocol/sdkand run your existing tests. Compile errors point you at every deprecated API. - Delete session state. This was my old transport layer:
// BEFORE: session-keyed transports (2025 spec)
const transports = new Map<string, StreamableHTTPServerTransport>();
app.post('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'] as string | undefined;
if (sessionId && transports.has(sessionId)) {
return transports.get(sessionId)!.handleRequest(req, res, req.body);
}
if (isInitializeRequest(req.body)) {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => transports.set(id, transport),
});
await server.connect(transport);
return transport.handleRequest(req, res, req.body);
}
res.status(400).json({ error: 'No valid session' });
});
And this is the entire replacement:
// AFTER: stateless, per-request transport (2026-07-28 RC)
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // stateless mode
});
res.on('close', () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
If your tool handlers mutate shared server state, construct the McpServer per request too. Mine are pure functions over a database, so a shared instance is fine.
- Remove sticky sessions from the load balancer. One deleted line in my nginx upstream block, the same boring HTTP scaling I rely on in my API development ↗ work:
upstream mcp_backend {
# ip_hash; <- delete this line
server 10.0.1.10:3000;
server 10.0.1.11:3000;
}
- Cache
tools/listwithttlMs. Without the handshake, clients re-fetch the tool list more often. The RC lets you declare it cacheable so they do not hammer you:
const server = new McpServer(
{ name: 'review-reply-mcp', version: '2.0.0' },
{
capabilities: {
tools: { listChanged: false, cache: { ttlMs: 300_000 } }, // 5 minutes
},
}
);
- Harden OAuth with
issvalidation. RFC 9207 issuer checking is mandatory in the RC, and it is a five-line guard:
function assertIssuer(params: URLSearchParams, expected: string) {
const iss = params.get('iss');
if (iss !== expected) {
throw new Error(`RFC 9207 violation: iss "${iss}" does not match "${expected}"`);
}
}
While you are in the auth code anyway, run the rest of the audit; I covered the full surface in my secure MCP server hardening checklist ↗.
Statelessness is not a constraint here, it is the feature. The moment session state left the protocol, my MCP server became a normal HTTP service that my load balancer, autoscaler, and deploy pipeline already knew how to handle.
How do I move long-running work to the new Tasks model?
Any tool that runs longer than a client timeout should now return a task handle instead of blocking. The client polls tasks/get with that handle until the work completes, which fits a stateless world: any instance can answer the poll because task state lives in your store, not in a connection.
My slowest tool syncs every unanswered review for a business location and drafts replies, which can take two to four minutes. The old version streamed progress notifications over a held connection. The new version:
server.registerTool('bulk_review_sync', {
description: 'Sync and draft replies for all unanswered reviews',
inputSchema: { locationId: z.string() },
}, async ({ locationId }, extra) => {
const task = await extra.tasks.create({ ttlMs: 15 * 60_000 });
syncQueue.add(async () => {
const result = await runFullSync(locationId, (pct) =>
task.progress({ percent: pct })
);
await task.complete(result);
});
return task.asResult(); // client polls tasks/get with the task token
});
Back the task store with Redis or your database, never process memory, or you have just reinvented the sticky session you spent step 2 deleting.
How do I test my server against the release candidate?
Test three things: protocol conformance with the RC build of MCP Inspector, statelessness by killing an instance mid-conversation, and regression by replaying a day of production traffic. If all three pass, you are ready for July 28.
The kill test is the one that caught my only real bug. Start two instances behind the load balancer, begin a multi-call agent session, then stop one instance. Under the 2025 spec this stranded the session; under the RC the surviving instance must serve the next request without the client noticing. Mine failed the first run because a rate limiter still used an in-memory counter, which is exactly the class of hidden state this test exists to find.
For replay and regression I lean on traces rather than logs, since a single agent request fans out across tools. I covered that setup in AI agent observability with Node.js and OpenTelemetry ↗.
What should be on my migration-day checklist?
Cut over behind a second route, not in place. I ran /mcp/v2 alongside the legacy endpoint for a week, which made rollback a config change instead of a deploy. The full list I worked through:
- Deploy the stateless build on a parallel route with both instances live.
- Point one internal client at it and run the kill test in production.
- Flip the default route, keep the legacy endpoint up for old clients.
- Watch error rates and p95 latency for 24 hours.
- Delete the session code path and the legacy route before the 12-month window makes you forget it exists.
Total elapsed time for me: about six hours of engineering plus the week of parallel running. Traffic in June is forgiving; traffic in late July, when every host application starts preferring the final spec, will not be.
Key takeaways
- The final MCP spec ships July 28, 2026; the release candidate locked May 21, 2026, and 15,900+ public mcp-server repos on GitHub are affected.
- The stateless core removes the initialize handshake and the
Mcp-Session-Idheader, so remote servers no longer need sticky sessions. - Remote HTTP servers should migrate in June while traffic is forgiving; stdio-only local servers can mostly wait for an SDK bump.
- The migration is mostly deletion: my diff removed 412 lines and added 168 for a 15-tool production server.
- While you are in there, move long tools to Tasks and add RFC 9207
issvalidation; both are cheap now and mandatory soon.
FAQ
Will my old MCP server stop working?
Not on July 28, 2026. The spec ships with a 12-month deprecation policy, so compliant clients keep accepting the 2025 protocol version into mid-2027. But Tasks, Extensions, and MCP Apps only target the new version, and host applications will drop old versions on their own schedules. Treat mid-2027 as the hard wall.
What replaces Mcp-Session-Id?
Nothing replaces it at the protocol level, which is the point of the revision. If your tools need continuity across calls, carry it yourself: an identifier inside tool arguments, a signed token, or server-side state in Redis keyed by something the client passes. The protocol no longer manages conversational state for you.
Do stdio-based local MCP servers need any changes?
Mostly no. stdio servers never used HTTP headers or load balancers, so the stateless core barely touches them, and the SDK shims the lifecycle change. Upgrade at your next planned SDK major bump. The exceptions are tools that run for minutes, which should still adopt the redesigned Tasks model.
How long does a typical migration take?
My production server, 15 tools and two instances, took about six hours of engineering plus a week of parallel running before I deleted the old path. A single-instance hobby server is an afternoon. A multi-tenant server with in-memory rate limiting, caching, or per-session auth will take longer, because every piece of hidden state has to move to a shared store.
Working on something like this?
I build web apps, AI features, and mobile products for clients. If this article matches a problem you have, tell me about it.
Start a conversationMalik Hamza Shabbir · Full-Stack & AI Engineer
I build full-stack and AI products solo: a reputation SaaS in production, RAG pipelines, and React Native apps. I write from what I ship, not from documentation summaries.
Related articles
How to Secure an MCP Server: 2026 Hardening Checklist
I audited my production MCP stack against the NSA's May 2026 guidance and the OX Security RCE disclosure. Here is the 12-point hardening checklist I use.
AI Agent Observability in Node.js with OpenTelemetry
OTel GenAI spans went stable in early 2026. Here is how I instrument a TypeScript agent in Node.js, track cost per trace, and alert on silent failures.
What Is WebMCP? Making Your Web App Work with AI Agents
WebMCP, announced at Google I/O 2026, lets your web app register typed tools AI agents can call in Chrome 149. Here is how I exposed mine, with code.