Skip to content

MCP 2026-07-28 (stateless) spec support - #5

Open
quinnj wants to merge 3 commits into
spec-2025-11-25-fixesfrom
spec-2026-07-28
Open

MCP 2026-07-28 (stateless) spec support#5
quinnj wants to merge 3 commits into
spec-2025-11-25-fixesfrom
spec-2026-07-28

Conversation

@quinnj

@quinnj quinnj commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Implements the newly released MCP 2026-07-28 spec (announced today) as a dual-era implementation: modern clients are served statelessly per the new revision, while existing 2025-11-25 clients keep working unchanged on the same endpoint.

Stacked on #4 — review after that merges (this PR's base is spec-2025-11-25-fixes; retarget to main once #4 lands).

Server

  • Stateless request handling: requests declaring 2026-07-28 (via params._meta io.modelcontextprotocol/protocolVersion or the MCP-Protocol-Version header) bypass sessions and the initialize handshake entirely. Era selection follows the spec's dual-era server rules.
  • server/discover with supportedVersions, capabilities, instructions, and _meta serverInfo (cacheable).
  • Header routing: validates Mcp-Method on every request and Mcp-Name on tools/call/prompts/get/resources/read against the body, including the =?base64?...?= sentinel encoding → HeaderMismatch (-32020); unsupported versions → UnsupportedProtocolVersionError (-32022) with the supported list; removed methods (ping, initialize, logging/setLevel, resources/subscribe, ...) → 404/-32601 on the modern path; resource-not-found aligned to -32602.
  • resultType on all modern results; ttlMs/cacheScope on the five cacheable results (configurable via cache_ttl_ms/cache_scope).
  • MRTR: handlers return MCPInputRequired(input_requests, request_state); input_responses(context)/request_state(context) expose the client's retry data. This is the protocol-native fit for flows like AiAPI's confirm-booking explicit-confirmation step.
  • Request-scoped notifications: send_progress!/send_log! (gated on progressToken / io.modelcontextprotocol/logLevel _meta per spec) are delivered as an SSE response stream ahead of the final response. Handlers deliberately run on the request task so task-local auth contexts (e.g. AiAPI's Roam context) survive.
  • subscriptions/listen: long-lived per-request SSE streams with the acknowledgment notification, subscriptionId tagging on every delivered notification, opt-in filters (toolsListChanged, promptsListChanged, resourcesListChanged, resourceSubscriptions), and graceful closure (close_subscription_listeners!). Wired into the existing list-changed/resource-updated broadcast helpers.

Client

  • protocol_version >= "2026-07-28" switches the client to modern mode: no handshake, _meta (protocolVersion/clientInfo/clientCapabilities) injected into every request, Mcp-Method/Mcp-Name headers sent.
  • initialize_client! transparently becomes server/discover for modern clients (drop-in for existing call sites); discover_server_info! exposed directly.
  • MRTR: is_input_required(result) + input_responses/request_state kwargs on call_tool/get_prompt/read_resource; meta kwarg on call_tool for progressToken/log-level opt-in.
  • listen_subscriptions! opens the long-lived stream and dispatches notifications to register_notification_handler! handlers in real time.

Not in scope (follow-ups)

  • x-mcp-header custom tool-parameter header mirroring (client MUST per spec — needs tool-schema awareness at call time)
  • True incremental streaming of in-flight notifications (currently buffered until the handler returns, wire-format correct)
  • CIMD client registration (DCR deprecation) — lives in OAuth.jl

Test plan

  • Full suite green on Julia 1.12.6 — 227 tests, including the new 58-test MCP 2026-07-28 set: stateless request/validation matrix, MRTR round trip, modern-client e2e over live HTTP (discover → list → call → streamed progress/log notifications → subscriptions/listen ack/notify/graceful close)
  • All legacy (2025-11-25) testsets unchanged and passing — dual-era behavior verified
  • JuliaC --trim=safe harness still green (0 verifier errors/warnings)

🤖 Generated with Claude Code

quinnj and others added 3 commits July 28, 2026 17:56
Dual-era server: requests declaring 2026-07-28 (via params._meta
io.modelcontextprotocol/protocolVersion or the MCP-Protocol-Version
header) are served statelessly with no session or initialize handshake;
legacy clients keep the existing session flow on the same endpoint.

Server:
- server/discover with supportedVersions/capabilities/serverInfo
- Mcp-Method/Mcp-Name request header validation incl. the =?base64?...?=
  sentinel encoding (HeaderMismatch -32020), header/_meta version
  consistency, UnsupportedProtocolVersionError -32022 with supported list
- resultType on all results; ttlMs/cacheScope on list/read results
  (config: cache_ttl_ms, cache_scope)
- MRTR: handlers return MCPInputRequired to request client input;
  input_responses/request_state context accessors on the retry
- Request-scoped notifications: send_progress!/send_log! (gated on
  progressToken / io.modelcontextprotocol/logLevel _meta) delivered as an
  SSE response stream ahead of the final response; handlers stay on the
  request task so task-local auth contexts survive
- subscriptions/listen long-lived streams with acknowledgment,
  subscriptionId tagging, and graceful closure via
  close_subscription_listeners!; wired into list-changed/resource-updated
  broadcasts
- Removed-in-2026-07-28 methods (initialize, ping, logging/setLevel,
  resources/subscribe...) return 404/-32601 on the modern path;
  resource-not-found maps to -32602 per the new spec

Client:
- protocol_version >= 2026-07-28 skips the initialize handshake, injects
  _meta (protocolVersion/clientInfo/clientCapabilities) into every
  request, and sends Mcp-Method/Mcp-Name headers
- discover_server_info!, is_input_required, call_tool/get_prompt/
  read_resource input_responses/request_state kwargs, call_tool meta
  kwarg, listen_subscriptions! real-time notification stream

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ptions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion tests

Two CI failures under HTTP.jl 2.x (CI resolves 2.5.5; local dev env had
1.11, which masked both):

- Tool/prompt/resource/completion/logging/cancellation handlers and the
  request hook are now invoked via Base.invokelatest: handlers registered
  after HTTP serving starts were hitting 'method too new to be called
  from this world context' when connection tasks dispatched them.

- HTTP.jl 2.x's server buffers SSE response bodies until the stream
  closes (reproducible with its own sse_stream do-block example), so
  mid-stream subscriptions/listen events only reach the client at
  graceful closure there. The subscriptions test now asserts live
  delivery only where the transport streams (HTTP 1.x) and asserts
  complete delivery at graceful close under both. Fixing the buffering
  belongs in HTTP.jl itself.

Full suite verified green under both HTTP 1.11 and HTTP 2.5.5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@quinnj

quinnj commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

CI is fixed — two distinct issues, both only reproducible under HTTP.jl 2.x (CI resolves 2.5.5; the local dev manifest had 1.11.0 which masked them):

  1. World-age: handlers registered after HTTP.serve! starts (like the test's noisy tool) failed with "method too new to be called from this world context" when connection tasks dispatched them. All user-supplied handlers (tools/prompts/resources/completion/logging/cancellation/request hook) are now invoked via Base.invokelatest — standard practice for servers supporting runtime handler registration. This was a latent bug on main too; no prior test ever called a late-registered tool.

  2. HTTP.jl 2.x server buffers SSE bodies until stream close — verified against HTTP.jl's own canonical sse_stream do-block example (curl -N receives all events in one burst; HTTP 1.11 streams them live). Protocol-wise subscriptions/listen still delivers everything correctly in order at graceful closure, but real-time delivery needs a fix in HTTP.jl itself (filed separately). The subscriptions test is now transport-aware: it asserts live mid-stream delivery under HTTP 1.x and complete delivery-at-close under both.

Full suite verified locally under both HTTP 1.11.0 and HTTP 2.5.5 (fresh-resolve env mirroring CI).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant