AI agents

Migrate a Python MCP server to the 2026-07-28 specification

The 2026-07-28 MCP specification removed the initialize handshake, replaced elicitation with an input_required round-trip, and deprecated roots, sampling, and logging. Here is the migration path for Python servers.

Outcome

Update a Python MCP server for the stateless 2026-07-28 specification by removing the initialize handshake, adopting the input_required round-trip, and dropping deprecated features.

Prerequisites
  • An existing MCP server, or the runnable FlyPython MCP example
  • Python 3.12 or newer
  • Basic JSON-RPC 2.0 familiarity

The 2026-07-28 Model Context Protocol specification is the largest release since remote MCP: it removes the initialize handshake entirely, replaces server-initiated requests with a multi-round-trip input_required flow, and deprecates roots, sampling, and logging. MCP is now stewarded by the Agentic AI Foundation under the Linux Foundation, and the Python SDK shipped alongside the specification.

You are done migrating when your server answers tools/list and tools/call with no session state of any kind, no tool depends on the deprecated server-initiated requests, and your authorization path satisfies the new issuer rules.

What changed in 2026-07-28

  • Stateless-first (SEP-2575, SEP-2567). The initialize/initialized exchange and the Mcp-Session-Id header are retired. Every request self-describes with protocol version, client identity, and capabilities through _meta. An optional server/discover RPC exists for clients that want capabilities up front.
  • Multi-round-trip replaces server-initiated requests (SEP-2322). elicitation/create, sampling/createMessage, and roots/list no longer hold open streams. A tool that needs client input returns a result with resultType: "input_required" plus the requests that need answers; the client retries the original call with the answers attached in inputResponses.
  • Per-request version and routing headers (SEP-2243). MCP-Protocol-Version travels with every request (2026-07-28), and Streamable HTTP requests must include Mcp-Method and Mcp-Name so infrastructure can route without parsing JSON bodies.
  • Authorization hardening. Authorization servers must return the iss parameter per RFC 9207 (SEP-2468), clients must set application_type during dynamic registration (SEP-837), and client credentials are issuer-bound — never reused across authorization servers (SEP-2352).
  • Response caching (SEP-2549). Responses from tools/list, prompts/list, resources/list, and resources/read carry ttlMs and cacheScope.
  • Tasks restructured (SEP-2663). Tasks moved into the io.modelcontextprotocol/tasks extension with poll-based tasks/get, a new tasks/update, and change notifications moved to a subscriptions/listen stream.
  • Deprecations with a minimum twelve-month window. Roots, sampling, and logging are deprecated (SEP-2577), the legacy HTTP+SSE transport has a one-year offramp, and Dynamic Client Registration is replaced by Client ID Metadata Documents (CIMD).

Migration checklist

  1. Delete the handshake. Remove initialize and notifications/initialized handling and every Mcp-Session-Id lookup. A request that still sends initialize should fail as an unknown method, not be negotiated.
  2. Make each request self-sufficient. Anything your server previously remembered from initialize — client capabilities, protocol version, identity — must now be read per-request from _meta, or fetched through server/discover when a client opts in.
  3. Replace elicitation with the input_required round-trip. A tool that used to call elicitation/create now returns resultType: "input_required" with the questions, and completes when the client retries with inputResponses.
  4. Stop building on deprecated primitives. Roots, sampling, and logging still work during the deprecation window, but new code should not depend on them.
  5. Upgrade the official Python SDK. The TypeScript, Python, Go, and C# SDKs shipped with the specification; the RC-to-final window was roughly ten weeks, so older SDK versions predate the breaking changes.
  6. Update transports. Drop the legacy HTTP+SSE transport on its one-year offramp and emit the required Mcp-Method and Mcp-Name headers on Streamable HTTP.
  7. Check the authorization path. Validate iss per RFC 9207, set application_type during registration, and keep one credential per issuer.
  8. Adopt caching metadata. Annotate list responses with ttlMs and cacheScope so stateless infrastructure can cache them safely.

The input_required flow in code

The runnable reference is the MCP tool server example. The essential shape:

class InputRequired(Exception):
    def __init__(self, requests):
        super().__init__("tool execution needs additional client input")
        self.requests = requests


def delete_resource(args, input_responses=None):
    answer = str((input_responses or {}).get("confirm", "")).strip().lower()
    if answer != "yes":
        raise InputRequired([{"id": "confirm", "prompt": "Type 'yes' to confirm deletion."}])
    return "deleted"

The server catches InputRequired before its generic exception handler and returns:

{
  "result": {
    "resultType": "input_required",
    "requests": [{"id": "confirm", "prompt": "Type 'yes' to confirm deletion."}],
    "content": [{"type": "text", "text": "Additional client input is required before this tool can finish."}]
  }
}

The client retries the identical tools/call with "inputResponses": {"confirm": "yes"}. Because the flow is retry-based, an unconfirmed call never mutates anything — the same property that makes idempotent automation safe.

Verify the complete behavior from the repository root, including JSON-RPC error codes and the removed handshake:

python examples/mcp-server/verify.py starter --expect-failure
python examples/mcp-server/verify.py solution

What this guide does not cover

This guide works from the published release notes and the shipped SDKs. Field-level wire schemas for _meta, the requests entries, and cacheScope values live in the specification and SDK types — consult them before shipping, and prefer the official Python SDK over hand-rolled dispatch in production. Authorization deployment details — issuer discovery, credential storage, and CIMD rollout — are also out of scope here.

Sources

Verification record

Documentation review

Editorial review against the 2026-07-28 release notes; the companion example runs its 8-test verifier in the repository. Verified 2026-09-06.

About the author

Organizational byline for FlyPython guides, verification records, and corrections. Editorial standards and contact details →