Somanath StudioTalk to an Engineer
Back to Writing
11 min read
MCP 2026-07-28stateless MCP serverMCP migrationAI SaaS architecture

MCP 2026 Is Stateless: A Migration Guide for SaaS Teams

A stateless MCP request routed across interchangeable servers with cache and authorization controls

Remote MCP servers just became much easier to operate like ordinary production web services.

The final Model Context Protocol specification dated 2026-07-28 removes the protocol-level handshake and session, makes each request self-describing, adds HTTP headers for routing, allows clients to cache list responses and strengthens authorization. It also changes how tools pause for user input and moves long-running Tasks into an extension.

The official MCP 2026-07-28 release announcement calls this a stateless protocol core. For a SaaS team, the practical change is that a request can land on any healthy server instance without sticky routing or a shared store just to preserve MCP transport state.

That does not make the product itself stateless. A support agent still needs to know which workspace it serves. A research job may still run for minutes. A billing tool still needs authorization and an audit trail.

The architecture decision is now more explicit: keep the transport disposable, and represent durable product state as data your application can identify, authorize and recover.

What Changed in MCP 2026-07-28

The previous remote protocol expected an initialize exchange and could associate later traffic with an Mcp-Session-Id. That model worked, but it made a horizontally scaled server behave differently from a normal request-response API.

In the new revision:

  • initialize and initialized are retired for the modern protocol era.
  • Mcp-Session-Id is removed.
  • Each request carries its protocol version, client identity and capabilities in _meta.
  • Clients can optionally call server/discover before making normal requests.
  • Streamable HTTP uses a separate POST for each JSON-RPC request or notification.
  • Server-Sent Events can still stream progress and the final response, but only for that request.

The updated Streamable HTTP specification is important here: “stateless” describes the protocol transport, not a ban on databases, queues, workflows or application state.

This is good news for production operations. A standard load balancer can distribute calls across instances. A failed instance does not own an invisible client session. Rolling deployments do not need every connection to remain attached to the old process.

But teams that stored product state behind the session ID now have migration work.

Replace Hidden Session State With Explicit Handles

Suppose an MCP tool starts a multi-step import. The old implementation might store the selected workspace, uploaded file and current step under a protocol session:

sessionStore.set(sessionId, {
  workspaceId,
  importId,
  currentStep: "mapping",
});

Later tool calls read the current import from that hidden session. This is convenient until the next call reaches a different instance, the session expires or two workflows run through the same connection.

The new design should return an explicit application handle:

type ImportHandle = {
  importId: string;
};

async function startImport(
  workspaceId: string,
  fileId: string,
): Promise<ImportHandle> {
  const job = await imports.create({
    workspaceId,
    fileId,
    status: "mapping",
  });

  return { importId: job.id };
}

async function confirmMapping(
  workspaceId: string,
  importId: string,
) {
  const job = await imports.getForWorkspace(importId, workspaceId);
  if (!job) throw new Error("Import not found");

  return imports.advance(job.id);
}

The model or client passes importId into the next tool. The server derives workspaceId from the authenticated request, not from the model’s arguments, and checks that the handle belongs to that workspace.

That pattern gives you several production properties at once:

  • Any server instance can resume the work.
  • The handle can be logged and traced without logging user content.
  • Authorization is checked on every call.
  • Two imports do not collide inside one transport session.
  • A worker or human operator can inspect the durable record.
  • Expiry and deletion become application policies instead of connection side effects.

Use opaque, high-entropy handles. Do not expose a sequential database ID that lets a caller guess another tenant’s workflow. Do not treat possession of the handle as authorization.

Header-Based Routing Helps, but It Is Not Authorization

Modern Streamable HTTP requests include Mcp-Method and, where relevant, Mcp-Name. A gateway can route, meter or apply coarse policies without parsing a JSON body.

That creates useful operational controls. You might send tools/list to a read-optimized pool, rate-limit an expensive tools/call, or block a retired tool name at the edge.

However, these headers are client input. A caller can lie about them.

Your MCP server must still verify that the HTTP header agrees with the JSON-RPC body, validate the access token, authorize the actual tool and validate its arguments. Treat the header as a routing hint that must be consistent—not as proof of identity or permission.

A practical gateway policy can separate three concerns:

  1. Edge controls: request size, rate limits, allowed methods and basic header consistency.
  2. Identity controls: token validation, issuer, audience, expiry and scopes.
  3. Application controls: tenant membership, record ownership and action-specific permission.

This is the same “boring” layering that makes a conventional API reliable. The principles in Why I Prefer Boring Architecture for SaaS apply directly: predictable boundaries are more valuable than clever transport state.

Cache Tool Lists Without Crossing Permission Boundaries

The new specification lets tools/list, prompts/list, resources/list and resources/read responses include a time-to-live and cache scope. Tool lists should also use deterministic ordering so repeated catalogs stay stable.

The current MCP tools specification says the visible tool set may vary with the authorization on each request. That makes cache design a security decision, not only a performance optimization.

For example, an administrator may see delete_workspace while a support viewer does not. If a shared cache stores the administrator’s tool list under only the server URL, the next viewer could receive a tool they should never have been shown.

Build the cache key from the dimensions that actually affect visibility:

const toolCatalogKey = [
  serverId,
  protocolVersion,
  tenantPolicyVersion,
  authenticatedRole,
  grantedScopeHash,
].join(":");

The exact dimensions depend on your authorization model. The safe rule is simple: if two callers can receive different tool catalogs, they cannot share the same cached entry.

Keep the catalog deterministic too. Random ordering or volatile descriptions invalidate upstream prompt caches and make agent behavior harder to compare between runs. Version tool descriptions deliberately, avoid embedding timestamps and test that unchanged policy produces byte-stable output.

Rework Pauses and Long-Running Tools Deliberately

The stateless core cannot rely on a server pushing an unrelated request back through a permanently open connection.

Multi Round-Trip Requests solve the “I need one more answer” case. A tool can return input_required with one or more input requests. The client collects the user’s response and retries the original call with those answers attached.

This is a good fit for:

  • Confirming a destructive action
  • Asking the user to choose between ambiguous records
  • Collecting a missing required field
  • Requesting approval before sending an external message

Design the retry as an idempotent continuation. The initial attempt may already have read data or reserved capacity. The retried call must not create a duplicate order, ticket or payment because the client repeated the request.

Longer work belongs in the Tasks extension. A server can return a task handle for work that should be polled, updated or cancelled. Store task status in a durable database or queue, bind it to the tenant and make cancellation best-effort but observable.

Do not turn every slow API call into a task. If a request normally finishes in a few seconds and streaming progress is enough, request-scoped streaming is simpler. Use a task when the work must survive disconnects, outlive a single request or support explicit later control.

These protocol mechanics do not replace product evaluation. Before giving a tool more autonomy, use the failure-mode and permission checks in How to Evaluate AI Agents Before Production.

Authorization Needs Its Own Migration Track

The release hardens MCP authorization in several ways. Clients must validate the authorization server issuer, credentials must stay bound to the issuer that created them, and Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents.

The full 2026-07-28 authorization specification also retains core protections such as PKCE, protected resource metadata, token audience validation and a prohibition on token passthrough.

For a SaaS team, this means “OAuth works in our demo” is not a sufficient migration result. Test:

  • A token issued for server A is rejected by server B.
  • A token from an unexpected issuer is rejected.
  • The MCP server never forwards the client’s token to an upstream API.
  • Desktop and CLI redirect URIs follow the correct application type rules.
  • Scopes map to actual tool permissions.
  • Refresh tokens and client credentials are encrypted and never logged.
  • Revoked access stops working on every server instance.

Keep authorization changes separate from transport changes in your rollout metrics. If both ship behind one switch, a failed request becomes difficult to classify: protocol negotiation, issuer validation, scope mapping and tool execution all look like “MCP is broken.”

A Safe Migration Plan for a SaaS MCP Server

Do not flip every client and server at once. Treat this as a compatibility migration.

1. Inventory state and protocol dependencies

Search for Mcp-Session-Id, initialize, server-initiated requests, standalone GET streams, Tasks methods and data keyed by a connection or session.

For every match, label it as transport bookkeeping, product state, authorization state or long-running work. Only the first category should disappear. The others need a durable home.

2. Record the versions in production

Log the negotiated protocol version, SDK version, client name and server release for every request. Keep sensitive content out of the log.

Without version telemetry, you cannot know which clients are ready or whether a fallback hides an incomplete migration.

3. Upgrade SDKs before changing behavior

Upgrade in a test branch, fix compile-time changes and keep the legacy protocol path working. The official TypeScript SDK migration guide notes that v2 does not send the modern revision by default for hand-constructed clients and servers; negotiation is an explicit choice.

That is useful for a staged rollout. First deploy code that understands both eras. Then enable automatic negotiation for an internal client. Pin the modern version only after you want incompatibility to fail loudly.

4. Externalize application state

Replace transport-session lookups with explicit, authorized handles. Add expiry, ownership checks, idempotency keys and cleanup jobs.

Restart an instance halfway through every multi-step test. If the workflow cannot continue on another instance, state is still hidden somewhere.

5. Add gateway and cache controls

Validate header-body agreement. Add per-tool rate limits where cost or risk justifies them. Partition list caches by every permission dimension and test role changes immediately invalidate the old view.

6. Migrate interactive and background flows

Use Multi Round-Trip Requests for short user decisions and Tasks for durable background work. Define timeout, retry, cancellation and duplicate-delivery behavior for both.

7. Test compatibility as a matrix

Run at least these combinations:

  • Legacy client → dual-era server
  • Modern client → dual-era server
  • Auto-negotiating client → legacy-only server
  • Modern-only client → legacy-only server, expecting a clear failure
  • Modern client → multiple server instances during a rolling deploy

Add unauthorized, expired-token, malformed-header and cross-tenant-handle cases. Load-test list caching and long-running tools separately.

8. Roll out with a rollback boundary

Enable the modern era for internal traffic, then a small customer cohort. Compare protocol errors, authorization failures, tool completion, duplicate side effects, cache hit rate and p95 latency.

Keep legacy support until client telemetry shows it is genuinely unused and the specification’s deprecation window fits your customer commitments.

MCP 2026 Migration Checklist

  • [ ] Inventory every use of sessions, initialization and server-initiated requests.
  • [ ] Separate transport state from durable product state.
  • [ ] Return opaque application handles for multi-step workflows.
  • [ ] Authorize every handle against the authenticated tenant.
  • [ ] Make retried tool calls idempotent.
  • [ ] Log negotiated protocol and SDK versions without user content.
  • [ ] Validate Mcp-Method and Mcp-Name against the JSON-RPC body.
  • [ ] Partition tool-list caches by role, scope and tenant policy.
  • [ ] Keep catalog ordering and descriptions deterministic.
  • [ ] Test issuer, audience, PKCE, scope and token-passthrough rules.
  • [ ] Use MRTR for short input or approval pauses.
  • [ ] Use Tasks only for durable, controllable long-running work.
  • [ ] Test modern and legacy client-server combinations.
  • [ ] Restart instances during active workflows.
  • [ ] Roll out by cohort with a tested legacy fallback.

Stateless Transport, Explicit Product State

MCP 2026-07-28 is a substantial production architecture improvement. Ordinary load balancing, request-level streaming, gateway routing and cacheable catalogs are easier to reason about than invisible state attached to a transport session.

The migration is not “delete the session store.” It is “move every important state transition into an explicit application model.”

If a workflow matters, give it a durable record. If a handle crosses a request boundary, authorize it again. If a catalog varies by permissions, partition the cache. If a tool can be retried, make its side effects idempotent.

Teams starting from scratch can use the first MCP server guide for product scoping, then adopt the stateless model from day one. If you are turning an agent prototype into a customer-facing system, the AI SaaS development service covers the broader architecture, evaluation and production-readiness work around the protocol.

Working on a SaaS that's starting to feel fragile?

Talk to an engineer about the parts that break first — without rewriting what already works. We'll recommend focused support or a compact team based on your scope.

Talk to an Engineer