The part of MCP 2026-07-28 I would worry about first is not the missing handshake. It is whatever the application currently hides behind that handshake: workspace selection, credentials, pending approvals, or a worker-local map that quietly assumes the next request will reach the same process.
The July 28, 2026 release removes protocol-level sessions and the required initialize / notifications/initialized exchange. Mcp-Session-Id disappears, and requests carry their own protocol context. It also introduces Multi Round-Trip Requests (MRTR), routing headers, cache hints, authorization changes, extensions, and a formal deprecation lifecycle. The specification changelog is the reference for those changes.
I would treat this as an infrastructure migration, not an SDK dependency bump. Stateless transport makes distribution easier; it does not make application state durable, retries safe, or authorization boundaries correct.
Start With the Dependencies, Not the SDK
A local stdio server with one-shot tools is relatively low risk: upgrade the SDK and test negotiation. A remote HTTP server without cross-request state still needs metadata, discovery, and header work. Session-backed business state, mid-call approval flows, OAuth registration, legacy HTTP+SSE, and experimental Tasks deserve separate migration workstreams. Gateways that inspect JSON-RPC bodies also need attention.
My first pass would search clients, servers, deployment configuration, and gateway policies for these identifiers:
Mcp-Session-Id initialize notifications/initialized sessionId ctx.sessionId extra.sessionId
sticky_session sticky-session elicitation/create sampling/createMessage roots/list
resources/subscribe resources/unsubscribe logging/setLevel tasks/result tasks/list Last-Event-ID
For each match, establish what breaks when the next request reaches another instance. Does initialization gate execution? Does a session select a user, credential, workspace, or conversation? Are tool catalogs connection-dependent? Does reconnect logic assume SSE message redelivery? Are OAuth credentials stored without their issuer? Which deprecated methods still carry production traffic?
I would also inspect side-effect ordering: a tool that writes externally and then asks for approval already has a correctness problem. Removing Mcp-Session-Id while retaining worker-local business state merely turns a visible dependency into an intermittent production failure.
Make Workflow State Explicit
There are three useful patterns here, and they solve different problems. Explicit handles expose dependencies in the tool contract. Shared storage makes state available across workers and restarts. Protected requestState carries continuation information for MRTR.
For a workspace workflow, return a server-minted handle and require it in subsequent arguments. These are the result and later call parameters, respectively:
{"resultType":"complete","content":[{"type":"text","text":"Workspace created."}],"structuredContent":{"workspaceHandle":"ws_7f93a2"}}
{"name":"update_workspace","arguments":{"workspaceHandle":"ws_7f93a2","status":"approved"}}
A handle makes the dependency visible, but the backing state still needs an appropriate home. Use a database, distributed cache, object store, or durable task system when multiple workers need access, workflows must survive restarts, state is too large to carry directly, work outlives a request, or transactional and single-use behavior matters. Any compatible instance should be able to process the subsequent call.
For MRTR, remember that opaque does not mean trustworthy. The client receives requestState and returns it later. Protect it with an HMAC or authenticated encryption, binding it to the authenticated principal, original operation, important parameters, and expiration. Include a nonce where replay protection is required. I would never accept unsigned continuation state merely because it resembles something the server previously emitted.
Negotiate Once, Carry Context on Every Request
Modern requests include protocol context in _meta; they must not depend on an earlier initialization exchange. A Streamable HTTP tool call can carry both HTTP routing information and JSON-RPC metadata:
POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json
Authorization: Bearer
{"jsonrpc":"2.0","id":"req-101","method":"tools/call","params":{"name":"search","arguments":{"query":"stateless MCP migration"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"example-client","version":"2.0.0"},"io.modelcontextprotocol/clientCapabilities":{"elicitation":{}}}}}
Servers targeting this version must implement server/discover, advertising supported versions, capabilities, and server identity. Calling discovery is optional for clients; it is not a replacement mandatory handshake before every operation. It is useful for selecting the modern protocol or determining whether legacy fallback is needed.
TypeScript v2 Still Requires Opt-In
Upgrading the TypeScript SDK does not automatically enable the new protocol. With the v2 SDK, explicitly configure automatic version negotiation when constructing the client; the following assumes Client and transport are already supplied by the integration:
const client = new Client(
{ name: "my-client", version: "1.0.0" },
{ versionNegotiation: { mode: "auto" } }
);
await client.connect(transport);
Automatic mode probes with server/discover and can fall back to legacy initialization. For @modelcontextprotocol/sdk v1 deployments, start with the v1-to-v2 migration guide. Existing v2 deployments should use the separate 2026-07-28 support guide.
Treat MRTR as a Retry Contract
Previously, a server could initiate requests such as elicitation/create, sampling/createMessage, or roots/list. MRTR replaces that interaction model: the server answers the original request with resultType: "input_required", the client collects input, and the client retries the original operation with a new JSON-RPC ID, inputResponses, and the returned requestState. The server either completes the operation or requests another round.
For example, an approval response and the subsequent retry look like this. The retry illustrates the MRTR fields; modern per-request metadata and HTTP headers still apply.
{"jsonrpc":"2.0","id":"delete-1","result":{"resultType":"input_required","inputRequests":{"confirm_delete":{"method":"elicitation/create","params":{"mode":"form","message":"Delete project project_123?","requestedSchema":{"type":"object","properties":{"confirmed":{"type":"boolean"}},"required":["confirmed"]}}}},"requestState":"protected-expiring-state"}}
{"jsonrpc":"2.0","id":"delete-2","method":"tools/call","params":{"name":"delete_project","arguments":{"projectId":"project_123"},"inputResponses":{"confirm_delete":{"action":"accept","content":{"confirmed":true}}},"requestState":"protected-expiring-state"}}
The placeholder state above represents a protected, expiring value, not a production token format. Before shipping this flow, define maximum rounds, expiry, cancellation, rejection, response-schema validation, and behavior when a client lacks the requested capability. Recheck authorization on every retry. Clients must handle resultType, preserve continuation state, and either support MRTR or explicitly reject it.
Replay protection and idempotency are separate concerns. Valid continuation state must not allow a purchase, deletion, credit deduction, or external write to happen twice. Stage the operation or use an idempotency key, and do not perform the irreversible action before returning input_required. The MRTR specification defines the interaction contract; the application still owns side-effect correctness.
Review Routing, Caching, and OAuth Together
MCP-Protocol-Version, Mcp-Method, and Mcp-Name let HTTP infrastructure classify requests without parsing every body. That supports tool-specific limits, separate list and execution policies, expensive-tool worker pools, high-risk operation restrictions, and per-tool latency, error, and cost accounting. But those headers are client-controlled. Validate them against the JSON-RPC body before applying policy, and reject and log mismatches. A caller must not advertise a harmless tool while invoking a privileged one.
Cacheable results now include ttlMs and cacheScope for tools/list, prompts/list, resources/list, resources/templates/list, and resources/read. I would normally include protocol version, server identity, method, request parameters, principal or tenant, authorization scope, cacheScope, and server configuration version in the cache key. A live TTL never justifies sharing a private result across authorization boundaries. Return deterministic lists too: stable tool ordering avoids unnecessary cache misses and may improve model prompt-cache reuse when definitions are included in prompts.
For OAuth, validate a returned iss against the issuer recorded for the authorization flow. Store client credentials by issuer and never reuse them with a different authorization server. Set an appropriate application_type during Dynamic Client Registration, and prepare new integrations for Client ID Metadata Documents (CIMD). DCR remains available for backward compatibility, but is deprecated as the preferred registration approach.
Move Streams and Long-Running Work Deliberately
subscriptions/listen replaces the older HTTP GET notification path and resources/subscribe / resources/unsubscribe flow. Clients open a long-lived POST-response stream and opt into notification categories. Request-specific progress and log notifications remain on the response stream of the request they describe. Stateless request processing does not eliminate long-lived notification connections; multi-instance deployments still need a shared event bus when an event originates on a different worker from the subscriber.
Long-running work moves from experimental core Tasks into the io.modelcontextprotocol/tasks extension. Use tasks/get for polling, tasks/update for client-to-server updates, durable task handles, and subscriptions/listen for opted-in updates. Do not carry the older tasks/result and tasks/list patterns into the new implementation.
The deprecation plan should be explicit: replace Roots with tool arguments, resource URIs, or configuration; replace Sampling with direct model-provider integration; use stderr for stdio logging or OpenTelemetry in production; move DCR integrations toward CIMD; and migrate legacy HTTP+SSE to Streamable HTTP. For deprecated includeContext values, omit the field or use "none". Deprecation is not immediate removal: the lifecycle provides a minimum twelve-month window, not a shared confirmed removal date. Check the deprecated-features registry before setting deadlines.
Keep Model Access Separate
MCP connects agents to tools, resources, prompts, approvals, and tasks. It does not standardize model pricing, provider credentials, inference endpoints, or provider failover. When replacing Sampling, I would keep inference behind a separate application interface. A unified multi-model API such as CometAPI can serve that layer through an OpenAI-compatible endpoint when centralized provider access, credentials, usage, and billing are genuinely useful. The MCP server still owns its tool contracts, authorization, state, and result handling; application orchestration decides when to invoke models and tools.
Canary the Complete Workflow
My rollout order would be: inventory versions, SDKs, sessions, SSE usage, DCR clients, and deprecated methods; upgrade SDKs outside production; add discovery and negotiation; replace hidden state; secure MRTR; introduce validated headers and scoped caches; test issuer boundaries; then canary modern and legacy paths together. Remove unnecessary sticky routing only after exercising workflows across multiple instances, and keep a rollback path throughout the canary.
The compatibility expectations should be unambiguous. Modern clients and modern servers use MCP 2026-07-28. Modern clients probe legacy servers and fall back where supported. Legacy clients continue through a dual-version server's legacy path. Unsupported combinations return a clear protocol-version error. A specification release does not mean every client, server, SDK, and hosted platform changes simultaneously.
Record protocol version per request, discovery success and fallback rates, missing or invalid headers, and header/body mismatches. For MRTR, measure requests, completions, rejections, timeouts, state-verification failures, and duplicate-operation prevention. Track cache hits by scope, issuer-validation failures, remaining HTTP+SSE and deprecated-method traffic, tool latency, and accepted-task rate. Those signals tell you whether retirement is justified.
My release criterion would not be “one-shot tools/call works.” It would be that a workflow can move between instances, collect approval, reject expired or replayed state, preserve authorization boundaries, and complete without duplicating an external write. That is the practical difference between removing protocol sessions and actually being ready to operate without them.
Originally published at cometapi.com
Top comments (0)