MCP 2026-07-28 Migration Checklist for TypeScript Servers

MCP 2026-07-28 Migration Checklist for TypeScript Servers

Aident AI

Developer workstation representing a dual-era MCP protocol migration.

MCP 2026-07-28 Migration Checklist for TypeScript Servers

To prepare a TypeScript MCP server for the 2026-07-28 protocol revision, install the v2 beta packages, opt into the new protocol era explicitly, replace session-bound state with verified per-request state, move server-to-client requests into input_required results, and test both modern and 2025-era clients before rollout. Do not remove your legacy path until production traffic shows that old clients have drained.

The July 28 revision is not simply another rename of the old HTTP+SSE transport. It changes the protocol core from session-oriented exchanges to per-request exchanges. The official TypeScript SDK can serve both eras from one endpoint, so the safest migration is an expand-and-contract rollout instead of a flag-day cutover.

This guide is current for the release candidate and TypeScript SDK beta available on July 23, 2026. Beta APIs can still change before the final specification lands.

What Actually Changes on July 28

The most important operational change is that the 2026-07-28 core is stateless between requests. A modern HTTP request does not carry Mcp-Session-Id, and a server should not assume that the next request reaches the same process.

Area

2025-era behavior

2026-07-28 behavior

HTTP server entry

StreamableHTTPServerTransport

createMcpHandler(factory)

Client handshake

initialize

server/discover, when the client opts in

Request state

Often stored by session ID

Returned as an opaque requestState and echoed on retry

Server-to-client requests

Push-style elicitation, sampling, and roots calls

Return inputRequired(...); the client fulfills and retries

Change events

Unsolicited notifications

A client-opened subscriptions/listen stream

HTTP cancellation

POST notifications/cancelled

Close the request's SSE response stream

Cache metadata

Optional or absent

ttlMs and cacheScope, with conservative SDK defaults

Upgrading an SDK package does not silently put 2026-07-28 messages on the wire. Hand-constructed Client, Server, and McpServer instances continue to use 2025-era behavior until you select a modern serving entry or client negotiation mode.

Prerequisites

Before changing code, collect:

  • every MCP transport your server exposes, including stdio, old HTTP+SSE, and Streamable HTTP;

  • every client and gateway version that calls the server;

  • every place that reads a session ID or stores per-session state;

  • every handler that asks the client for elicitation, sampling, roots, or other follow-up input;

  • current request success rate, latency, protocol version, and error-code baselines;

  • a rollback route that can continue serving 2025-era clients.

If you still run the older separate HTTP+SSE transport, migrate that endpoint to Streamable HTTP first. That transport cleanup is separate from adopting the 2026-07-28 protocol era.

Step 1: Install the TypeScript v2 Betas

The MCP project published separate beta packages for servers and clients:

pnpm add @modelcontextprotocol/server@beta
pnpm add @modelcontextprotocol/client@beta

If your code is still on @modelcontextprotocol/sdk v1, follow the official v1-to-v2 guide before applying the 2026-specific changes. Do the package upgrade on a branch and pin the resolved versions in your lockfile. A floating beta tag can move while you are testing.

Expected result: the server, client, and framework adapter imports resolve from the v2 packages, while existing tests still exercise 2025-era behavior by default.

Step 2: Serve Both Protocol Eras Over HTTP

For a stateless Streamable HTTP server, replace the per-request transport setup with createMcpHandler(factory):

import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';

const handler = createMcpHandler(() => {
  const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });

  // Register tools, resources, and prompts here.
  return server;
});

export default handler;

By default, this handler serves modern requests per request and also supports stateless 2025-era traffic. For Node frameworks, wrap it once with toNodeHandler(handler) from @modelcontextprotocol/node.

If your existing 2025 server is sessionful, keep it in front of a strict modern handler during the transition:

const modern = createMcpHandler(buildServer, { legacy: 'reject' });

export default {
  async fetch(request: Request) {
    if (await isLegacyRequest(request)) {
      return existingLegacyHandler(request);
    }

    return modern.fetch(request);
  },
};

Expected result: a modern request reaches the new per-request handler, while a request without a modern envelope continues through the known legacy path. Do not route based only on a user-agent string or a guessed SDK version.

Step 3: Opt Clients Into Negotiation

The TypeScript v2 client keeps the 2025 handshake by default. Use automatic negotiation while you still need compatibility:

const client = new Client({ name: 'my-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });

await client.connect(transport);
const era = client.getProtocolEra(); // "modern" or "legacy"

mode: "auto" probes with server/discover and falls back to the 2025 handshake when it positively identifies a legacy server. Use { pin: "2026-07-28" } only in a modern-only test or after your compatibility gate passes; the connection rejects against a 2025-only server.

HTTP probe timeouts are real connection failures, not evidence that a server is legacy. On stdio, the official transport uses a disposable sibling process for the probe because some old servers exit on a request sent before initialize.

Expected result: your test matrix records both modern and legacy outcomes. Network failures remain failures instead of being silently mislabeled as compatibility fallbacks.

Step 4: Replace Session State With Verified Request State

Search for Mcp-Session-Id, ctx.sessionId, extra.sessionId, and any map keyed by those values. Modern requests have no session ID.

For a multi-round operation, return an opaque requestState with inputRequired(...). The client echoes it byte-for-byte on the next round, and the handler reads the verified value through ctx.mcpReq.requestState().

Treat that value as untrusted input. The SDK's createRequestStateCodec helper signs a JSON-serializable payload with HMAC-SHA256, but does not encrypt it. Bind the state to the authenticated principal, original method and parameters, and an expiry. Reject expired or invalid state before the handler continues.

Expected result: any process can handle the next request without consulting in-memory session state, and a modified, expired, or cross-user state value is rejected.

Step 5: Convert Push Requests to input_required

The modern protocol removes the server-to-client JSON-RPC request channel. Replace calls such as ctx.mcpReq.elicitInput(...) and requestSampling(...) with an inputRequired(...) result from the original handler. On retry, read the response with the schema-aware acceptedContent(...) overload or the discriminated inputResponse(...) helper.

Write the handler once in the new style. The TypeScript SDK's legacy shim translates it into real server-to-client requests for compatible 2025-era stdio or sessionful connections. Stateless legacy HTTP cannot provide that return channel, so interactive flows need an explicitly supported streaming or sessionful path during the transition.

Expected result: the same handler completes a multi-round operation on a modern client and on each legacy transport you promise to support. Declined, malformed, expired, and too-many-rounds cases fail predictably.

Step 6: Update Notifications, Headers, and Caching

Audit three less-visible surfaces:

  1. Subscriptions: modern clients receive tools, prompts, and resources changes through subscriptions/listen, not unsolicited list_changed or resources/updated notifications.

  2. HTTP headers: modern Streamable HTTP validates MCP-Protocol-Version, Mcp-Method, and Mcp-Name against the message body. Tool arguments marked with x-mcp-header are mirrored into Mcp-Param-* headers.

  3. Cache policy: cacheable modern results include ttlMs and cacheScope. The SDK defaults to ttlMs: 0 and cacheScope: "private", which is safe but disables useful caching until you choose a policy.

Update reverse-proxy and CORS allowlists for the standard headers before enabling modern browser clients. A proxy that drops or rewrites them can create a 400 response with JSON-RPC error -32020 even when the request body is valid.

Step 7: Run a Dual-Era Test Matrix

Test at least these combinations:

Client

Server

Expected behavior

2025-only

dual-era

Legacy handshake and successful tool call

auto

2025-only

Probe, legacy fallback, successful tool call

auto

dual-era

Modern negotiation and successful tool call

2026-pinned

dual-era

Modern negotiation and successful tool call

2026-pinned

2025-only

Typed era-negotiation failure

For in-process HTTP tests, send a StreamableHTTPClientTransport through handler.fetch(new Request(...)). The in-memory linked transport exercises only 2025-era instances. For stdio coverage, run serveStdio(() => buildServer()) as a child process.

Also test:

  • invalid and expired requestState;

  • missing or mismatched MCP headers;

  • cancellation during a streamed response;

  • a proxy or load balancer sending consecutive rounds to different instances;

  • subscription reconnect after an unexpected close;

  • cache separation between principals;

  • legacy traffic during rollback.

Common Migration Failures

The Server Upgraded but Still Speaks the Old Revision

Installing v2 packages is not enough. Use createMcpHandler for HTTP or serveStdio(factory) for stdio, and opt the client into versionNegotiation.

Modern Requests Lose Their State

The handler still depends on an in-memory session map. Move only the minimum continuation data into integrity-protected requestState, or store durable state behind an identifier that is also bound to the principal and expiry.

Requests Fail With Header Mismatch

Inspect the request at every proxy hop. Confirm that MCP-Protocol-Version, Mcp-Method, Mcp-Name, and any Mcp-Param-* headers reach the application unchanged and agree with the JSON-RPC body.

Browser Negotiation Falls Back Unexpectedly

Check CORS preflight rules. The SDK can treat an opaque browser CORS failure during the probe as a legacy fallback because many deployed servers predate the modern headers. Fix the allowlist before treating the fallback as a server capability result.

A Multi-Round Tool Hangs on Legacy HTTP

A stateless legacy HTTP request has no return path for server-to-client requests. Keep a sessionful or streaming legacy path for interactive tools, or require the modern protocol for that operation.

Roll Out Without a Flag Day

Use four gates:

  1. Deploy a dual-era server and keep the existing legacy route available.

  2. Enable mode: "auto" for an internal client cohort and compare success rate, latency, protocol errors, and fallback rate with the baseline.

  3. Expand the cohort only after modern tool calls, multi-round flows, cancellation, and subscriptions pass under real load.

  4. Pin modern clients, then remove the legacy path only after traffic proves that supported old clients have drained and rollback remains tested.

Log the negotiated protocol revision and stable error code, but never log authorization headers, request-state payloads, credentials, or sensitive tool arguments.

Where Aident Loadout Fits

If your actual goal is to give an agent a connected service rather than maintain a custom MCP server, compare the migration cost with using a managed Aident Loadout Action. Loadout lets an agent discover a narrow capability when needed and keeps provider credentials outside the prompt and model context.

Ask your coding agent:

Follow https://aident.ai/SETUP.md

Then connect one staging-safe integration and measure one outcome: the agent discovers the intended Action and completes one successful read-only call without loading an entire provider catalog into its prompt. For architecture tradeoffs, see Local vs Remote MCP Servers and How to Reduce MCP Token Usage in Claude Code and Codex.

Use Aident Loadout to run that measured compatibility check with a scoped integration.

Sources

Refresh this guide when the final 2026-07-28 specification or stable TypeScript v2 packages ship, or when the negotiation, dual-era serving, or request-state contracts change.

Home

Home

Home

Integrations

Integrations

Integrations

Vault

Vault

Vault

Audit

Audit

Audit

Arana Grande

Arana Grande

Arana Grande

Free

Free

Free

30-day audit summary

30-day audit summary

30-day audit summary

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

Daily action-call volume and the latest receipts from the Loadout audit trail.

View Audit

View Audit

View Audit

Loadout usage

Loadout usage

Loadout usage

617 action calls in the last 30 days

617 action calls in the last 30 days

617 action calls in the last 30 days

May 19 - Jun 17

May 19 - Jun 17

May 19 - Jun 17

10 active days

10 active days

10 active days

Less

Less

Less

More

More

More

Recent activity

Recent activity

Recent activity

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Latest action-call receipts from connected agents

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Apr 23, 09:23 AM

Shopify

Shopify

Shopify

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Creates Or Updates An Asset For A Theme

Success

Success

Success

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Apr 23, 09:21 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Success

Success

Success

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Apr 23, 08:53 AM

Shopify

Shopify

Shopify

Update Products Param Product Id

Update Products Param Product Id

Update Products Param Product Id

Failed

Failed

Failed

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Apr 22, 22:13 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Apr 22, 22:12 PM

Shopify

Shopify

Shopify

Create Product Image

Create Product Image

Create Product Image

Success

Success

Success

Connected integration coverage

Connected integration coverage

Connected integration coverage

162

162

162

of 753 accessible connected

of 753 accessible connected

of 753 accessible connected

Callable actions

Callable actions

Callable actions

1,126

1,126

1,126

Vault credentials

Vault credentials

Vault credentials

8

8

8

Explore what's possible

Explore what's possible

Explore what's possible

See all Integrations

See all Integrations

See all Integrations

Google Ads

Google Ads

Google Ads

All available Goolge Ads tools via...

All available Goolge Ads tools via...

All available Goolge Ads tools via...

X (twitter)

X (twitter)

X (twitter)

All available X tools via...

All available X tools via...

All available X tools via...

Github

Github

Github

All available Github tools via...

All available Github tools via...

All available Github tools via...

Notion

Notion

Notion

All available Notion tools via...

All available Notion tools via...

All available Notion tools via...

Slack

Slack

Slack

All available Slack tools via...

All available Slack tools via...

All available Slack tools via...

Firecrawl

Firecrawl

Firecrawl

All available Firecrawl tools via...

All available Firecrawl tools via...

All available Firecrawl tools via...

753 integrations are available for loadouts.

753 integrations are available for loadouts.

753 integrations are available for loadouts.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Plug your entire stack into your AI agents.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.

Skip the integration headache. Plug 750+ tools into Claude Code, Codex, and OpenClaw in one go, and let your agents execute today.