How this is verified

A package that claims parity across five revisions has to show it rather than assert it. Four things do that, and each catches something the others cannot.

The parametrized suite

One suite runs every adapter against one registry. The harness supplies the requests; each adapter supplies its expectations.

  • Method map. Every operation an adapter exposes round-trips: operation_for(method_for(op)) is op.

  • Failure map. Every FailureKind produces the code and status its revision defines, and the mapping is total.

  • Projection. A listing validates against that revision’s constraints, and a hidden tool is absent from the listing and unreachable by call.

  • Header rules. Required headers are enforced or ignored exactly as specified, and encoded values are decoded before comparison.

  • Selection. Each adapter is reachable, and the choice is stable under irrelevant header noise.

  • Caching. Two identical cacheable requests produce byte-identical results, and a registration between them changes the output.

Cross-adapter, the same registry called through every revision produces semantically equivalent results, and batch input is accepted by exactly the adapters that allow it.

Our client against our server

Every capability is driven end to end through the bundled Client, over a real socket, once per revision. This is what catches a claim that holds in a unit test and fails on the wire – reading a stream to its end before looking at it, for instance, which deadlocks exactly one case and no others.

Independent implementations

Our own client agreeing with our own server proves only that they agree. The suite therefore also drives the server with the official mcp SDK’s ClientSession, and drives a real SDK server with our client.

Those checks are where several assumptions were corrected. The SDK opens the GET notification stream only once a session id has been issued; it delivers a parsed notification rather than an envelope; it sets the elicitation capability only when a callback is actually configured. None of that is in the specification, and all of it matters.

Two stacks in four processes

Every check above runs inside one process, where a shared store is indistinguishable from a Python dictionary. The claim that a request can land on any worker needs more than that, so one suite starts four servers in four processes – two of this package and two of the official SDK – over one SQLite file, and plugs it into each stack through that stack’s own extension points: SessionStore and Hub here, RequestStateSecurity(keys=...), SubscriptionBus and EventStore there. It confirms by PID that each pair really is two processes, then begins work on one node and finishes it on the other. A fifth SDK node shares the file but not the key, to separate what the store carries from what the client does.

This is also what measures the comparison in deployment design rather than asserting it: round-trip state and change events cross processes on both stacks, and a session with a handshake crosses on this one only. It found the defect that made the difference visible in the first place – an SSE data: line with no payload, which the SDK writes to prime a stream and this package’s client read as JSON.

The documentation

Every python block in these pages is executed by markdown-pytest under the same suite as everything else. A page that stops being true stops passing. The copyable test setup is documented in Testing a server.

That is not only a guard against rot. Writing this documentation found three real defects: a client that never returned the session id a server issued, a client that could not read a JSON-RPC error carrying id: null, and a listen that yielded one extra message on 2026-07-28 and nothing equivalent elsewhere.

Running it

uv run pytest          # tests, documentation and README
uv run ruff check .
uv run ty check src/ examples/
uv run sphinx-build -b html docs docs/_build

Not implemented

Pagination. Every listing returns everything. Cursors are accepted and ignored. If pagination arrives, cacheability has to be re-derived along with it.

Resumption of a dropped stream. 2025-03-26 onward lets a client reconnect with Last-Event-ID and receive what it missed. Streams here are not replayable: a client that loses one re-sends the request. The SDK implements this behind its EventStore.

Batching over stdio. 2025-03-26 permits JSON-RPC batch arrays and the HTTP transport accepts them. stdio does not.

The Tasks and MCP Apps extensions.