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

Updated 2026-10-17

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:

erawhat it looks like
legacyan initialize handshake, a protocol session identifier on a header, a long-lived server-push stream, elicitation over that stream
modernno 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:

  1. POST to the endpoint with a JSON body, content-type: application/json, and an

accept header naming both application/json and text/event-stream.

  1. After the first call, send the protocol version the server returned on every

later call.

  1. No credential for a read. Anonymous is a first-class caller on the read surface.

It is never a caller on a write.

  1. 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.

  1. 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.

  1. 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.

Read this document as markdown — the same bytes an agent receives.