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:

  1. Keep using POST /mcp on the existing AppTheory handler.
  2. Do not call initialize and do not send or expect Mcp-Session-Id.
  3. Call server/discover to read supported protocol versions, capabilities, and server identity.
  4. Send Content-Type: application/json and Accept: application/json, text/event-stream.
  5. Send Mcp-Protocol-Version: 2026-07-28 on every HTTP POST.
  6. Send the matching params._meta["io.modelcontextprotocol/protocolVersion"] and an object-valued params._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.
  7. Send Mcp-Method equal to the JSON-RPC method on every request or notification.
  8. Send Mcp-Name for the methods below, using the exact body value or its exact case-sensitive =?base64?{canonical Base64}?= encoding:

    Method Mcp-Name value
    tools/call params.name
    prompts/get params.name
    resources/read params.uri
  9. Populate the required client-capability object with every capability needed by a multi-round result.
  10. 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:

  1. 2026-07-28
  2. 2025-11-25
  3. 2025-06-18
  4. 2025-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 finished
  • resultType: "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

  1. Deploy the upgraded AppTheory runtime without changing existing clients.
  2. Verify a session-ful canary still initializes, calls tools, streams/replays if used, and deletes its session.
  3. Add one stateless canary that calls server/discover.
  4. 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.
  5. Verify the client handles both result types, cache metadata, server identity metadata, modern method-probe 404, and all three modern transport validation codes.
  6. 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.