MCP Specification Compliance
This page records this server's compliance status against the Model
Context Protocol specification, and the decisions behind what was and
was not adopted from the latest revision, 2026-07-28. It exists so a
future contributor can see why a given piece of the spec is or is not
implemented without re-deriving the reasoning.
Summary
This server is dual-era: it implements its original protocol
revision, 2024-11-05 (a connection-scoped initialize handshake),
and the current revision, 2026-07-28 (stateless, per-request
negotiation), at the same time, on both transports. A request is
served under whichever model it uses:
- A request whose
params._metacarriesio.modelcontextprotocol/protocolVersionis served under the2026-07-28model. On HTTP, a presentMCP-Protocol-Versionheader also marks a request modern even when the body doesn't, since no client speaking a revision older than2025-06-18-- including this server's own legacy revision -- ever sends that header; this closes the gap for a client that means to be modern but sends a body with no usable_meta.protocolVersion(omitted, or the reserved key misspelled), which otherwise would have been silently served as legacy with no indication the header was ignored. SeeisModernHTTPininternal/mcp/modern.go. - A request without either signal, including every
initializehandshake, is served exactly as this server has always served it.
This matches the versioning specification's own guidance: "A dual-era
server MAY serve both eras concurrently on the same endpoint or
process." Adopting 2026-07-28 in full therefore did not require
removing anything: the legacy path is unchanged, byte-for-byte, for a
request that does not opt into the modern one.
What is implemented
server/discover(stdio and HTTP) — required by the spec for every server. ReturnssupportedVersions,capabilities, andserverInfo. Subject to the same per-request validation as every other modern method when called as one (with_meta.protocolVersionset); a client that does not yet know what this server supports can instead call it exactly like a legacy request (no_metaat all) and get the same answer with no validation to satisfy first, sincehandleDiscover/handleDiscoverHTTPdo not vary their response by era.- Per-request
_metanegotiation —io.modelcontextprotocol/protocolVersion,clientInfo,clientCapabilities, andlogLevelare parsed from every request'sparams._meta. A request whoseprotocolVersionis not2026-07-28(the only modern revision this server implements) getsUnsupportedProtocolVersionError(-32022); a request missing the requiredclientCapabilitiesfield gets-32602 Invalid params. resultTypeon every modern result (always"complete": this server never implements Multi Round-Trip Requests, so"input_required"never applies), and_meta.serverInfoon every modern result.ttlMs/cacheScopeon the results the spec classifies as cacheable (tools/list,resources/list,prompts/list,resources/read). This server's registries are populated once at startup and never change afterward, so every list is valid indefinitely (ttlMsreflects a 24-hour horizon rather than an unbounded value, andcacheScopeis always"public", since nothing returned varies per caller).- Streamable HTTP request headers —
MCP-Protocol-Version,Mcp-Method, and (fortools/call,resources/read,prompts/get)Mcp-Name, including the Base64 sentinel encoding for values that are not safe plain ASCII. A mismatch between a header and the request body returnsHeaderMismatch(-32020) and HTTP400, per spec. Header validation only applies to a request that is itself modern; a legacy request is never expected to carry these headers and is not checked for them. extensionscapability field (always an empty object: this server implements no MCP extensions).- Resource-not-found as a protocol error. Independent of the
2026-07-28work, but found while implementing it:resources/readon an unknown URI previously returned a JSON-RPC success whose content happened to describe an error, on both registries, in every revision this server has ever shipped. This is now a real JSON-RPC error —-32002for a legacy request (the code every revision through2025-11-25defines for exactly this case) or-32602for a modern request (the code2026-07-28renumbered it to, to align with the JSON-RPC specification). Seemcp.ErrResourceNotFound,resourceNotFoundCode, andContextAwareRegistry.Readininternal/resources/. 404alongside-32601for a modern request naming a method this server doesn't implement, on HTTP. The transport spec pairs the two specifically so a client can tell a modern server that simply doesn't implement a method apart from a legacy HTTP+SSE server that doesn't host this endpoint at all -- both could otherwise return a bare404with nothing to distinguish them. This is a handler-level rejection like any other-32601, so it is checked independently of the preflight-vs-handler distinction the general400handling below relies on.initializeitself is one instance of this: it isn't a method the modern era recognizes at all (2026-07-28removed the handshake entirely), so a modern-shaped request naming it gets this same treatment rather than a legacyInitializeResult.
What was deliberately not adopted, and why
subscriptions/listen(the replacement for the old GET/SSE stream andresources/subscribe/unsubscribe) — this server has no source of change notifications to deliver. Every tool, resource, and prompt is registered once at startup (built-ins are fixed in code; custom ones load from a definitions file read once during initialization) and never changes for the life of the process, so there is nothing alistChangednotification would ever report. Implementing a long-lived SSE endpoint with keep-alives and disconnect handling for a stream that can never emit anything would be speculative infrastructure with no current use. This server does not declarelistChanged/subscribein its capabilities today either, legacy or modern, so no client should expect it.- Multi Round-Trip Requests (MRTR), and by extension Roots,
Sampling, and Elicitation — this server has never implemented any
of the three client-side features MRTR exists to replace (it talks to
LLM providers directly through its own proxy, not through the MCP
sampling capability), so there is nothing to migrate. The spec also
deprecates Roots, Sampling, and Logging outright as of
2026-07-28; adopting them now to immediately migrate them to MRTR would be building a deprecated feature. x-mcp-headertool parameter annotations — explicitly optional for servers per spec ("use ofx-mcp-headeris optional for servers"). It exists to let infrastructure (load balancers, tenant routers) inspect a specific argument without parsing the JSON-RPC body; none of this server's tools have a parameter that benefits from that, since none of them are multi-tenant-routed.- OAuth-related SEPs (issuer validation, Dynamic Client
Registration deprecation, Client ID Metadata Documents,
application_type) — this server's HTTP authentication is a simple bearer-token scheme (internal/auth), not OAuth. These SEPs govern a framework this server does not use. - Icons — purely cosmetic (visual identifiers for tools/resources in a client UI); no functional effect on protocol compliance one way or the other. Not adopted for this pass; nothing prevents adding it later without touching anything above.
- A newer legacy floor. This server's legacy revision remains
2024-11-05; it was not bumped to2025-03-26,2025-06-18, or2025-11-25on the way to2026-07-28. Those revisions are handshake-based like2024-11-05, so a legacy client negotiating any of them would see behavior this server does not implement (their own incremental additions -- elicitation, richer completions, and so on). Skipping straight to the stateless model for anything past2024-11-05avoids implementing three handshake-based revisions this server would then have to maintain forever, for a set of clients that can transparently use the stateless model instead by sending_meta.io.modelcontextprotocol/protocolVersionon their requests.
Known gaps
Unlike the section above, these are not deliberate choices -- they are gaps found during review that this pass did not set out to fix, recorded here so nobody has to re-derive them:
Acceptheader. The transport requires a client to list bothapplication/jsonandtext/event-stream, because a conforming server may answer any request with either a single JSON object or a request-scoped SSE stream. The requirement binds the client rather than the server, and this server always answers withapplication/json, since it implements no streaming method; it therefore does not inspect the header, and a request that omits it is not rejected. The OpenAPI specification documents the header so the contract stays discoverable. Enforcing it would only become meaningful alongside a streaming response.
Where the implementation lives
internal/mcp/modern.go— the modern-protocol machinery:_metaparsing and era detection,server/discover's data, the two new error types, HTTP header validation, and the result-wrapping helpers used by both transports.internal/mcp/server.go/internal/mcp/http_server.go— the per-request modern/legacy branch,server/discover's dispatch, and every handler updated to send a wrapped result for a modern request.internal/resources/registry.go/internal/resources/context_aware_registry.go— the resource-lookup ordering fix (checking whether a URI is known before acquiring a database client, so a not-found response never depends on database connectivity) and theErrResourceNotFoundsentinel.test/mcp_modern_protocol_test.go— end-to-end coverage against the real server binary over real HTTP:server/discover's shape, header validation (missing headers, mismatchedMcp-Name, unsupported version, missingclientCapabilities), the modern result fields, and confirmation that a legacy request sees none of them.