Skip to content

MCP 2026-07-28 conformance

neon-mcp targets the 2026-07-28 revision of the Model Context Protocol, the stateless core1, on the Python mcp SDK 2.x (2.2.0 at release) through its low-level Server API3. The SDK serves both eras on stdio, so clients on earlier revisions (2024-11-05 … 2025-11-25) still work.

What the server does

Requirement neon-mcp
No initialize handshake, no sessions Streamable HTTP runs with stateless_http=True: no Mcp-Session-Id, any instance answers any request
server/discover answered by the SDK with supported versions, capabilities (tools, resources, prompts) and the server instructions
Per-request _meta envelope io.modelcontextprotocol/clientCapabilities decides whether MRTR elicitation may be used
Cacheable list results tools/list 5 min, resources/list and resources/templates/list 1 h, prompts/list 24 h, server/discover 5 min, resources/read 1 h (24 h for static guides); cacheScope is always public because nothing depends on the caller
resultType complete on every result; input_required only from neon_get_sample
Result _meta the SDK's io.modelcontextprotocol/serverInfo plus io.neon-mcp/upstream (NEON requests, cache hits, rate-limit headroom, identity)
JSON Schema 2020-12 every inputSchema and outputSchema declares the dialect; outputs match structuredContent
Tool annotations and _meta all four hints on every tool; io.neon-mcp/surface, requiresToken, endpoints, stdioOnly
Header routing Mcp-Method / Mcp-Name must match the body (400, -32020); an unsupported version is -32022
Deprecated features none: no roots, sampling, logging/setLevel, SSE transport or sessions (a CI job greps for them)

MRTR and requestState

Only neon_get_sample asks the user a question, and only when the client declares elicitation support: a sample tag that belongs to several classes produces an InputRequiredResult with a form listing the candidates. The continuation (requestState) is base64url JSON of at most 16 KiB holding the question and candidates; it never contains a token, a path or an authorization decision, it expires after an hour, and the answer is re-validated against a fresh lookup on resume. It is unsigned by design: tampering can only change the caller's own question.

Measured sizes

With all 20 tools (stdio), tools/list is 107,734 bytes of compact JSON: input schemas 25,347 B, output schemas 67,661 B, descriptions 6,581 characters. HTTP lists 19 tools in 102,342 B. Pydantic's generated title keys are dropped from both schemas and descriptions from output schemas (the tool reference keeps them). The conformance bound is 135,000 B (measured × 1.25).

SDK behaviour worth knowing

  • A request whose MCP-Protocol-Version header names an older revision (e.g. 2025-11-25) is served in the legacy era even if its body carries a 2026-07-28 envelope; a modern header with a different envelope version is rejected with -32020.
  • resources/read and prompts/get require Mcp-Name (the URI or prompt name).

Conformance tests

tests/test_spec_conformance_2026_07_28.py and tests/transport/ run in their own CI job: schema dialect and validity, annotations and _meta, sorted and stable tools/list within its size bound, cache hints on every list and read, discovery, stateless requests in both eras, result _meta, header routing, protocol-version errors, host allow-lists, resources and prompts over HTTP, an MRTR round trip with a tampered continuation, and a real stdio subprocess speaking both protocol eras.