Skip to content

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

Merged
quinnj merged 14 commits into
mainfrom
spec-2026-07-28
Aug 4, 2026
Merged

MCP 2026-07-28 (stateless) spec support#5
quinnj merged 14 commits into
mainfrom
spec-2026-07-28

Conversation

@quinnj

@quinnj quinnj commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Implements the released MCP 2026-07-28 protocol as a dual-era client and server. Stateful 2025-11-25 clients continue to use initialization and sessions. Modern clients use stateless request metadata and server/discover on the same Streamable HTTP endpoint.

Server

  • Validates required modern _meta, protocol headers, Mcp-Method, Mcp-Name, and recognized Mcp-Param-* headers before handler execution.
  • Returns the specified HTTP status and JSON-RPC errors for invalid metadata, header mismatches, unsupported versions, removed methods, and missing client capabilities.
  • Supports server/discover, cache metadata, request-scoped progress and log events, and filtered subscriptions/listen streams.
  • Supports multi-round-trip input_required results from tools, prompts, and resources. Unsupported client interactions are filtered by declared capabilities.
  • Cleans up disconnected subscription listeners and preserves the existing task-local request context.

Client

  • Injects modern request metadata and standard routing headers automatically.
  • Implements x-mcp-header schema validation, tool filtering, nested parameter extraction, safe-integer checks, Base64 encoding, and schema caching.
  • Uses server/discover instead of the removed initialization handshake in modern mode.
  • Rejects legacy-only calls in modern mode and keeps modern low-level helpers namespaced.

Compatibility and documentation

  • Preserves the public positional constructors that existed in v1.0.0.
  • Keeps all existing 2025-11-25, OAuth, MCP Apps, and JuliaC static-server behavior.
  • Adds a dated protocol guide, updates the package support matrix, replaces the stale roadmap, and adds repository maintenance guidance.
  • Updates the two remaining workflows that used obsolete action majors.

Validation

  • Julia 1.10.11: full package suite passed in a clean environment. The suite includes 101 2026-07-28 checks.
  • Julia 1.12.6: full package suite passed. The JuliaC --trim=safe compile check passed.
  • Documenter 1.17: docs build and doctests passed.
  • Official MCP conformance runner 0.2.0-alpha.10: 97/97 relevant checks passed with no warnings. This covers stateless metadata, standard headers, custom headers on both client and server, malformed-tool filtering, elicitation, sampling, roots, prompt MRTR, and capability filtering.

Support limit

Request-scoped progress and log events use the correct SSE wire format and order, but the current server buffers them until the handler returns. Long-lived independent notifications use subscriptions/listen.

Co-authored by Codex

quinnj and others added 9 commits July 22, 2026 12:11
Add a concrete tools-only Streamable HTTP server for native compilation. Typed request envelopes, raw JSON fragments, concrete handlers, and explicit sessions avoid dynamic Function and Dict dispatch while preserving initialize, tools/list, tools/call, ping, and session lifecycle behavior.
…med responses

- Return tools/prompts/resources/templates lists in deterministic sorted
  order so clients get stable prompt-cache-friendly results
- Unknown tool/prompt in tools/call & prompts/get now return -32602
  (Invalid params) instead of -32601; unknown resource in resources/read
  returns -32002 (Resource not found) per the 2025-11-25 spec
- Client now handles Streamable HTTP POST responses delivered as SSE
  streams: request-scoped notifications/server requests are dispatched to
  registered handlers and the final JSON-RPC response is extracted

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JSON.lazy/typed-parse request paths reach Parsers' recursive float
parsing and repr()-based error formatting, neither of which passes the
JuliaC --trim=safe verifier (456 errors). Replace them with a minimal
self-contained scanner (string unescape incl. surrogate pairs, value
extent skipping, Int64 ids) so the static server's entire request graph
verifies clean. Drops the StructUtils dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test/trim_compile_tests.jl: standard trim harness (compiles workloads
  with --trim=safe in a temp env, requires 0 verifier errors/warnings,
  runs the produced executable); skips on Julia < 1.12/prerelease/32-bit
- test/mcp_trim_safe.jl: StaticMCPServer workload exercising the full
  session + tools request lifecycle over in-memory HTTP requests
- New testset covering deterministic list ordering, spec error codes,
  and SSE-streamed POST response handling in the client

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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).

quinnj added 3 commits August 4, 2026 11:34
Validate JSON, initialization parameters, tool schemas, and result shapes in the trim-safe server. Prevent request-only methods from running as notifications and return stable JSON-RPC errors for handler failures. Add regression tests and executable documentation.
…x/review-pr5-fixes

# Conflicts:
#	src/server.jl
@quinnj
quinnj deleted the branch main August 4, 2026 17:54
@quinnj quinnj closed this Aug 4, 2026
@quinnj quinnj reopened this Aug 4, 2026
@quinnj
quinnj changed the base branch from spec-2025-11-25-fixes to main August 4, 2026 17:55
@quinnj
quinnj merged commit f7dbf1a into main Aug 4, 2026
11 checks passed
@quinnj
quinnj deleted the spec-2026-07-28 branch August 4, 2026 18:32
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