Home / Articles

MCP 2026-07-28: How the Stateless Spec Changes Remote Server Design

The MCP 2026-07-28 spec goes stateless: no sessions or handshake, plus MRTR, Tasks and cache hints. What changes for remote servers and how to migrate them.

Publisher: REACTAPP.TOP23 min read5381 words

This article is published in English.

Sources: MCP Just Changed Its Architecture: Why the 2026 Specification Makes MCP Truly Cloud-Native

A turning point for MCP

The 2026-07-28 revision of the Model Context Protocol is a bigger event than a routine version bump. It overturns one of MCP's founding assumptions, namely that a remote server is organized around a protocol session tied to a connection. MCP has moved away from a stateful, bidirectional model and toward a stateless request and response design that behaves like ordinary distributed HTTP infrastructure. The release candidate announcement called it the largest revision since the protocol launched, and the final specification followed on July 28, 2026.

Stateful MCP wasn't incapable of working. The trouble was that protocol-level state created operational constraints that sit awkwardly on modern cloud platforms. A remote deployment might have to remember a session identifier, pin that session to one backend, coordinate state across replicas and keep long-lived communication machinery running for interactions that were really plain request and response. That's manageable at small scale. At large scale it brings the same class of headaches that sticky-session web systems have wrestled with for years.

In the new design the protocol session is removed and the initialize and initialized handshake is dropped. Each request embeds the client metadata it requires, and servers become findable through explicit discovery. Server-to-client interactions are reorganized as Multi Round-Trip Requests, HTTP headers carry routing metadata, clients get cache hints on list and resource responses, long-running work moves into the Tasks extension, extension lifecycle management is formalized and a stricter deprecation policy arrives. What results is an MCP core that fits comfortably behind plain load balancers, gateways, web application firewalls and serverless or autoscaled deployments.

One mental model helps. Old remote MCP acted like a distributed conversational connection protocol, while the new version acts like an HTTP API whose stateful behavior is expressed explicitly in the application or extension layer.

Why session-based MCP became a poor fit

The phrase "stateful MCP broke" needs careful reading, because stateful MCP didn't suddenly stop working. What happened is that protocol-managed state turned into a worse and worse match for the infrastructure where MCP servers were being deployed.

In the earlier model, a client opened with an initialize request, and the server answered with an Mcp-Session-Id that every later request had to carry. That tied the logical client to a server-side session. The maintainers' own before-and-after example makes the contrast plain: the legacy flow needed an initialization request followed by the real tool call, whereas the 2026 protocol can send the tool invocation directly as one self-contained request.

The operational consequence matters more than the saved HTTP request. Imagine an MCP service with eight replicas behind a load balancer. With sessions, request one creates a session on instance A, and request two has to reach instance A again unless the session state is shared. That forces a choice between two strategies: sticky routing, so the same client keeps hitting the same instance, or a shared session store that every instance can read. Both tie protocol behavior to deployment topology.

Sticky routing costs flexibility, because the load balancer can no longer send each request to whichever healthy instance has capacity. One node can drown while another idles because sessions are pinned, and failover gets harder when the original state lives on a node that vanished.

Keeping sessions in a shared store solves part of the problem while adding a distributed-systems one. Each session turns into a record that must be created, replicated, expired, purged and kept available at all times. At that point the MCP server is an application bundled with a session-state subsystem spread across machines. The release candidate calls out sticky sessions and shared stores as infrastructure that stops being required at the protocol layer once sessions disappear.

Kubernetes, autoscaling container platforms, cloud functions and serverless runtimes make the issue even clearer. A request may land on pod A, the pod may disappear during scaling, and the next request arrives at pod F. For a stateless HTTP service that's completely normal, but it hurts a protocol whose correctness assumes connection-associated state remains available.

The old protocol had a second conceptual mismatch. It used a bidirectional model in which servers could start requests toward the client. Elicitation, sampling and roots listing all relied on that pattern, which made sense while a persistent channel existed. Persistent bidirectional streams strain infrastructure, because firewalls, proxies, gateways, autoscalers, serverless runtimes and connection pools each have their own timeouts and lifecycles. Keeping a stream open just so the server might ask a question later is fundamentally different from ordinary request and response HTTP.

A developer who tried it independently gives a concrete example of the payoff. He called the new stateless protocol much easier to implement on both the client and server side, and highlighted that applications no longer track server-side session identifiers or steer a session back to one machine. He went on to build a stateless MCP explorer and release a new Datasette MCP integration.

So the insight isn't that state is bad. Hidden protocol state is expensive, while explicit application state is manageable.

The session and the handshake are gone

The change people notice first is that the protocol session no longer exists. In 2026-07-28 the initialize and notifications/initialized exchange is gone, and so is the Mcp-Session-Id header. Each request now includes the protocol version and client capabilities inside _meta. A client wanting to learn about a server beforehand can call server/discover. The final changelog classifies both removals, session and handshake, as major changes.

Previously the flow was a handshake followed by the real call:

Client
  |
  | initialize
  v
MCP Server
  |
  | Mcp-Session-Id = ABC
  v
Client
  |
  | tools/call + Mcp-Session-Id: ABC
  v
Same logical server session

Now the call can happen directly:

Client
  |
  | tools/call
  | protocol version
  | capabilities
  | client metadata
  v
Load Balancer
  |
  +----> MCP Instance A
  |
  +----> MCP Instance B
  |
  +----> MCP Instance C

A request now carries all the context the protocol layer needs to make sense of it. On top of that, Streamable HTTP copies key metadata into headers, for example in this request:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json

The body stays the source of truth, while the HTTP envelope offers metadata that intermediaries can use. The specification requires intermediaries and transports to reject inconsistent combinations of header and body where appropriate.

Removing the session doesn't mean applications can't have state. Application state simply becomes an explicit part of the data model. A shopping workflow could, for instance, return a handle for the basket:

{
  "basket_id": "bsk_8f2a..."
}

and the next tool call would hand that handle back along with the item:

{
  "basket_id": "bsk_8f2a...",
  "item_id": "SKU-123"
}

On the server side, the identifier is looked up in Redis, PostgreSQL, DynamoDB, Cosmos DB, an object store or any suitable system. The MCP transport stops owning that state. The maintainers recommend explicit handles like basket_id and browser_id as the way to replace hidden protocol state.

That amounts to an architectural separation, shown here:

MCP protocol state
        |
        v
       NONEApplication state
        |
        v
Explicit IDs + external state store


Long-running task state
        |
        v
Tasks extension + durable task store

It's arguably the most important idea in the whole release.

Per-request metadata and discovery

If the handshake is gone, how does a server find out which protocol version and capabilities a client supports? The answer is per-request metadata. Reserved _meta fields live under the io.modelcontextprotocol/* namespace, and version plus client capabilities accompany each request, with no one-time negotiation that a connection quietly remembers. When a client chooses to identify itself, its identity rides along as per-request metadata too, and servers can put theirs in result metadata. A request conceptually looks like this:

{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/call",
  "params": {
    "name": "search",
    "arguments": {
      "query": "MCP stateless architecture"
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      },
      "io.modelcontextprotocol/clientInfo": {
        "name": "my-agent",
        "version": "3.2.0"
      }
    }
  }
}

When information up front is useful, the server implements server/discover. The final changelog makes it a required RPC for servers, so clients can query supported protocol versions, capabilities and server identity before doing anything else. That's handy when a client needs to pick its behavior based on what the server supports.

There's a subtle distinction here. Clients may choose not to call server/discover, but servers must provide it. MCP therefore no longer needs discovery to establish a session, and it still offers a formal way to inspect capabilities proactively. The design resembles mature HTTP APIs, where each request is independently interpretable and optional discovery endpoints or metadata supply richer detail.

Round-robin routing and scale to zero

Here the design pays off most clearly for cloud architecture. Without protocol sessions, an MCP endpoint can run behind an ordinary load balancer with plain round-robin routing. The official announcement presents that as the intended target: several MCP instances, round-robin routing, and no shared protocol session store.

Take three replicas behind a load balancer:

                  +------------------+
                  |  Load Balancer   |
                  +---------+--------+
                            |
               +------------+------------+
               |            |            |
               v            v            v
           MCP Pod A    MCP Pod B    MCP Pod C

Request 1 may go to A, request 2 to C and request 3 to B. No protocol rule requires all three to reach the same instance, which is exactly the property cloud-native systems want.

Suppose a horizontal pod autoscaler decides only one replica is needed overnight. Two instances can disappear. When a request arrives later, a fresh instance can start and serve it immediately, since it doesn't have to rebuild a protocol session first. The architecture becomes this:

The MCP service itself is stateless, and business state stays durable outside it.

There's a caveat, though. Stateless MCP doesn't automatically make every MCP application scale to zero. If an application keeps a basket, workflow, conversation, transaction or job only in process memory, it has simply recreated the hidden-state problem one layer up.

Real scale-to-zero requires anything that must survive an instance shutdown to live in durable infrastructure. One plausible production layout uses Redis for short-lived coordination, PostgreSQL or a managed NoSQL database for lasting application state, and an object store for big artifacts. Authentication stays outside the process too, handled by the OAuth or OIDC authorization system rather than guessed from local memory.

The trade-off is clear. The protocol makes stateless infrastructure possible, and the application designer is now responsible for deciding which state survives, where it lives, how it's authorized, how long it stays and how it's cleaned up. That's a healthier systems-design boundary.

Managing state yourself

Explicit handles may be the most far-reaching result of this model. Take an AI procurement agent instructed to order 100 GPUs, obtain approval and submit the order. A session-based design could easily bury that workflow in MCP session state. The new architecture nudges toward something visible, where creating the request returns an identifier:

create_purchase_request()
        |
        v
purchase_request_id = pr_123
        |
        v
request_approval(pr_123)
        |
        v
approval_id = appr_789
        |
        v
submit_purchase(pr_123, appr_789)

The state is no longer implicit in an HTTP session. It's domain data, and that's powerful because domain data can be secured, audited, versioned, replicated and queried independently. A state record might conceptually look like this:

{
  "purchase_request_id": "pr_123",
  "user_id": "user_42",
  "status": "awaiting_approval",
  "items": [
    {
      "sku": "GPU-H100",
      "quantity": 100
    }
  ],
  "created_at": "2026-08-21T08:00:00Z"
}

The MCP server just resolves the identifier and validates authorization. Observability improves too. Instead of "session ABC did something", a system can report that purchase request pr_123 moved from draft to approved, a trace with real business meaning.

Security also becomes easier to reason about. An opaque identifier is never enough authorization by itself, and the server must still confirm that the authenticated caller is allowed to touch the state behind that handle. The Tasks extension treats task identifiers the same way: servers should generate IDs that are hard to guess, and leaving out tasks/list is an intentional privacy choice, since nobody can enumerate other people's task IDs.

Multi Round-Trip Requests

Dropping persistent server-to-client requests creates another basic problem. Consider a tool call like this one:

delete_customer_data(customer_id=123)

The server can't safely delete anything until a user confirms, with a prompt along the lines of "Delete 48 records?". The legacy model could keep a channel open and ask the client for more input. A stateless design can't depend on a permanently open connection, and Multi Round-Trip Requests, or MRTR, exist to cover that gap.

MRTR covers a single logical operation that needs several exchanges without a persistent connection or a server-side session. While waiting for confirmation, clarification or sampling, nothing holds an HTTP connection open or leans on sticky connection state. Instead the interaction becomes a chain of separate HTTP requests joined by continuation state. A client first requests an action, such as a tools/call for a data-deletion tool. The server skips launching its own RPC toward the client while the original operation sits suspended and answers with an intermediate result saying it needs more input:

{
  "resultType": "input_required",
  "inputRequests": {
    "confirm": {
      "type": "elicitation",
      "message": "Delete 48 records?",
      "schema": {
        "type": "boolean"
      }
    }
  },
  "requestState": "..."
}

The client collects the answers and retries the original request with them attached:

Client
   |
   | tools/call
   v
Server
   |
   | input_required
   | requestState
   v
Client
   |
   | user confirmation
   v
Client
   |
   | same operation + inputResponses + requestState
   v
Any MCP instance

The operation is thus broken into several HTTP round trips with no need to keep the transport open. The official release describes MRTR as the replacement for the server-initiated elicitation/create, sampling/createMessage and roots/list patterns.

The key field is requestState. Whatever the server needs in order to resume goes inside it, packaged as an opaque token that is signed, encrypted or otherwise protected against tampering. The specification does not push all application state into one central MCP session store, and the information needed to continue an interaction simply rides on the next request. It resembles a continuation token, and the bigger gain is that a long logical interaction no longer means a long physical connection.

HTTP headers and cache hints

Going stateless would boost scalability by itself, yet the protocol also becomes easier for HTTP infrastructure to work with. Streamable HTTP requests carry standardized headers such as Mcp-Method and Mcp-Name, so gateways and load balancers can decide on routing, authorization, rate limits and telemetry without opening JSON-RPC bodies. The body is still the authority, and any disagreement between a mirrored header and the matching body field leads to rejection.

A gateway could read these two values:

Mcp-Method = tools/call
Mcp-Name   = search

and apply policy checks of this kind:

tools/call + search  -> route/search-cluster
tools/call + execute -> route/execution-cluster
resources/read       -> route/resource-cluster

That gives a clean control-plane and data-plane split, since an intermediary can grasp the operational category of a request without understanding the application's JSON schema.

The release also adds caching metadata through ttlMs and cacheScope. A list response might carry them like this:

{
  "result": {
    "tools": [
      ...
    ],
    "ttlMs": 60000,
    "cacheScope": "public"
  }
}

The semantics intentionally resemble HTTP cache-control. ttlMs tells the client how long a result counts as fresh, and cacheScope says whether the result is safe to share. The final specification separates "public" from "private" and warns implementers not to treat cache scope as a stand-in for authorization checks. Freshness works out as follows:

fresh_until = response_received_time + ttlMs

A client should normally re-fetch when it needs the result and the result has gone stale, rather than reading the TTL as a mandatory polling interval. For implementations that do poll, the specification recommends jitter and backoff.

Tool catalogs make the value obvious. If a server lists 250 tools and 500 agents connect, constant tools/list calls from each one produce heavy control-plane traffic. A reasonable TTL lets clients reuse the catalog, and deterministic ordering stops needless churn in the serialized tool set, which can keep upstream LLM prompt caches stable. The changelog recommends deterministic ordering for just that reason. Another benefit: SSE connections that stay open are no longer the sole way to learn a list changed, so cache freshness and change notifications can both operate.

Long-running tasks

Stateless request and response APIs still have to handle jobs that run for minutes or hours. Take a call like this:

run_ml_training_job()

It might submit a distributed training job, wait for GPUs, run multiple stages, produce checkpoints and upload artifacts. Holding an HTTP connection open through all of that is fragile. The 2026-07-28 architecture handles it through the Tasks extension, identified as io.modelcontextprotocol/tasks.

For a supported tools/call, the server can reply with a task handle in place of the final result:

{
  "resultType": "task",
  "task": {
    "taskId": "task_7b93...",
    "status": "working"
  }
}

The client can then ask for status:

tasks/get(taskId)

and keep polling until the job reads completed, failed or cancelled. The extension also offers tasks/update for extra input and tasks/cancel for cancellation.

One distinction is critical. The core protocol is stateless, and the Tasks extension deliberately represents durable state:

Core MCP
    |
    +---- Stateless request/response
Tasks extension
    |
    +---- Durable asynchronous state machine

That isn't a contradiction. It's a separation of concerns. A task can map onto an external job system, as in this flow:

MCP tools/call
      |
      v
Create task
      |
      v
Queue / workflow engine
      |
      +--> Kubernetes Job
      |
      +--> Azure Batch
      |
      +--> AWS Step Functions
      |
      +--> Databricks Job
      |
      +--> CI/CD pipeline

Clients use the MCP task ID as the durable handle onto the state of that external workflow. Production setups should not keep task state only in a dictionary inside the process. The extension documents tasks as durable state machines and advises persisting task IDs durably, so that polling continues after a client crash or a server restart.

One implementation subtlety is left. The Tasks extension may still need requests routed to whichever system holds the task state. The final task proposal lets Mcp-Name carry the task ID for tasks/get, tasks/update and tasks/cancel, which allows intermediaries to send follow-ups to the state holder when needed. The full exchange among client, MCP server and task engine appears in this sequence diagram:

Client                  MCP Server               Task DB / Engine
  |                         |                           |
  |--- 1. tools/call ------>|                           |
  |                         |--- 2. Register Task ----->| (Status: working)
  |<-- 3. Return taskId ----|                           |
  |                         |                           |
  |--- 4. tasks/get ------->|                           |
  |                           \--- 5. Query state ----->| (Status: working)
  |<-- 6. Status: working --/                           |
  |                         |                           |
  |--- 7. tasks/get ------->|                           |
  |                           \--- 8. Query state ----->| (Status: completed)
  |<-- 9. Final Result -----/                           |

In the typical pattern, a client starts the job with a tools/call, and the server writes a durable task entry into a shared backend such as Redis, PostgreSQL or a workflow engine like AWS Step Functions or Kubernetes. It then returns a taskId and an initial working status right away. The first HTTP connection closes cleanly and frees network resources, and the client periodically checks progress with tasks/get.

The accurate architectural statement is therefore not that everything in MCP is stateless. The MCP core is stateless, and stateful behavior is modeled explicitly through application data and extensions such as Tasks.

A production data and ML operations agent

Picture a production AI platform exposing MCP tools to an enterprise agent. The agent has tools such as these:

search_dataset()
inspect_schema()
run_sql()
start_training_job()
get_training_metrics()
deploy_model()
rollback_deployment()

A user then requests the following: find the newest customer-churn dataset, profile it, train a candidate model, compare metrics against the production model and deploy only if the gain is at least 5%.

Under the old stateful architecture, the system could keep a long-lived protocol session tracking the interaction, which couples the user's logical workflow to one server instance. Under the new one, the process can be designed explicitly. The first call might be a search:

search_dataset("customer churn latest")

and the server returns a dataset handle:

{
  "dataset_id": "ds_2026_08_20_0042"
}

The next request inspects the schema through that handle:

inspect_schema("ds_2026_08_20_0042")

The schema comes from the metadata system. The following request launches training:

start_training_job("ds_2026_08_20_0042")

The MCP server creates a durable Task:

task_id = "task_train_98af..."

The training itself runs outside the MCP process. The client polls it:

tasks/get("task_train_98af...")

and the task might move through a series of states:

QUEUED
   |
RUNNING
   |
EVALUATING
   |
INPUT_REQUIRED
   |
RUNNING
   |
COMPLETED

In the INPUT_REQUIRED state the system could ask for approval of the deployment, noting that the candidate is 7.2% better but would increase GPU spend by 18%. MRTR can carry that question without keeping the first connection open. The client replies like this:

inputResponses = {
    "approve": true
}

and the request resumes.

Now imagine the MCP server running across six Kubernetes replicas:

                  Ingress
                      |
              Load Balancer
                      |
        +------+------+------+------+
        |      |      |      |      |
       MCP1   MCP2   MCP3   MCP4   MCP5
                      |
                      v
               Task Store
                      |
              +-------+-------+
              |               |
           Redis          PostgreSQL
              |
              v
        Workflow Engine
              |
        +-----+------+
        |            |
     GPU Job     Model Registry

Here the first tools/call lands on MCP2, the next request on MCP5, the task poll on MCP1 and the approval retry on MCP3. Nothing breaks, because the protocol no longer presumes that a session is bound to any single server. A killed MCP3 gets replaced by Kubernetes. If the deployment drops from six replicas to two overnight, the workflow survives because task and application state sit outside the process. That is where the specification proves its practical value.

Authorization and security hardening

Alongside the stateless changes come meaningful security upgrades. The final release asks for stricter OAuth and OIDC behavior: authorization servers should provide an iss parameter as RFC 9207 describes, and MCP clients must check that issuer before they redeem an authorization code, closing off authorization-server mix-up attacks. Client credentials are also tied to the issuer that created them, so reusing them against another authorization server is forbidden. Dynamic Client Registration remains for compatibility but is formally deprecated, with Client ID Metadata Documents as the replacement.

The threat model shifts usefully with statelessness, though it brings duties. A production deployment ought to authenticate each request and authorize the particular tool or resource operation. It should check explicit state handles against the authenticated principal, keep task IDs from being enumerated, and encrypt sensitive state both stored and in flight. Consider a record like this one:

basket_id = bsk_123
user_id   = user_456

Nobody should conclude from it that possessing bsk_123 is enough to act on the basket. Before anything else the server should verify ownership, along these lines:

authenticated_subject == basket.owner

and then enforce tool-specific permissions.

W3C Trace Context propagation is documented as well, carried in _meta through traceparent, tracestate and baggage. Tracing across the host, the MCP client, the MCP server and downstream services gets much simpler, and the maintainers link this directly to OpenTelemetry-compatible traces. Observability should therefore be part of the design from day one. A traced request path might look like this:

User Request
    |
    v
Agent
    |
    | traceparent
    v
MCP Client
    |
    | traceparent
    v
MCP Gateway
    |
    v
MCP Server
    |
    v
Database / Queue / API

The resulting trace can show the whole causal chain and not a set of unrelated HTTP transactions.

Extensions become first-class

Extensions also get formal status. They already existed, yet nothing resembled a mature lifecycle on a par with a core protocol track. Now each extension has a reverse-DNS identifier, explicit capability negotiation, its own repository, independent versioning and a dedicated track within the SEP process.

The reason is that MCP is expanding quicker than a single monolithic protocol version can evolve safely. Supporting Tasks should not oblige every implementation to run asynchronous distributed jobs, and a minimal command-line server should not be forced to adopt MCP Apps. Larger standards ecosystems follow the same reflex of keeping the core small and stable and shifting specialized features into modules that change on their own schedule.

Two extensions get top billing in this release. MCP Apps adds server-rendered interactive UI inside a sandboxed iframe, and Tasks adds asynchronous execution. The resulting hierarchy looks like this:

                    MCP
                     |
          +----------+----------+
          |                     |
       Core Protocol        Extensions
          |                     |
      Stateless HTTP      +-----+------+
                          |            |
                       Tasks       MCP Apps

It gives MCP a far more sustainable way to evolve.

Deprecations and upgrading

The release introduces a formal feature lifecycle on purpose. The changelog defines Active, Deprecated and Removed states and sets a minimum twelve-month deprecation window, so application developers get a predictable migration period and not emergency changes whenever the protocol moves.

Roots, Sampling and Logging are on the deprecation list. Official migration advice replaces Roots with explicit tool parameters, resource URIs or server configuration, replaces Sampling with direct LLM-provider integrations, and replaces Logging with stderr or OpenTelemetry. The older HTTP+SSE transport is deprecated as well, and Streamable HTTP is the preferred option.

The experimental Tasks API from 2025-11-25 marks another migration boundary. It now lives in the io.modelcontextprotocol/tasks extension with a reworked lifecycle based on tasks/get, tasks/update and tasks/cancel. tasks/list is gone because listing tasks without a session raises scoping and privacy concerns.

For an organization migrating an existing MCP fleet, the safest order runs like this:

  1. Upgrade the SDK, then search for code that relies on Mcp-Session-Id. Look closely at dictionaries keyed by session, connection registries and sticky-session routing rules built around the old model.
  2. Separate protocol state from application state, giving anything that represents real business state an explicit identifier and moving it into a suitably durable store.
  3. Update the gateway configuration to understand the new MCP headers, and give cacheable list responses explicit TTL and scope policies, especially since public versus private caching has security consequences.
  4. Evaluate long-running workflows for Tasks adoption rather than implementing them as indefinitely open HTTP requests.

The current TypeScript SDK migration documentation shows the transitional reality as well. Implementations can support the new protocol while keeping compatibility machinery for clients from the previous era, so migration can be incremental and doesn't need one ecosystem-wide cutover.

What this means for MCP architecture

The most profound consequence is that MCP now keeps apart four concerns that used to blur together:

  • The protocol layer owns the meaning of messages.
  • The transport layer moves requests over HTTP or stdio.
  • The application layer holds state, authorization context, workflows and what identifies the business object.
  • The extension layer supplies optional features such as asynchronous Tasks and MCP Apps.

The layering looks like this:

                 +----------------------------+
                 |        MCP Protocol        |
                 | JSON-RPC + request model   |
                 +-------------+--------------+
                               |
                 +-------------v--------------+
                 |         Transport          |
                 | stdio / Streamable HTTP    |
                 +-------------+--------------+
                               |
                 +-------------v--------------+
                 |      Application Layer     |
                 | explicit IDs + databases   |
                 +-------------+--------------+
                               |
                 +-------------v--------------+
                 |         Extensions         |
                 | Tasks / MCP Apps / others   |
                 +----------------------------+

This scales far better than cramming everything into one connection-scoped session abstraction, and it brings MCP into line with long-established distributed-systems practice: stateless request handling, durable external state, explicit identifiers, cache semantics, idempotent retries, asynchronous job handles and distributed tracing.

The trade-offs

There are costs. The main benefit is horizontal scalability, and the price is that managing state becomes the application's job. Developers have to settle how state handles are created, how long they live, where they are kept, how access is authorized and how stale ones get cleaned up.

Since client capabilities and metadata move with every request, payloads grow slightly. The extra bytes are normally small, yet very high-throughput systems should avoid repeating capability payloads needlessly.

MRTR makes stateless HTTP possible for exchanges that once needed server-initiated requests, though it exposes the interaction's state machine more openly. A client has to recognize input_required, hold on to the request state, gather the answers and retry properly.

Tasks take care of long-running execution, at the price of a task store, which is one more durable subsystem. That is no defect, just explicit state standing in for hidden protocol state.

Caching improves efficiency, but wrong cacheScope values can create security problems. A response wrongly labeled "public" could become shareable across authorization contexts, and the caching guidance warns that cache scope doesn't replace primitive-level authorization.

The stateless core shouldn't be read as less architecture. It's better described as responsibility distributed more sensibly.

The conceptual shift

Asked where a logical interaction lives, the old model implicitly said it lived inside the MCP session tied to the connection. The new model asks instead which state the operation really needs, and the answer is spelled out: a domain identifier, a task identifier, an authorization context or a request-state token needed to carry on. That abstraction scales much better.

Transactions have transaction IDs, Kubernetes workflows have job IDs, shopping carts have cart IDs, CI pipelines have run IDs and training jobs have execution IDs. MCP applications can now use the same vocabulary.

What the release adds up to

This release is more than protocol cleanup. It shifts MCP from connection-oriented agent integration toward cloud-native distributed systems, and the pieces add up as follows:

  • Dropping Mcp-Session-Id ends protocol-level affinity.
  • Dropping the handshake makes each request a self-contained exchange.
  • server/discover offers capability discovery with no session semantics attached.
  • Mcp-Method and Mcp-Name expose MCP traffic to everyday HTTP tooling.
  • ttlMs and cacheScope give clients and gateways a shared vocabulary for freshness and sharing.
  • MRTR abandons the idea that a server asking the client something requires a connection that never closes.
  • Tasks are a durable abstraction for work that outlasts a single HTTP request.
  • Explicit handles lift business state out of hidden transport metadata and into the application's own data model.
  • Extensions stop the core from bloating while letting the ecosystem move fast.
  • Tighter authorization matches how real OAuth and OIDC deployments behave.
  • Trace-context conventions from OpenTelemetry make distributed MCP systems easier to observe.
  • A formal deprecation policy gives the ecosystem a predictable way to change.

The stateless model makes MCP dramatically easier to implement, and the independent developer's explorer and Datasette work show that the simplicity turns into usable tooling. His description of the old protocol as requiring session initialization and the new one as a single self-contained request is the operational change the maintainers were aiming for.

The takeaway for architects is that MCP did not remove state. What it removed was hidden state owned by the protocol, swapping it for explicit, composable state owned by the application. That change lets an MCP server go from this pairing:

sticky sessions
      +
shared session store
      +
long-lived connections
      +
connection affinity

and toward this one:

stateless request handling
      +
ordinary load balancing
      +
external durable state
      +
explicit handles
      +
asynchronous task primitives

For enterprise deployments this is a big gain, since it suits Kubernetes, autoscaling, serverless execution, API gateways, zero-downtime rollouts, multi-region routing, distributed tracing and large multi-tenant platforms far better. Put simply, the specification changes MCP from a standard for how agents call tools into infrastructure that fits naturally inside modern distributed AI systems.