[2026] MCP Stateless Migration: Lambda + MRTR (Guide)
MCP 2026-07-28 removes sessions: no initialize handshake, no Mcp-Session-Id, every request carries protocol version in _meta, and mid-call input uses Multi Round-Trip Requests. I migrated our Junagadh gateway to stateless Lambda in one evening — P95 780ms, zero sticky routing, ElastiCache deleted, rollback 2s.
Author: Deepak Bagada — AI Developer & Architect, Junagadh, Gujarat, India — Founder SaaS Next, builder of Curro. Connect linkedin.com/in/deepak-bagada · deepakbagada.in — Last reviewed 2026-09-17.
I run AI Development & Autonomous Agents where every gateway now ships stateless by default. The web layer is Website Development & Laravel Architecture, rollout automation lives in Business Workflow Automation, and direct questions go to get in touch.
Why the session model broke at scale
MCP 2025-11-25 was a stateful protocol. A client opened with initialize, the server issued a session, and every later request echoed Mcp-Session-Id. That design pinned a conversation to whichever instance issued the session. Run two instances and you needed ALB sticky routing or a shared session store in DynamoDB or ElastiCache. Both were correct for that protocol. Both were tax.
The numbers tell you why the maintainers moved. Per the official MCP blog for the 2026-07-28 specification, Tier 1 SDKs pull close to half-a-billion downloads a month, and LangChain reports MCP tool calls from ChatGPT users up 98x across 2026, more than doubling in August alone. Session infrastructure does not survive that curve. Every new instance multiplies coordination cost, and every redeploy is a session massacre.
Here is my war story. In June 2026 our Rajkot RFQ-inbox gateway ran two ECS tasks behind an ALB with stickiness plus an ElastiCache session store. At 02:00 I deployed a one-line Pydantic fix. The new tasks came up healthy, the old tasks drained, and 340 live agent sessions died with them — each holding a half-filled quotation draft for a foundry client. The stream broke, the client retried, and the retry created duplicate draft quotations because our create_draft tool was not idempotent. It took 18 minutes to notice, 40 minutes to clean the duplicates, and one uncomfortable call where I explained why a one-line fix cost a client 58 minutes of night-shift work. The session store survived. The sessions did not. That night I decided our gateway would go stateless the week the spec froze.
MRTR replaces every server-initiated request
The old protocol let servers push requests to clients mid-call — confirmations, sampling calls, root queries — over a held-open stream. The 2026-07-28 spec deletes that pattern and replaces it with Multi Round-Trip Requests (SEP-2322). A server that needs input returns resultType: "input_required" with an inputRequests map (elicitations, sampling calls, root queries) and an opaque requestState token. The client fulfills the requests, then re-sends the original call with inputResponses plus the echoed requestState. Any instance can pick it up because requestState carries all context needed to resume. No shared session store. No held-open connection. The server never waits.
This is what makes stateless work on AWS Lambda, and it is the single design decision I respect most in the new spec. Our Lambda handler is now a pure function of the request:
// Lambda MCP handler — stateless, MRTR resume on any instance
import type { APIGatewayProxyHandler } from "aws-lambda";
export const handler: APIGatewayProxyHandler = async (event) => {
const call = JSON.parse(event.body ?? "{}");
const tenant = call._meta?.tenant_id ?? "public";
// 1. Gate BEFORE exec — OPA + short-lived scoped JWT
if (!(await opaAllow(tenant, call.tool))) {
return json(403, { error: "denied — HITL required" });
}
// 2. Tool needs human input? Return input_required, never hold the stream
const missing = await validateArgs(call.tool, call.args);
if (missing.length) {
return json(200, {
resultType: "input_required",
inputRequests: { elicitation: missing },
requestState: signState({ tool: call.tool, args: call.args, tenant }),
});
}
// 3. Idempotent exec — re-issued calls produce zero duplicate side effects
const result = await execIdempotent(call.tool, call.args, tenant);
return json(200, { resultType: "complete", result });
};
# FastMCP server — stateless from the start, explicit identifiers
from fastmcp import FastMCP
from pydantic import BaseModel
mcp = FastMCP("junagadh-gateway", protocol="2026-07-28")
class QuoteArgs(BaseModel):
gstin: str
items: int
@mcp.tool(idempotency_key="gstin")
def create_draft(args: QuoteArgs, tenant_id: str) -> dict:
key = f"{tenant_id}:{args.gstin}"
if seen(key): # re-issued MRTR retry returns the stored result
return stored(key)
return store(key, build_draft(args, tenant_id))
Two details from production. First, requestState must be signed and timestamped — an unsigned resume token is a confused-deputy hole, because the model can see and shape state identifiers. Second, every tool behind MRTR must be idempotent. Stream resumability was removed, so a broken response stream means the client re-issues the call. AWS Architecture Blog (Sep 1, 2026) prescribes the same idempotent-task pattern from the Well-Architected Agentic AI Lens. Our create_draft duplicate disaster from June is now impossible by construction.
Header routing, cache hints and trace context
Streamable HTTP requests must now include Mcp-Method and Mcp-Name headers (SEP-2243). Your gateway, rate limiter and WAF can route and meter on headers instead of parsing JSON bodies. Responses from tools/list, prompts/list, resources/list and resources/read carry ttlMs and cacheScope (SEP-2549), so catalogs stop being re-fetched every run — LangChain's new langchain.mcp client caches them with cache=True. Every request carries W3C Trace Context in _meta (traceparent, tracestate, baggage), so traces flow into any OpenTelemetry backend including CloudWatch without custom plumbing.
Second war story, this time about money. Our old stack paid for three things that existed only to compensate for sessions: an ElastiCache node for the session store (₹3.1K/mo on cache.t3.micro Mumbai), ALB LCU burn from sticky routing (₹1.8K/mo at our volume), and a sidecar that reaped dead sessions (~₹900/mo in Fargate time). Total ~₹5.8K/mo — basically a second VPS — to keep 340 sessions warm for a Surat textile catalog sync that runs 11 minutes a day. After migration: zero. Same gateway serves the Surat sync plus the Rajkot inbox from plain round-robin Lambda, P95 780ms sandboxed, and the 90-day OTel ledger in Postgres proves every call. The protocol embodies failure isolation now; instance loss is a non-event and scale-in never drains sessions.
When NOT to migrate yet
Do not delete your legacy lane this week. Protocol versions are frozen snapshots — a client and server only need one shared version, so 2025-11-25 servers keep working with clients that still speak it. If revenue flows through 2025-era clients (ours did: two foundry buyers on pinned desktop builds), keep the backward-compatible lane with session semantics, keep ALB stickiness and the session store for that lane only, and instrument the gateway to log protocol version per request. Set a sunset date and mean it.
Also plan the deprecation exits deliberately. Roots, Sampling, Logging and the HTTP+SSE transport are deprecated with a twelve-month floor — earliest removal July 2027. ping, logging/setLevel and notifications/roots/list_changed were removed outright, log level moved into per-request _meta, and resource-not-found changed from -32002 to -32602. If your client code matches on -32002, migration breaks it silently. Grep first, migrate second. New servers should target 2026-07-28 directly and never adopt deprecated features.
Migration checklist that passed our conformance run
| Step | Action | Proof artifact |
|---|---|---|
| 1. SDK opt-in | Upgrade to Tier 1 SDK speaking 2026-07-28, enable explicitly | server/discover returns new version |
| 2. Session audit | Grep for Mcp-Session-Id, handshake state, sticky assumptions |
Zero session references in code |
| 3. Tasks move | Experimental Tasks API → official io.modelcontextprotocol/tasks extension |
tasks/get + tasks/update green |
| 4. Error codes | -32002 → -32602, remove ping/logging/setLevel |
Conformance suite 100% |
| 5. MRTR | All mid-call input via input_required + signed requestState |
Kill-connection test resumes clean |
| 6. Delete tax | Remove session store, sticky rules, handshake infra | Bill drops ~₹5.8K/mo |
| 7. Observe | W3C trace in _meta, per-operation metrics on Mcp-Method |
Grafana P95 dashboard + 90-day JSONL |
Run the official conformance suite in a test environment and promote only when green — protocol inspectors can pin 2026-07-28 and test exactly what clients will send.
Frequently Asked Questions
What changed in the MCP 2026-07-28 stateless core?
The initialize handshake and Mcp-Session-Id are gone; each request carries protocol version, client identity and capabilities in _meta, mid-call input uses MRTR, and Tasks plus MCP Apps ship as extensions. Servers scale on plain round-robin HTTP with no session store, and any instance can resume any call.
How does MRTR resume a call on any Lambda instance?
The server returns input_required with an inputRequests map and a signed opaque requestState token; the client answers and re-sends the original call with inputResponses. All resume context travels in the token, so the retry lands on any instance with no shared state and no held-open stream.
How much does going stateless save on AWS in India 2026?
Our Junagadh gateway deleted ~₹5.8K/mo — ElastiCache session store, ALB sticky LCU burn and a session-reaper sidecar — while holding P95 780ms. Savings scale with instance count because the new protocol needs zero compensating infrastructure; log protocol version per request and sunset the legacy lane on a date.
Can a Gujarat SME migrate without a Mumbai or Bengaluru agency in 2026?
Yes — our two-person Junagadh team migrated in one evening: SDK opt-in, session grep, MRTR for two approval tools, conformance green, then deleted the session store. The frozen-version rule means old clients keep working during the transition, so the risk window is one deploy with 2s rollback, not a rewrite.
Bottom Line: MCP 2026-07-28 turns agent infrastructure into ordinary HTTP — stateless, cacheable, routable — and the migration pays for itself the month you delete the session store. Keep the legacy lane for old clients, make every tool idempotent, and let MRTR do the waiting.
From Junagadh — where the gateway holds no sessions and the ledger remembers everything.