MCP 2026-07-28 Dual-Version Migration
This milestone closes the audited 2026-07-28 conformance gap list; it does not claim complete coverage of the draft.
Mcp-Param-* mirroring, subscriptions/listen, per-request io.modelcontextprotocol/logLevel,
DiscoverResult.instructions, and trace context remain deferred.
AppTheory serves the audited MCP 2026-07-28 stateless transport alongside the established 2025-11-25 session-ful
transport on the same MCP handler. This is an additive server change; there is no server-wide mode flag and no second
route or deployment construct.
Existing session-ful clients: no change
Clients that use 2025-11-25 keep the same behavior:
- call
initialize - retain the returned
Mcp-Session-Id - use that session id for later POST/GET/DELETE requests
- receive the existing response bodies without
resultType - use SSE streaming, replay, tasks, and the existing session error contract as before
Do not add modern routing headers to a session-ful client unless it is intentionally sending a 2026-07-28 request.
All pre-existing session-ful contract fixtures remain byte-identical.
Modern stateless clients: migration checklist
For a client that opts into 2026-07-28:
- Keep using
POST /mcpon the existing AppTheory handler. - Do not call
initializeand do not send or expectMcp-Session-Id. - Call
server/discoverto read supported protocol versions, capabilities, and server identity. - Send
Content-Type: application/jsonandAccept: application/json, text/event-stream. - Send
Mcp-Protocol-Version: 2026-07-28on every HTTP POST. - Send the matching
params._meta["io.modelcontextprotocol/protocolVersion"]and an object-valuedparams._meta["io.modelcontextprotocol/clientCapabilities"]on every request or notification. Metadata-only protocol selection remains valid for non-HTTP bindings such as stdio, not Streamable HTTP. - Send
Mcp-Methodequal to the JSON-RPCmethodon every request or notification. -
Send
Mcp-Namefor the methods below, using the exact body value or its exact case-sensitive=?base64?{canonical Base64}?=encoding:Method Mcp-Namevaluetools/callparams.nameprompts/getparams.nameresources/readparams.uri - Populate the required client-capability object with every capability needed by a multi-round result.
- Branch on every successful result’s
resultType.
The 2026-07-28 shape is POST-only in this milestone. It does not route ping, logging/setLevel, initialize,
sessions, GET/listen, DELETE, task methods, task-augmented tool calls, resource subscriptions, or
subscriptions/listen. An unavailable modern method returns HTTP 404 with -32601. A GET or DELETE carrying
Mcp-Protocol-Version: 2026-07-28 returns HTTP 405 before legacy session validation. A client-posted JSON-RPC
response is rejected with HTTP 400; modern clients post only requests and notifications.
Discover before calling methods
Example:
curl -sS \
-X POST "https://YOUR_ENDPOINT/mcp" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: server/discover' \
-d '{
"jsonrpc":"2.0",
"id":"discover",
"method":"server/discover",
"params":{
"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}
}
}
}'
AppTheory advertises, in preference order:
2026-07-282025-11-252025-06-182025-03-26
The result derives capabilities from the server’s configured registries, hooks, and extensions and returns the server
constructor’s name/version under _meta["io.modelcontextprotocol/serverInfo"]. Modern discovery omits tasks
because AppTheory does not implement the 2026 task extension. A task-enabled 2025-11-25 discovery result can still
advertise the established task capability. A subscriptions capability is intentionally absent.
Handle resultType
Every successful modern result has one of:
resultType: "complete": processing is finishedresultType: "input_required": fulfill the returned input requests and retry the original request
For input_required, preserve requestState, collect a response for each named entry in inputRequests, and send those
values as inputResponses on the retry. The input request method determines the required client capability. For
example, this request metadata advertises elicitation:
{
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
}
}
Do not synthesize resultType for a 2025-11-25 response. AppTheory deliberately preserves the old wire shape.
By default, every successful modern result also includes _meta["io.modelcontextprotocol/serverInfo"] for both
complete and input_required. The explicit server-level opt-out is
mcp.WithServerInfoMetadata(false) in Go, includeServerInfoMetadata: false in TypeScript, or
include_server_info_metadata=False in Python.
Complete results from server/discover, tools/list, prompts/list, resources/list,
resources/templates/list, and resources/read also include required ttlMs and cacheScope fields. AppTheory’s
fail-closed defaults are ttlMs: 0 and cacheScope: "private"; use "public" only through explicit per-surface
configuration.
Handle the modern validation errors
AppTheory exports and returns these 2026-07-28 error codes:
| Code | Name | Action |
|---|---|---|
-32020 |
Header mismatch | Make protocol metadata and routing headers match the JSON-RPC body |
-32021 |
Missing required client capability | Advertise every capability needed by the returned input requests |
-32022 |
Unsupported protocol version | Choose a value from error.data.supported |
These errors return a JSON-RPC error body with HTTP 400. -32021 includes
error.data.requiredCapabilities; -32022 includes error.data.supported and error.data.requested.
Missing or invalid required protocol/client-capability metadata returns HTTP 400 with -32602. A missing required
HTTP protocol header is a -32020 condition. Malformed Base64 in the exact lowercase Mcp-Name sentinel also returns
-32020. Modern method-not-found responses use HTTP 404 with -32601; the 2025-11-25 path retains HTTP 200 for
the same JSON-RPC error.
In particular, a sessionless request with an unknown or future protocol header now receives -32022 instead of first
failing as a missing legacy session.
Preserved resource-not-found deviation
AppTheory returns -32602 for resource-not-found in both protocol eras. The 2025-11-25 specification names
-32002, but the repository has never emitted it. This milestone deliberately preserves the existing -32602
contract rather than changing the byte-pinned session-ful path while adding modern conformance.
Rollout
- Deploy the upgraded AppTheory runtime without changing existing clients.
- Verify a session-ful canary still initializes, calls tools, streams/replays if used, and deletes its session.
- Add one stateless canary that calls
server/discover. - Verify every stateless POST carries the version header, both required request metadata fields, and truthful routing headers at the HTTP edge before enabling tool calls.
- Verify the client handles both result types, cache metadata, server identity metadata, modern method-probe
404, and all three modern transport validation codes. - Expand the stateless canary only after capability and multi-round input handling is ready.
For the complete runtime surface, see docs/integrations/mcp.md. For the Remote MCP deployment shape, see
docs/integrations/remote-mcp.md.