Skip to main content
FrontMCP speaks every MCP revision from 2024-11-05 through 2026-07-28 on the same endpoint. There is no configuration switch: the revision is selected per request, from what the client presents.
2026-07-28 is a large breaking revision — it removes sessions, the initialize handshake, and the server→client request direction. Existing clients are unaffected: a request that does not declare 2026-07-28 takes the exact same code path it always did.

How a revision is selected

A request is served as 2026-07-28 when any of these is true:
  • its params._meta carries io.modelcontextprotocol/protocolVersion (a key that exists only in this revision);
  • its MCP-Protocol-Version header names a version the session-based pipeline does not know (so an unknown or future version gets a proper -32022 instead of a confusing session error);
  • its method is server/discover or subscriptions/listen.
Everything else — including every request carrying Mcp-Session-Id, and every initialize — is served by the session pipeline unchanged.

Default for unversioned requests

A client that names a revision always gets that revision. For a bare JSON-RPC call that names none — no initialize, no Mcp-Session-Id, no MCP-Protocol-Version — the fallback is configurable:
When the SERVER defaults a request to this revision, the mirrored-header rules are not enforced — the client never opted into SEP-2243, so requiring Mcp-Method / Mcp-Name would turn a working call into a 400. Headers that ARE present are still validated. Once a client declares 2026-07-28 itself, the full contract applies.

What changed in 2026-07-28

Statelessness

There is no initialize and no Mcp-Session-Id. Every request carries its own protocol version, client capabilities, and (optionally) client identity:
Capabilities are per request. FrontMCP never carries a declaration from one request to the next.

server/discover

Replaces initialize as the (optional) up-front negotiation step:
The result lists supportedVersions, capabilities (including extensions), and instructions.

Mirrored request headers

Streamable HTTP mirrors body fields into headers so intermediaries can route without parsing the body. FrontMCP validates that the two agree and rejects a mismatch with 400 + -32020: Values that are not header-safe travel Base64-wrapped as =?base64?…?=; FrontMCP decodes before comparing. To mirror a tool argument into a header, annotate it in the schema:

Result envelope

Every result carries resultType ("complete", "input_required", or "task") plus _meta["io.modelcontextprotocol/serverInfo"]. List and read results additionally carry caching hints:
cacheScope is public for anonymous traffic and private whenever the result could vary by caller.

Multi Round-Trip Requests (MRTR)

The server→client request direction is gone. When a tool calls this.elicit(), this.sample(), or this.listRoots(), FrontMCP answers the original request with an interim result:
The client gathers the input and re-issues the same request — with a new JSON-RPC id — carrying inputResponses and the echoed requestState.
requestState round-trips through the client, so FrontMCP signs it (HMAC-SHA256) and binds it to the authenticated principal, the originating request, and a 10-minute expiry. A tampered, replayed, or expired blob is discarded and the exchange restarts.
Tools are replayed, not resumed: the tool runs again from the top and the recorded answers resolve inline. Keep tool bodies idempotent up to the point they ask for input.

Request-scoped notifications

logging/setLevel is gone. A client opts in per request:
FrontMCP then answers with an SSE stream carrying notifications/message and notifications/progress for that request, terminated by the final response. A request that omits logLevel receives no log notifications at all.

subscriptions/listen

Replaces the standalone GET stream and resources/subscribe/unsubscribe:
The response is a long-lived SSE stream. Its first message is notifications/subscriptions/acknowledged, reporting which types the server actually honors; every message carries _meta["io.modelcontextprotocol/subscriptionId"].

Removed methods and verbs

initialize, notifications/initialized, ping, logging/setLevel, notifications/roots/list_changed, resources/subscribe, resources/unsubscribe, tasks/list, and tasks/result return 404 + -32601. HTTP GET and DELETE on the MCP endpoint return 405.

Error codes

Tasks extension

Tasks moved out of the core protocol into io.modelcontextprotocol/tasks. A client declares it per request:
A tool declaring execution: { taskSupport: 'optional' } then answers with a handle instead of running inline:
Poll tasks/get until terminal. If the task reports input_required, answer its inputRequests with tasks/update. tasks/cancel still works.
Tasks require an authenticated caller under 2026-07-28. With no protocol sessions, an anonymous task could not be scoped to its creator, so FrontMCP refuses to create one on a public server rather than pooling unrelated callers into a shared namespace.

Client support

The upstream @modelcontextprotocol/sdk client does not speak this revision, so FrontMCP ships its own:
The client handles the MRTR retry loop and task polling internally, so callTool resolves with the final result either way. It also drops tools whose x-mcp-header annotations are invalid, as the spec requires.

Proxying a 2026 remote

A remote app can negotiate the revision:
auto probes server/discover and falls back to the session transports when the remote does not answer it.

Authorization changes

  • Authorization responses now carry the RFC 9207 iss parameter, and FrontMCP validates a present iss against the provider’s recorded issuer before redeeming the code.
  • Dynamic Client Registration accepts application_type (web | native, defaulting to web).
  • Re-registering an upstream provider under a different issuer discards credentials bound to the previous authorization server.
  • DCR is deprecated in favour of Client ID Metadata Documents; it remains available for authorization servers that have not adopted CIMD.

Deprecations

Still functional, but new servers should not adopt them:
  • Roots — pass directories via tool parameters or server configuration.
  • Sampling — integrate with an LLM provider API directly.
  • Logging — log to stderr or use OpenTelemetry.
  • HTTP+SSE transport — migrate to Streamable HTTP.

Cloudflare Workers

A Worker serves this revision natively and defaults to it: server/discover, stateless tools/call, subscriptions/listen and the mirrored-header rules all work through the same fetch handler, and no session is minted. Legacy clients that send initialize keep working on the same endpoint. Skills served over MCP (skill:// resources, skills/search / skills/load / skills/list) go through the shared handler set, so they are available under 2026-07-28 without extra configuration.

Observability

OpenTelemetry context propagates through _meta (SEP-414). Send traceparent, tracestate, or baggage and FrontMCP echoes them on the result so a client can stitch its span to the server’s.