MCP Goes Stateless: Migrating to the 2026-07-28 Spec
If you operate an MCP server behind a load balancer, the MCP 2026-07-28 spec migration is the biggest change you will make to that deployment this year. On July 28, 2026, the Model Context Protocol specification tagged “2026-07-28” reached General Availability, superseding “2025-11-25,” and all four Tier-1 SDKs — TypeScript, Python, Go, and C# — shipped updated releases the same day. The headline change is not a new tool-calling feature. It is the removal of protocol-level sessions, the removal of the initialize handshake, and a wholesale shift to a stateless, per-request wire format. That single architectural decision cascades into routing, caching, auth, and the entire server-initiated-request model that Sampling and Roots were built on.
This is not a cosmetic version bump, and treating it like one is the single most common mistake teams make with this release. Every framework-level post on this site so far has covered a slice of the surrounding ecosystem — server frameworks, security architecture, long-running task patterns — but none has treated the base wire-protocol rewrite itself as the unifying event it is. It is more consequential than any of those narrower changes, because it is the load-bearing assumption they all now sit on top of.
Teams that skip a deliberate migration plan will find their gateways silently misrouting requests once session affinity stops mattering, their Sampling-dependent servers throwing UnsupportedProtocolVersionError against clients still speaking the old handshake, and their long-running task flows blocked on an RPC — tasks/result — that no longer exists in the spec at all.
What this covers: what SEP-2567, SEP-2575, SEP-2322, SEP-2549, and SEP-2468 actually change in the wire protocol; a concrete migration walkthrough from session-pinned gateways to stateless round-robin routing; how to replace deprecated Sampling and Roots calls with the new Multi Round-Trip Requests pattern; the dated SDK-version timeline from RC beta to GA to patch releases; and the hidden costs — lost SSE resumability chief among them — that launch-day coverage of this migration mostly skipped over.
Context and Background
MCP’s original transport design, Streamable HTTP, borrowed a session model that looked a lot like a traditional web session: a client called initialize, the server handed back an Mcp-Session-Id, and every subsequent request on that connection carried the identifier so the server could resume state without the client re-sending its full context each time. It worked, and for a locally-run, single-user MCP server talking stdio to one client, it barely mattered. But it imported a well-known class of infrastructure problems into a protocol whose main appeal was supposed to be simplicity: session affinity means your load balancer needs sticky routing, or you need a shared session store like Redis behind it, and either choice adds an operational dependency that most MCP server operators never explicitly signed up for when they first stood up a server.
This is not a new problem in distributed systems generally — it is the same trade-off HTTP itself settled decades ago in favor of statelessness at the transport layer, precisely so that any server could answer any request. MCP’s early session model was, in effect, a step backward from that lesson, reintroduced because a chat-style handshake felt natural for a protocol modeled loosely on a single long-lived conversation between one client and one model. It took real production scale for that mismatch to surface as an operational cost rather than a design curiosity.
By early-to-mid 2026, as more MCP servers moved from local, single-tenant integrations into multi-tenant HTTP deployments serving many concurrent agents, this became the protocol’s most-cited scaling complaint in operator forums and issue trackers. The MCP server security architecture considerations that already burdened HTTP-transport operators — OAuth flows, token scoping, audience validation, replay protection — now had to coexist with session-affinity requirements at the load-balancer layer, doubling the infrastructure surface a platform team had to reason about for every server they ran. Auth state and routing state were two separate stateful subsystems layered on top of what was, conceptually, meant to be a thin RPC protocol.
The Model Context Protocol steering group addressed this directly with a cluster of Spec Enhancement Proposals landing together in the 2026-07-28 revision: SEP-2567 (stateless Streamable HTTP), SEP-2575 (handshake removal and server/discover), SEP-2322 (Multi Round-Trip Requests), SEP-2663 (the tasks extension), SEP-2549 (cacheable results), and SEP-2468 together with SEP-2596 (OAuth hardening and the Sampling/Roots deprecation timeline). Full detail on the individual proposals and the canonical protocol text lives at the official Model Context Protocol specification site. Read together, these SEPs describe one consistent goal, not six unrelated ones: every MCP request should be fully self-describing, so that any server replica, anywhere, can answer it correctly, with no connection affinity, no handshake state, and no shared session store required anywhere in the request path.
The Stateless Core: What SEP-2567 and SEP-2575 Actually Remove
The MCP stateless protocol redesign touches four distinct mechanisms that previously depended on persistent connection state, and understanding all four — not just the headline “sessions are gone” — is the difference between a clean migration and a production incident during your rollout window. It is tempting to read “stateless” as a single flag you flip; in practice it is four separate removals, each with its own migration checklist, and skipping one because you fixed another is exactly how a team ends up with a server that passes its own unit tests but fails the moment a real gateway sits in front of it.
Figure 1 shows the old handshake-plus-session flow next to the new per-request flow: the top half depends on the server remembering a session ID between calls; the bottom half carries protocol version and capabilities inside every request’s _meta block, so no call depends on any prior call having happened.

Figure 1: Sequence comparison of the pre-2026-07-28 initialize-and-session handshake against the post-2026-07-28 stateless per-request exchange using server/discover and _meta fields.
Under 2026-07-28, protocol version negotiation and client capability declaration no longer happen once, at connection setup. They travel on every single request, inside a _meta object using the io.modelcontextprotocol/protocolVersion and clientCapabilities keys. If a server receives a request whose declared version it cannot serve, it returns UnsupportedProtocolVersionError immediately, on that request, rather than failing an earlier handshake step that every subsequent call implicitly depended on. This is a meaningfully different failure mode operationally: version mismatches are now caught, and reported, per call, which means a fleet running mixed server versions during a rolling deploy will surface version errors scattered across individual requests rather than as a single, easy-to-spot connection-level negotiation failure at the start of a client’s session.
Session IDs and Mcp-Session-Id Are Gone
SEP-2567 removes protocol-level sessions and the Mcp-Session-Id header from Streamable HTTP entirely — not deprecates, removes. There is no server-side session object for the protocol to track, and no header for a client to carry forward between calls. This is the change with the single largest infrastructure blast radius: anywhere your architecture assumed “the same client keeps hitting the same server instance,” that assumption is now false at the protocol level, full stop. Any statefulness your application genuinely needs — a multi-step wizard, an in-progress upload, a conversation buffer — now has to be built and explicitly owned by your application logic, keyed by whatever identifier you choose, rather than delegated implicitly to MCP’s transport layer the way it may have been before.
server/discover Replaces the initialize Handshake
The initialize → notifications/initialized sequence is gone entirely, not merely reordered. In its place, SEP-2575 introduces a new, required RPC: server/discover. It is a single, idempotent call a client can issue at any time — not only at connection start — to retrieve a server’s current protocol version support and capability advertisement. Because it is idempotent and requires no prior state, server/discover can be called against any replica behind a load balancer and get an equivalent answer, which is exactly the property session-based initialize could never guarantee once you scaled past a single server instance: two replicas answering initialize at slightly different points in a deploy rollout could legitimately disagree, and clients had no clean way to detect that.
Long-Lived Streams Consolidate Into subscriptions/listen
The old model exposed several separate long-lived mechanisms: an HTTP GET for server-to-client streaming, plus distinct resources/subscribe and resources/unsubscribe RPCs for resource-change notifications, each with its own lifecycle to manage. SEP-2575 collapses all of that into one long-lived stream, subscriptions/listen, where the client opts into specific notification types up front rather than opening and tearing down separate subscription channels for each resource it cares about. Alongside this consolidation, ping, logging/setLevel, and notifications/roots/list_changed are removed outright; log verbosity is now set per-request via _meta.logLevel rather than through a stateful, connection-wide setting that persisted for the life of a session.
The practical effect for server authors: an implementation that used to hold open multiple named subscriptions per connected client now holds, at most, one subscriptions/listen stream per client, with notification-type filtering handled as request parameters rather than as separate RPC lifecycles. This is a genuine simplification once the initial rewrite is done — fewer RPC handlers to maintain, fewer connection-state edge cases to test — but it does mean auditing every place your server code special-cased resources/subscribe versus resources/unsubscribe behavior and folding that logic into stream-open-time filtering instead of connection-lifecycle events.
Migration Walkthrough: Gateways, Sampling, and Tasks
Three areas require concrete code and infrastructure changes, and they are the ones that actually break silently — in production, under load, well after your local test suite passes — if you skip them.
Figure 2 traces the Multi Round-Trip Requests pattern that replaces server-initiated sampling: instead of the server calling back into the client mid-request, the server returns an input-required result and the client re-issues the same call with the answer attached.

Figure 2: MRTR sequence — a tool call that needs client input returns InputRequiredResult with resultType input_required, and the client resubmits the request carrying inputResponses to complete it.
Step 1 — Re-architect Gateway Routing Away From Session Affinity
If your gateway pinned requests to a specific backend using Mcp-Session-Id, or maintained a Redis-backed lookup table mapping session IDs to instances, that entire subsystem is now dead weight, and worse than dead weight — it is a subsystem that can actively misroute traffic once your servers stop emitting session IDs your gateway logic still expects to see. Google’s Developers Blog framed the upside of removing it explicitly: a stateless core is what lets MCP gateways adopt standard round-robin routing and deploy MCP servers on serverless platforms such as Cloud Run, because there is no session affinity left to preserve and no Redis dependency required purely for MCP routing decisions.
# Illustrative before/after gateway config, conceptual only
# BEFORE (2025-11-25): session-pinned routing
upstream mcp_backends {
hash $http_mcp_session_id consistent; # sticky by session
server mcp-1.internal:8080;
server mcp-2.internal:8080;
}
# AFTER (2026-07-28): stateless round-robin
upstream mcp_backends {
least_conn; # or round-robin
server mcp-1.internal:8080;
server mcp-2.internal:8080;
server mcp-3.internal:8080; # scale freely, no affinity
}
Figure 3 sets the before/after gateway topology side by side.

Figure 3: Before, a load balancer pins clients to specific server instances via a Redis-backed session table; after, any stateless replica answers any request, enabling plain round-robin and serverless scale-to-zero deployment.
Removing the session store also changes your caching story, and this is the part teams most often forget to plan for. The new CacheableResult interface (SEP-2549) requires every list/read result to carry ttlMs and a cacheScope of either "public" or "private". Previously, caching decisions were often bolted on ad hoc, frequently keyed off the same session ID your gateway was using for routing — a pattern that quietly worked precisely because the two subsystems shared state. Now caching is a first-class, protocol-level field your server must populate on every applicable response, and your gateway, or any intermediate CDN-style cache sitting in front of it, can honor it without any session context at all.
# Illustrative Python, conceptual - CacheableResult shape under 2026-07-28
def list_resources(request):
return {
"resources": [...],
"resultType": "success",
"ttlMs": 60000,
"cacheScope": "public", # or "private" if per-caller
}
Step 2 — Replace Sampling and Roots With Direct Calls or MRTR
Roots and Sampling are formally Deprecated under SEP-2577 and SEP-2596, with a minimum 12-month deprecation window before removal — so they still function today, but you should not build new integrations on them, and existing ones need a migration path on a realistic timeline, not an emergency one. The legacy HTTP+SSE transport, as distinct from Streamable HTTP, is likewise reclassified Deprecated in the same revision.
The mechanism that made server-initiated sampling/createMessage and roots/list possible was, fundamentally, a stateful connection the server could call back into mid-request, trusting that the same client was still listening on the other end. Without protocol sessions, that callback model has nowhere to live — there is no guaranteed persistent channel back to a specific client instance to call into. SEP-2322 replaces it with Multi Round-Trip Requests: instead of the server reaching back into the client, the server returns a result with resultType: "input_required" — an InputRequiredResult — and the client, which already knows how to retry a request, reissues the original call, this time attaching inputResponses. Every result in the new spec carries a required resultType field, so "success", "input_required", and error variants are always explicit in the payload rather than inferred from response shape or HTTP status code the way some implementations previously did.
// Illustrative TypeScript, conceptual - MRTR replacing sampling/createMessage
// OLD (2025-11-25): server calls back into client mid-request
async function oldToolHandler(params) {
const sample = await server.requestSampling({
messages: [{ role: "user", content: "Summarize this diff" }],
});
return { result: sample.content };
}
// NEW (2026-07-28): server returns input_required, client retries
async function newToolHandler(params) {
if (!params.inputResponses) {
return {
resultType: "input_required",
inputRequest: { kind: "completion", prompt: "Summarize this diff" },
};
}
return {
resultType: "success",
result: params.inputResponses.completion,
};
}
For most tool authors, the pragmatic fix is simpler than reimplementing MRTR faithfully for every call site: if your server was using Sampling purely to get a completion from whatever model the client happened to be running, call your own model provider’s API directly from the server instead, and pass any user-facing context in as ordinary tool parameters up front. MRTR is the right tool specifically when the client genuinely must supply something the server cannot obtain on its own — an interactive elicitation, a user confirmation dialog, a client-side file path sourced from Roots. For everything else, cutting out the round trip entirely is both simpler to implement and removes a runtime dependency on a feature with a 12-month deprecation clock already running against it.
Step 3 — Convert Blocking Task Flows to tasks/get Polling
The experimental “tasks” support from earlier draft revisions is now the official io.modelcontextprotocol/tasks extension (SEP-2663), and it changed shape substantially in the process of graduating. The blocking tasks/result call — which held a connection open until a long-running operation finished, functioning almost like a synchronous wait — is gone. In its place: tasks/get for polling current status on demand, and a new tasks/update call for pushing incremental progress. tasks/list was removed entirely, so servers can no longer offer a client-facing enumeration of all outstanding tasks; each task must now be tracked by the ID the client already holds from when it created it. For a deeper comparison of this pattern against streaming and webhook alternatives for long-running tools, see the dedicated piece on MCP long-running tool patterns.
# Illustrative Python, conceptual - polling replaces blocking tasks/result
# OLD: blocked until done
result = await client.call("tasks/result", {"taskId": task_id})
# NEW: poll with backoff
import time
while True:
status = await client.call("tasks/get", {"taskId": task_id})
if status["resultType"] == "success":
result = status["result"]
break
if status["resultType"] == "error":
raise RuntimeError(status["error"])
time.sleep(poll_interval_seconds)
Because tasks/get is a plain stateless RPC rather than a held-open connection, it composes naturally with the rest of the stateless core — any replica can answer a poll, so a task started against one server instance can be checked from any other instance behind the same gateway. But it does shift the responsibility for backoff strategy, timeout handling, and give-up logic entirely onto the client, where previously the blocking call’s own connection timeout did a meaningful share of that work implicitly, without the client author having to think about it at all.
Step 4 — Update Your Test Suite and Monitoring Before You Update Production
None of the three steps above are safe to verify manually against a staging environment alone, because the failure modes that matter — mixed-version rollouts, concurrent MRTR round trips from multiple agents, a dropped subscriptions/listen stream mid-flight — are concurrency and timing bugs by nature. Add explicit test cases for: a client on the old handshake hitting a 2026-07-28 server (expect a clean UnsupportedProtocolVersionError, not a hang); two concurrent MRTR flows against the same tool where responses could plausibly be swapped if your correlation-ID scheme has a gap; and a forcibly-dropped subscriptions/listen stream, to confirm your client reissues a fresh request rather than attempting a resume that the protocol no longer supports.
On the monitoring side, add a dashboard panel counting UnsupportedProtocolVersionError responses specifically, segmented by server replica version if your deploy tooling can tag that. A spike there during a rollout window is expected and benign; a sustained rate after rollout completes means some client population is still speaking the old handshake and needs its own upgrade path communicated to them. Treat this metric as your primary signal that the migration is actually finished, rather than assuming it is done once your own server code merges and deploys cleanly.
Trade-offs, Gotchas, and What Goes Wrong
Removing session state is a clean win for routing flexibility and deployment elasticity, but it is not a free lunch, and this is the part launch-day coverage of the migration largely glossed over in its rush to cover the headline changes.
The most consequential loss is SSE resumability. The old transport supported Last-Event-ID so a client could reconnect after a dropped stream and resume exactly where it left off, without re-requesting everything that had already streamed through. SEP-2575 removes this along with the rest of the stateful transport surface. Under 2026-07-28, a broken stream has no resume point — the client must reissue the original request with a new ID and start over from scratch. For a subscriptions/listen stream that has been open for hours accumulating notification state relevant to a long-running agent workflow, this is a real operational cost, not a theoretical one: a flaky network hop now means reconstructing context rather than resuming from a byte offset, and that reconstruction cost falls entirely on whatever client code you write.
A second, subtler gap: elicitation-completion notifications and other callback-style signals that used to arrive as server-initiated pushes now have no session to be pushed into in the first place. If your server logic depends on knowing that a specific MRTR round trip completed — not just that a client sent inputResponses at some point, but that they belong to a specific earlier input_required result and not a different, concurrent one — you have to hand-roll a correlation ID scheme yourself. The protocol gives you resultType and lets you shape your own request and response payloads, but it does not hand you a built-in mechanism for matching a later “here’s your input” call back to the specific earlier prompt that triggered it, once no session context ties the two together automatically. Teams that skip this step will see intermittent input-response mismatches under concurrent load; the failure mode is easy to miss in testing with a single client hitting a single server, and only shows up once multiple agents are hitting the same stateless server pool concurrently in production.
Third, the version-per-request model means a rolling deploy across mixed old-and-new server replicas produces a burst of UnsupportedProtocolVersionError responses precisely during the deploy window, rather than a single clean cutover moment. Plan your rollout to route by declared version compatibility during that transition window, not purely by server capacity, or clients will see a scattering of version errors that looks alarming even when the underlying rollout is proceeding exactly as planned.
Finally, server/discover being callable at any time is a genuine feature, but it also means capability advertisement is no longer a one-time cost paid once at connection open. A naive client implementation that calls server/discover before every single request adds a full extra round trip to every call, which is a meaningful latency regression at scale. Well-behaved clients should cache the discovery result, respecting the server’s own CacheableResult TTL semantics for how long that cached advertisement should be trusted, rather than re-discovering on literally every request out of an abundance of caution.
There is also a testing-surface cost that is easy to underestimate going in. A stateful protocol, for all its operational downsides, gave you a natural place to reason about ordering: a session had a beginning, a middle, and an end, and bugs tended to show up as a broken sequence within that lifecycle. A stateless protocol has no such lifecycle to anchor your reasoning against — every request is independent, which means every request needs to be independently correct, and bugs that only manifest under concurrent, interleaved traffic from multiple agents hitting the same stateless server pool are structurally harder to reproduce from a single-client test run. Budget real engineering time for concurrency-focused testing here, not just a protocol-compliance checklist.

Figure 4: The SDK version timeline behind the 2026-07-28 spec — RC betas shipped a month ahead of GA, and both the Python and Go SDKs updated on GA day itself, with patch releases following within four weeks.
Treat this timeline as a practical adoption signal, not just a changelog curiosity. The gap between the 2026-06-29 beta and the 2026-07-28 GA was the actual window teams had to test against the new wire format before it became the default; teams that waited until GA to start migration testing compressed that work into whatever was left of the two Python patch releases that followed. If you are migrating a production MCP deployment now, pin to the latest patch (Python SDK v2.1.1 or newer) rather than the GA-day release, since both post-GA patches shipped fixes relevant to real migration friction, not just cosmetic changes.
Practical Recommendations
Treat this as an infrastructure migration first and a code migration second — the SDK upgrade itself is usually the easy part; the gateway, caching, and auth changes are where incidents actually happen, weeks after the code merges cleanly.
- Audit your gateway config for anything keyed on
Mcp-Session-Id— consistent-hash routing rules, Redis session lookups, sticky-cookie logic — and replace it with plain round-robin or least-connections routing before you deploy any 2026-07-28-speaking servers behind that gateway. - Upgrade to a stable, non-preview SDK release rather than lingering on a beta. Python teams should move to
mcpv2.0.0 or later; v2.1.1, from 2026-08-25, is the current patch as of this writing and includes a FastMCP import-warning fix that points directly at the migration guide. Go teams move to v1.7.0. Rust SDK support is evolving alongside the other implementations; if you depend on it, track its repository directly rather than assuming version parity with the Tier-1 four. - Grep your server code for
sampling/createMessage,roots/list, andtasks/result. Each call site is either deprecated or removed under the new revision; decide per call site whether a direct provider-API call or an MRTRinput_requiredresult is the right replacement, following the walkthrough above rather than a blanket rule. See also the comparison of FastMCP against the official SDK for how each framework’s high-level decorators have adapted to the new result shapes. - Populate
ttlMsandcacheScopeon everylist/readresult you emit. Skipping this doesn’t break clients today, but it silently disables a caching layer you may end up counting on once intermediate caches start honoringCacheableResultfields by default. - Validate
issper RFC 9207 on every OAuth token exchange, and plan a deliberate move off Dynamic Client Registration toward Client ID Metadata Documents — DCR is deprecated under this revision, not removed outright, but new integrations should not be built on it going forward. - Design a correlation-ID scheme for MRTR round trips before you need it under concurrent load, not after you’ve already shipped an intermittent input-mismatch bug to production and are debugging it under time pressure.
- Budget explicitly for lost SSE resumability in any long-running
subscriptions/listenconsumer — build your own checkpoint-and-resume logic at the application layer if a dropped stream losing its accumulated context is unacceptable for your particular use case.
Sequence these seven items rather than tackling them in parallel. Gateway routing and the SDK upgrade are prerequisites for everything else, because you cannot meaningfully test MRTR correlation or cache-header behavior against a server that is still speaking the old handshake underneath. Treat the Sampling and Roots migration, and the OAuth hardening work, as follow-on projects you can stage against their respective deprecation windows once the stateless core itself is solid in production — trying to land all seven changes in a single release is how a team ends up debugging three unrelated failure modes simultaneously with no clean way to isolate which change caused which symptom.
Frequently Asked Questions
What exactly does “stateless” mean in the 2026-07-28 MCP spec?
It means the protocol layer holds no session state between requests. There is no Mcp-Session-Id, no server-side session object tracking a connection’s history, and no initialize handshake that must precede other calls before they are considered valid. Every request carries its own protocol version and capabilities in _meta, and any server replica can answer any request without consulting shared state elsewhere. Your application can still be stateful internally — that responsibility just moved from the protocol layer up to you, the implementer.
Do I have to rewrite my MCP server to use server/discover?
Yes, if you relied on the initialize and notifications/initialized sequence for capability negotiation — that handshake is removed, not merely deprecated, in the 2026-07-28 revision. server/discover is now the required RPC for advertising version and capability support, and unlike initialize, it can be called at any point in a client’s lifecycle, not only once at connection start, which makes it far friendlier to stateless, replica-based deployments.
Is Sampling actually gone, or just deprecated?
Deprecated, not removed. SEP-2577 and SEP-2596 set a minimum 12-month deprecation window for Roots and Sampling, so existing integrations keep working for now without immediate breakage. But server-initiated calls like sampling/createMessage depended on the persistent connection state that no longer exists in the stateless core, so new work should migrate to direct provider-API calls or the Multi Round-Trip Requests pattern rather than extending Sampling-based code further.
What happened to tasks/list and tasks/result?
Both are removed outright, not deprecated alongside Sampling. The experimental tasks support graduated into the official io.modelcontextprotocol/tasks extension under SEP-2663, which replaces the blocking tasks/result call with polling via tasks/get plus a new tasks/update call, and drops tasks/list entirely — clients must track task IDs themselves after creating a task rather than asking the server to enumerate all outstanding tasks on demand.
Which SDK version should I be running right now?
For Python, v2.0.0 from 2026-07-28 is the GA baseline; v2.1.0 from 2026-08-24 and the v2.1.1 patch from 2026-08-25 are current and add features like direct StdioServerParameters support and prompt-message Image and Audio types. Go’s equivalent GA release is v1.7.0, also shipped 2026-07-28. TypeScript split into separate @modelcontextprotocol/server and @modelcontextprotocol/client packages, replacing the old monolithic SDK package entirely. C#’s ModelContextProtocol package moved from preview status to a 2026-07-28 stable release the same day.
Does removing session affinity actually save infrastructure cost?
There is no verified benchmark quantifying latency or cost reduction specifically from this migration, so treat any specific percentage claim you encounter with real skepticism. What is well-supported directionally: removing session affinity lets gateways use standard round-robin routing and deploy MCP servers on serverless platforms like Cloud Run without a dedicated session-affinity layer, per Google’s own framing of the change — a structural simplification in how these systems are built and operated, even without a hard number attached to quantify it.
Should I wait to migrate, given the 12-month deprecation window on Sampling and Roots?
Waiting on the deprecated features is reasonable if your integration genuinely depends on them and reworking it is nontrivial — you have real runway. But the stateless core itself, the handshake removal, and server/discover are not on a deprecation clock; they are the current spec now. Separate the two decisions: adopt the stateless transport and gateway changes on a near-term timeline, and treat the Sampling and Roots migration as its own, slower-paced project against that 12-month window.
Further Reading
- MCP server frameworks: FastMCP vs. the official SDK
- MCP server security architecture
- MCP tasks extension vs. streaming vs. webhooks for long-running tools
- Official Model Context Protocol specification
- Google Developers Blog on stateless MCP gateway design
By Riju — about
