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 as2026-07-28 when any of these is true:
- its
params._metacarriesio.modelcontextprotocol/protocolVersion(a key that exists only in this revision); - its
MCP-Protocol-Versionheader names a version the session-based pipeline does not know (so an unknown or future version gets a proper-32022instead of a confusing session error); - its method is
server/discoverorsubscriptions/listen.
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 — noinitialize, 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 noinitialize and no Mcp-Session-Id. Every request carries its own
protocol version, client capabilities, and (optionally) client identity:
server/discover
Replaces initialize as the (optional) up-front negotiation step:
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 with400 + -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 carriesresultType ("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 callsthis.elicit(),
this.sample(), or this.listRoots(), FrontMCP answers the original
request with an interim result:
inputResponses and the echoed requestState.
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:
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:
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 intoio.modelcontextprotocol/tasks. A
client declares it per request:
execution: { taskSupport: 'optional' } then answers with a
handle instead of running inline:
tasks/get until terminal. If the task reports input_required, answer its
inputRequests with tasks/update. tasks/cancel still works.
Client support
The upstream@modelcontextprotocol/sdk client does not speak this revision, so
FrontMCP ships its own:
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
issparameter, and FrontMCP validates a presentissagainst the provider’s recorded issuer before redeeming the code. - Dynamic Client Registration accepts
application_type(web|native, defaulting toweb). - 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
stderror 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.