Cup.py and the transport
What a client needs to talk to this surface: the two protocol eras served from one endpoint, the endpoint and cache rules, handles instead of sessions, how the software development kit wraps the eighteen declared tools, and where credentials and the OAuth door stand today.
markdownguidev1EDD6-DR36area: transportaudience: agentaudience: implementertransportmcpsdkclientoauthcredentialseras
Cup.py and the transport
This guide is for a client: an agent, a script or a library that has to talk to this surface and wants to know what is actually required. It has three parts — the protocol, the client contract, and the credential question — and it marks every claim as measured on a run, read from the specification, or not built yet.
1. Two eras, one endpoint
The protocol this surface speaks changed shape in 2026. The relevant difference, for a client:
| era | what it looks like |
|---|---|
| legacy | an initialize handshake, a protocol session identifier on a header, a long-lived server-push stream, elicitation over that stream |
| modern | no handshake and no protocol session: each request carries its protocol version, an up-front discovery method replaces trial negotiation, results declare a result type, a cache scope and a time to live, and multi round-trip requests replace server-initiated calls |
One endpoint serves both. That is this platform's choice and not a protocol guarantee, and the reason it was made is worth stating: no claim about which era a given client speaks is better than second-hand, the cost of serving both is one handler, and the cost of guessing wrong is a dead integration. The client matrix in the specification marks every row as unverified, deliberately.
What a run measured, and why you should still not assume it. On the release this hub
was tested against, with the modern-transport flag on, the server answered protocol
version 2025-06-18 with a stable list key across the whole run, and both of the method
rules below held — the harness sheet's rows M3, M6, M17 and M18, measured in
docs/commander/RUNS/2026-10-15-public-staging.md. The specification's older reading,
that the surface pins 2024-11-05, describes the handler a flags-off deployment
serves, and the cutover is chartered flags-off, so that version may still be the answer
on a given host. Read the version out of the response and send that back; do not
assume either value.
Two endpoint rules a client meets immediately:
- The tool list is a pure function of the protocol era and the registry version. Not
of the endpoint, the caller, the credential, the plan or the tier. It is returned in registry order, and the hub-scoped endpoint forms differ only in which hub the calls resolve against by default.
- POST only. A GET and a DELETE on an MCP path answer
405. That is this platform's
endpoint hygiene, not a protocol requirement: the modern era removed the GET stream and the session lifecycle a DELETE used to end.
The list key, which is how caching works here, is a hash of the era and the registry version. It is returned in the metadata of every cacheable result and as the HTTP entity tag, and a client may send it back to receive a not-modified answer. Because the key has no hub input, every endpoint shares one cached list per era. A call naming a tool outside the registry answers with a method-not-found and the current list key, so a stale list degrades to one round trip and never to a wrong answer.
2. What a client needs
The minimum, and it is genuinely this short:
- POST to the endpoint with a JSON body,
content-type: application/json, and an
accept header naming both application/json and text/event-stream.
- After the first call, send the protocol version the server returned on every
later call.
- No credential for a read. Anonymous is a first-class caller on the read surface.
It is never a caller on a write.
- Identify yourself. Send a session label on every call that takes one, and your
model name and start time where an agent-info argument is accepted. This is how a run is matched on the server's own rows afterwards; an unlabelled run cannot be.
- Mint a handle and pass it back. The modern transport has no connection state, so
a session is an opaque handle a tool returns and later calls carry as an argument. The boot call mints nothing and reads nothing by design.
- Read the refusal. Every refusal names what was refused, why, and the call that
recovers. Do not retry a refusal unchanged.
Two things not to build. Do not hold a stream open: this surface opens none and declares no subscription of any kind. Do not batch JSON-RPC arrays: they are refused with a message. A long-running command returns a run handle immediately and you poll it.
The compatibility pair. Two tools, search and fetch, exist under exactly those
names because some client families require them: search returns a list of hits with an
id, a title, a URL and a snippet, and fetch returns one document by that id. They are
the plainest possible retrieval pair and they are core on every endpoint.
A caveat about search on the tested release, measured and recorded. The tool named
search reads a different corpus from the hub's own documents: on the run above, five
queries about this server's own documentation returned hits from another product's
corpus, with working ids, and nothing in the answer named the corpus; the same question
asked through the documents tool returned twelve hits from this hub. It is recorded as
a finding (tracking/backlog/hub.md item 34.3) and the fix is the owner's call. Until
it lands: to read this hub, use the documents tool, not search.
3. The eighteen tools, and what the kit wraps
The registry declares eighteen tools, counted from the normative command registry in
the specification: four single-call tools (_hello, help, search, fetch) and
fourteen umbrella tools that take a command argument (account, hubs, session,
decisions, insights, docs, layers, skills, knowledge, memory, ops,
hub_admin, tenant_admin, redaction).
Six of the eighteen are on the wire on the tested release, and this is the single
most useful fact for a client author: _hello, docs, fetch, help, search and
session, with nine reachable command rows between them. That is the harness sheet's
rows M7, M8 and M9, measured on the same run. A step that asks for anything else is
answered honestly as not served, and a client that improvises a route around a missing
capability has stopped measuring this surface.
The software development kit is Cup.py, and it is specified rather than built. The
Python distribution is cup.py, the import name is cuppy, the command line is
cuppy; the TypeScript package is @cuppy/sdk. What the kit is for: wrapping the
eighteen tools as functions, scaffolding a hub from the skeleton, validating front
matter and conformance, importing and exporting bundles, and a thin client for scripts.
The program rule attached to it is the one that matters — the kit is the only client the
platform's own interfaces use, so it can never fall behind the surface.
Nothing of it exists in this tree. Searching the repository for either package name
at the base commit in this document's front matter returns design documents and no
implementation; the kit and its validator are later-phase work. Treat every sentence in
the paragraph above as a specification, and treat cuppy validate as a proposal with a
defined shape. What exists today is the MCP surface itself, which any client can call
over plain HTTP with the six steps in section 2.
4. Where credentials stand today
Three doors are specified, and they are not interchangeable.
Anonymous is the read door, and it works. The run in section 1 made every call anonymously, against a public hub, and reached the hub's boot document that way.
A key is a machine credential, issued by an account owner for an app talking to cuppy. It is explicitly not the user door: a person signing in is not supposed to paste a key, and the key classes the hub mints are scoped by purpose — a bearer key, a continuous-integration signing key, and a publisher key whose reach is a closed enumeration of three write commands and nothing else. A publisher key may write documents under its own layer, attach notes and report outcomes; every other command is refused for it on every hub including its own.
OAuth is the third door, not the only one, and today it is built and closed. The resource-server half is implemented behind a flag that defaults off: protected-resource metadata, the challenge that starts discovery, and bearer validation against the authorization server's published keys. With the flag off there is nothing to discover. And even with it on, the door is inert for a person, because the authorization server mints no token whose audience is cuppy and whose subject is a human being yet; the change that would is the owner's open decision D-13 and belongs to another system's release process. So: the OAuth door is behind a flag until the identity side ships, and a client should not plan a user login against it.
A write needs a resolved principal. A row the registry marks as a write, a
destructive operation or a long one, reached by a caller for whom no principal resolves,
answers principal_required before any write and names the credential kind that
would resolve one. That is a different answer from "known identity, insufficient rung"
and a different answer again from "no such hub", and the distinction is the point: each
tells the client a different thing to do next.
Two rules that are frozen and will not be relaxed for convenience. A role comes from the credential, never from a header — a development-mode header once granted the owner rung to anyone for two days, and that is why the rule is written this way. And a shared published key is not a principal: it resolves a rung and names no identity.
5. If something goes wrong
- A 405 means you used GET or DELETE. Use POST.
- A method-not-found naming a tool means your list is stale: list the tools again,
and the answer carries the current list key.
- A refusal naming a command means the tool is served and the command is not. Ask
help for the tool: the refusal names that call.
- A not-found about a hub or a document is deliberately the same answer whether the
thing is absent or invisible to you. Do not read it as a hint that something exists.
- An authentication refusal on a write is the control working. Resolve a principal
or stop; do not look for another route to the same write.
Next reads
guides/connect.md— how to connect from each client, with the deep links.reference/cuppy/mcp-architecture.md— the surface in full: the tools, the umbrella
shape, the error envelope.
guides/agent-ergonomics.md— what a call promises you and how it names your next
action.
guides/test-loop.md— how a client agent tests a hub, and what the first run of this
one found.