Claude Code

MCP 2026-07-28 Migration Guide: Stateless Protocol, No Sessions, server/discover, MRTR, and SDK v2 Upgrade

How do I migrate my MCP server or client to the 2026-07-28 Model Context Protocol specification?

MCP 2026-07-28 makes the protocol stateless. It removes the initialize handshake and the Mcp-Session-Id header, so every request carries its protocol version and client capabilities in _meta. Servers must implement server/discover and return server-to-client requests (elicitation, sampling, roots) as input_required results instead of sending their own requests. Upgrade to the v2 TypeScript packages (2.0.0) or Python mcp 2.0.0. Both can serve modern and 2025-era clients from one server.

What changed versus 2025-11-25

The official changelog lists these major changes since revision 2025-11-25.

  • Protocol-level sessions and the Mcp-Session-Id header are removed from Streamable HTTP. Servers that need state across calls mint explicit handles and pass them as ordinary tool arguments (SEP-2567).
  • The initialize / notifications/initialized handshake is removed. Every request carries io.modelcontextprotocol/protocolVersion and io.modelcontextprotocol/clientCapabilities in _meta. Clients SHOULD send io.modelcontextprotocol/clientInfo, and servers SHOULD return io.modelcontextprotocol/serverInfo in each result's _meta (SEP-2575).
  • New server/discover RPC. Servers MUST implement it to advertise supported versions, capabilities and identity.
  • The HTTP GET endpoint and resources/subscribe / resources/unsubscribe are replaced by subscriptions/listen, one long-lived POST response stream. Clients opt in to toolsListChanged, promptsListChanged, resourcesListChanged or resourceSubscriptions.
  • ping, logging/setLevel and notifications/roots/list_changed are removed. Log level is set per request with io.modelcontextprotocol/logLevel in _meta.
  • Tasks move out of core into the io.modelcontextprotocol/tasks extension. It polls with tasks/get, adds tasks/update, and removes tasks/result and tasks/list (SEP-2663).
  • Multi Round-Trip Requests (MRTR) replace server-initiated roots/list, sampling/createMessage and elicitation/create. Servers return resultType "input_required" with inputRequests, and clients retry with inputResponses (SEP-2322).
  • Every result has a required resultType field: "complete" or "input_required".
  • SSE resumability (Last-Event-ID and SSE event IDs) is removed. If a stream breaks, the client MUST re-issue the request with a new request ID.

Minor changes include: required Mcp-Method and Mcp-Name headers on Streamable HTTP POSTs, plus x-mcp-header for mirroring tool parameters into headers. Results of tools/list, prompts/list, resources/list, resources/read and resources/templates/list must carry ttlMs and cacheScope. tools/list SHOULD return tools in a deterministic order. Resource not found moves from -32002 to -32602. The error codes are renumbered to HeaderMismatch -32020, MissingRequiredClientCapability -32021 and UnsupportedProtocolVersion -32022. inputSchema and outputSchema may use any JSON Schema 2020-12 keyword. notifications/elicitation/complete and the URL mode elicitationId are removed. Auth changes add RFC 9207 iss validation, a required application_type in Dynamic Client Registration, and credentials bound to the issuing authorization server.

Removed and deprecated features

Removed in 2026-07-28:

  • Sessions and the Mcp-Session-Id header
  • The initialize handshake
  • The standalone HTTP GET stream, plus resources/subscribe and resources/unsubscribe
  • ping, logging/setLevel and notifications/roots/list_changed
  • SSE stream resumability via Last-Event-ID
  • notifications/elicitation/complete and the elicitationId field

Deprecated (still works, scheduled for removal, minimum twelve-month window):

  • Roots, Sampling and Logging (SEP-2577). Pass paths through tool parameters, resource URIs or config instead of Roots. Call LLM provider APIs directly instead of Sampling. Log to stderr or use OpenTelemetry instead of Logging.
  • The HTTP+SSE transport from 2024-11-05. Migrate to Streamable HTTP.
  • The includeContext values "thisServer" and "allServers". Omit the field or use "none".
  • OAuth Dynamic Client Registration (RFC 7591), deprecated in favor of Client ID Metadata Documents.

What server authors must change

  • Implement server/discover and return supportedVersions, capabilities, and serverInfo in _meta.
  • Read the protocol version from each request's _meta. If you do not support it, return UnsupportedProtocolVersionError (-32022) that lists the supported versions. On HTTP, send it with 400 Bad Request.
  • Drop session-keyed state. Use explicit handles in tool arguments, or requestState for MRTR flows. Treat requestState as attacker-controlled and protect it with HMAC or AEAD when it affects auth or business logic.
  • Replace server-initiated elicitation, sampling and roots requests with an InputRequiredResult on tools/call, prompts/get or resources/read only.
  • Add resultType to every result, and ttlMs plus cacheScope to list and read results.
  • On Streamable HTTP, validate that MCP-Protocol-Version, Mcp-Method and Mcp-Name match the body. Reject mismatches with 400 and -32020. Answer GET or DELETE with 405, and ignore Mcp-Session-Id and Last-Event-ID.
  • Emit notifications/message only for requests that include io.modelcontextprotocol/logLevel. Deliver list-changed notifications only on subscriptions/listen.
  • A modern-only server SHOULD name its supported versions in the error it returns to a legacy initialize request.

InputRequiredResult (trimmed from the spec)

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "github_login": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Please provide your GitHub username",
          "requestedSchema": {
            "type": "object",
            "properties": { "name": { "type": "string" } },
            "required": ["name"]
          }
        }
      }
    },
    "requestState": "AEAD-protected blob"
  }
}

What client authors must change

  • Stop sending initialize. Put protocolVersion, clientCapabilities and, ideally, clientInfo in the _meta of every request.
  • On HTTP, send MCP-Protocol-Version, Mcp-Method and, for tools/call, resources/read and prompts/get, Mcp-Name on every POST. Mirror any x-mcp-header tool parameters as Mcp-Param-{Name}, using the =?base64?...?= encoding for unsafe values. Leave out of tools/list any tool whose x-mcp-header annotations are invalid.
  • Handle resultType "input_required". Fulfill inputRequests, then retry the original request with a new JSON-RPC id, the inputResponses, and the exact requestState. Treat results without resultType from older servers as "complete".
  • Open subscriptions/listen for change notifications instead of the GET stream or resources/subscribe.
  • If a response stream breaks, re-issue the request with a new id. There is no resumption.
  • Use ttlMs and cacheScope to cache list results.
  • For OAuth, validate a present iss before redeeming the code, set application_type in Dynamic Client Registration, and key stored credentials by issuer.

Streamable HTTP request (from the spec)

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

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "location": "Seattle, WA" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

How to negotiate protocolVersion

There is no handshake. Every request declares its version, and the server accepts or rejects each request on its own. A client may call server/discover first to learn supportedVersions. It may also send any request inline and handle UnsupportedProtocolVersionError, then retry with a version from the supported list.

UnsupportedProtocolVersionError

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32022,
    "message": "Unsupported protocol version",
    "data": {
      "supported": ["2026-07-28", "2025-11-25"],
      "requested": "1900-01-01"
    }
  }
}

Talking to legacy (2025-11-25 and earlier) servers:

  • stdio: send server/discover first. If you get any error that is not a recognized modern error, or a timeout, fall back to initialize.
  • Streamable HTTP: try a modern request. On 400, check the body. A recognized modern JSON-RPC error means the server is modern, so retry or fix the request. An empty or unrecognized body means you should fall back to initialize.
  • Cache the result per server process (stdio) or per origin (HTTP).
  • A dual-era server picks its behavior from the first request. Modern _meta gets stateless handling. initialize gets legacy semantics.

SDK versions that support 2026-07-28

The official blog says the four Tier 1 SDKs (TypeScript, Python, Go and C#) support the spec as of release, and Rust supports it in beta.

  • Python: mcp v2.0.0 (released 28 Jul 2026) supports 2026-07-28 and still serves earlier revisions. Client(target) negotiates the version automatically. FastMCP is renamed MCPServer. v2.1.0 followed on 24 Aug 2026, and v2.1.1 is the latest release. To stay on v1, pin mcp>=1.28,<2. v1.x now gets only security fixes.
  • TypeScript: the v2 split packages @modelcontextprotocol/server, @modelcontextprotocol/server-legacy, @modelcontextprotocol/client, @modelcontextprotocol/core, @modelcontextprotocol/node, @modelcontextprotocol/express, @modelcontextprotocol/hono, @modelcontextprotocol/fastify and @modelcontextprotocol/codemod are at 2.0.0. The v1 line (1.30.0) does not mention 2026-07-28.
  • TypeScript codemod for upgrading v1 code (from the beta announcement): npx @modelcontextprotocol/codemod@beta v1-to-v2 .
  • Go and C#: the beta announcement listed go-sdk v1.7.0-pre.1 and ModelContextProtocol 2.0.0-preview.1. Check their release pages for stable versions.

TypeScript v2: opt in on the client, serve both eras on the server

// Client: probes with server/discover, falls back to initialize
const client = new Client({ name: 'my-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
// client.getProtocolEra() returns 'modern' or 'legacy'

// Server: createMcpHandler serves 2026-07-28 and 2025-era by default (legacy: 'stateless')
const modern = createMcpHandler(factory, { legacy: 'reject' }); // modern only
// stdio: serveStdio(() => buildServer())

Note: the TypeScript v2 client keeps the 2025 handshake by default. Set versionNegotiation to mode 'auto', or pin with mode { pin: '2026-07-28' }, to send modern traffic.

Get Claude Code settings changes by email

When a setting, flag or permission rule in this guide changes, it goes in the weekly AgentAtlas update, checked against the vendor docs. One email a week.

Sources

More on Claude Code

More Claude Code guides

Other guides

How to allow Cursor to run all commands: Run Everything, allowlist and sandboxCodex CLI: approval and sandbox modesGemini CLI: YOLO mode, auto_edit and allowing specific shell commandsCursor Privacy Mode: telemetry, training and data retentionGemini CLI: turn off usage statistics and telemetryAdd MCP servers to Cursor with mcp.json: Keep files out of Cursor with .cursorignore: Configure Codex with config.toml: Configure Gemini CLI with settings.json: GitHub Copilot custom instructions: Adding MCP servers to GitHub Copilot: Gemini CLI extensions and custom commands: Configuring OpenCode with opencode.json: Cline Rules: workspace, global, and conditional rulesKiro steering files: .kiro/steering, inclusion modes, and AGENTS.mdZed agent: instruction files, MCP servers, tool permissions, and ACP agentsCursor CLI: install, headless mode, permissions and CIAider: CONVENTIONS.md, .aider.conf.yml and API keysJunie guidelines, MCP and the Action Allowlist: Amp AGENTS.md, settings.json and MCP: Configure Goose extensions, hints, recipes and permissions: Customize OpenHands with skills, AGENTS.md, setup.sh and MCP: Configure Factory Droid CLI: AGENTS.md, settings.json, autonomy, custom droids and MCPConfigure Qwen Code: settings.json, providers, QWEN.md, MCP and approval modesCursor hooks: hooks.json, beforeShellExecution, afterFileEdit and exit codesCursor sandbox: sandbox.json, network allowlist and run modesCursor settings: settings.json, Cursor Settings and the ~/.cursor config filesCursor browser agent: @browser, browser automation and approval modesCursor subagents: .cursor/agents, frontmatter, built-in Explore, Bash and BrowserCursor telemetry and OpenTelemetry export (Privacy Mode, OTLP, Analytics API): Cursor rules setup: project, user and Team Rules, .mdc frontmatter, globs and AGENTS.mdCodex AGENTS.md: global, project and override instruction files explainedCodex MCP servers: codex mcp add and [mcp_servers] in config.tomlCodex sandbox modes: read-only, workspace-write, danger-full-access and network accesscodex exec: run Codex non-interactively in scripts and CI with JSON outputCodex skills: create SKILL.md, skill folder locations and $skill invocationGEMINI.md in Gemini CLI: context file locations, imports and /memoryGemini CLI MCP servers: gemini mcp add and mcpServers in settings.jsonGemini CLI custom slash commands: TOML files, {{args}}, !{shell} and @{file}Gemini CLI headless mode: gemini -p, JSON output and exit codes for scripts and CIGemini CLI sandbox: enable Docker, Podman, macOS Seatbelt or gVisorcopilot-setup-steps.yml: set up the GitHub Copilot cloudGitHub Copilot CLI permissions: --allow-tool, --deny-tool, --allow-all-tools and trusted directoriesGitHub Copilot custom agents: .github/agents/*.agent.md profiles, tools and frontmatterGitHub Copilot code review settings: automatic PR reviews, rulesets, effort level and approvalsOpenCode MCP Server Setup: Local and Remote Servers in opencode.jsonOpenCode Permissions: Allow, Ask or Deny Bash, Edit and WebfetchOpenCode Custom Agents and Subagents: Markdown Files, Modes and @ MentionsCline MCP Setup: cline_mcp_settings.json for Local and Remote ServersCline Auto Approve and YOLO Mode: Run Commands and Edits Without PromptsCline Skills: SKILL.md Location, Format and Slash Command UseAmp MCP Setup: amp mcp add, amp.mcpServers and Workspace ApprovalAmp AGENTS.md: File Locations, @ Mentions and Glob-Scoped GuidanceAmp Skills: SKILL.md Folders, amp skill add and Search OrderKiro MCP Setup: .kiro/settings/mcp.json, autoApprove and OAuthKiro Hooks: .kiro/hooks JSON, PreToolUse, PostFileSave and StopKiro Permissions: permissions.yaml Allow, Ask and Deny RulesFactory Droid MCP Setup: droid mcp add, mcp.json Locations and /mcpFactory Droid Hooks: hooks.json, PreToolUse Matchers and Exit CodesFactory Droid Custom Droids (Subagents): .factory/droids Markdown FilesJunie MCP Setup: .junie/mcp/mcp.json for CLI and /mcp CommandJunie Brave Mode and Action Allowlist: Auto-Approve Commands in Junie CLIJunie Custom Slash Commands: .junie/commands Markdown Files and Argumentsgoose .goosehints and AGENTS.md: Global and Project Instructionsgoose Permission Modes: GOOSE_MODE auto, approve, smart_approve and chatgoose Recipes: recipe.yaml Format, Parameters and goose run --recipeZed Agent Tool Permissions: Auto-Approve Terminal Commands with tool_permissionsUse Claude, Codex and Gemini CLI in Zed: External Agents and agent_serversZed with Ollama, LM Studio and llama.cpp: Local Model Setup and Context Length

Reviewed Oct 13, 2026. Settings change often; the linked vendor docs are the source of truth.