Plan the atpmcp cutover to MCP 2026-07-28 #651

Merged ngerakines.me
targeting main from atpmcp-blobs
Merged opened by and assigned to ngerakines.me
targeting main from atpmcp-blobs

The 2026-07-28 revision is not a version bump. It deletes the initialize/notifications/initialized handshake and ping, and makes the protocol stateless: every request carries its own protocol version and client capabilities in _meta, server/discover replaces the handshake, every result carries resultType, list and read results carry cache hints, server-initiated requests become multi round-trip results, and subscriptions/listen opens a long-lived stream that shares the one stdout channel with ordinary responses. Roots, Sampling and Logging are deprecated, with stderr named as the migration for stdio servers -- which is already what atpmcp does.

This plan reads the revision against the server we have and records what it would take. Two findings shape everything else.

The transport cannot be patched. Today's loop reads a line, awaits the handler, writes the answer, and only then reads again. Under this revision that fails three ways, none retrofittable: a notifications/cancelled sent while a request is running cannot be read, so cancellation is structurally impossible rather than merely unimplemented; subscriptions/listen is a request that does not return until cancelled, so a serial loop deadlocks on it outright; and progress notifications have to be written while the request producing them is still in flight. Answering subscriptions/listen is not optional either -- the base protocol requires every implementation to support the message patterns, and subscribe-and-notify is one of the three. So concurrency is forced even though nothing this server exposes ever changes.

And a resource list may not depend on history. The obvious blob feature -- listing what this session has already fetched -- is forbidden twice: the resources page says the advertised set MUST NOT vary "as a side effect of other requests on the connection", and the base protocol says a stdio process is not a session at all. The blob cache survives that rule because its key is a content address derived entirely from the request's own parameters; a list built from tool history would not.

The blob half is mostly plumbing on work the workspace already has: upload_blob, get_blob and DpopBody::Bytes in atproto-client, compute_raw_cid in atproto-dasl, and pds_endpoints_validated in atproto-identity. What it adds is the discipline around them -- verify every fetched blob against its CID, cap what can be returned inline, and address them under a scheme this server owns rather than one the ecosystem may want.

Commits 0