How this is verified¶
The checks below cover protocol revisions, transports, shared state, and extensions. Executable documentation examples run in the same test suite.
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
FailureKindproduces 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.
Listings and cache hints. Listings reflect current declarations. Cacheable results carry revision-specific freshness fields.
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 sync --all-groups --all-extras
uv run pytest # tests, documentation and README
uv run ruff format --check .
uv run ruff check .
uv run ty check src/ examples/
uv run sphinx-build -W --keep-going -b html docs docs/_build
Not implemented¶
Resumption of a response stream. The GET notification stream resumes
with Last-Event-ID; see Transports.
The stream a POST opens for one request does not: it carries no ids.
Retrying can execute the handler again, so the application must make retries
safe. The SDK supports response replay through its EventStore.
Batch response envelopes over stdio. 2025-03-26 batch input is decoded,
but stdio emits individual response lines rather than one response array.
Use individual requests over stdio. Streamable HTTP supports batch envelopes
for that revision.
Built-in Tasks and MCP Apps implementations. The generic extension API and Skills loader are available; see Extensions and skills. That API does not implement Tasks lifecycle rules or MCP Apps rendering.
Sampling and roots APIs. These do not have built-in server handler APIs.
Extension coverage¶
tests/test_extensions.py checks method dispatch over HTTP and stdio,
dependency cleanup, metadata checks, registration conflicts, and resource
fallback on all four older revisions. tests/test_skills.py checks file bytes
against manifests, nested skills, pagination, startup validation, and snapshots.
The optional directory-reading feature of Skills is not implemented or
advertised. Skills use skills/list, skills/get, and resources/read.
tests/test_extension_wire.py checks the legacy wire boundary on every older
revision over stdio and HTTP. It verifies ordinary resource response shapes,
rejection of native extension requests, and no extension capability declaration.
Extension-specific result fields remain JSON text inside resource contents.
See Extension compatibility.