Style guide
How the site looks, and why
Every swatch, specimen and component below is the real thing, rendered from the same tokens the site uses. Use the theme button in the header to see it in light and dark.
Color
Values shown are read from this page as rendered, in the current theme.
Surfaces
--bgThe page.
--bg-raisedCards, panels and code blocks: anything that sits on the page.
--bg-sunkenWells, hovered rows, the footer: anything set into the page.
--bg-insetThe deepest step. Rare.
Text and lines
--fgBody text and headings.
--fg-mutedLedes, captions, secondary text.
--fg-subtleThe lightest text allowed. Counts, placeholders, fine print.
--borderHairlines between rows and around cards.
--border-strongControl outlines and rules that need to be seen.
Accent
The accent marks interaction: something you can follow, press, or are currently on. It is separate from the five hues so that a link never reads as a role.
--accentLinks, focus rings, the primary button, the current item.
--accent-softThe highlight behind a targeted entry.
--on-accentText on an accent fill.
The five hues
Every diagram on the site assigns one hue to each kind of principal, and keeps that assignment everywhere. Each hue has a solid value for text and outlines and a soft value for fills.
--red--red-softAlso: danger, “no,” cautions.
--blue--blue-softAlso: working group drafts.
--violet--violet-softAlso: projects and platforms.
--green--green-softAlso: success, “yes,” published standards.
--amber--amber-softAlso: “partly,” individual drafts, out-of-band messages.
Contrast
Each cell is measured live. Text needs 4.5 to 1 to pass WCAG AA.
| Text token | on --bg | on --bg-raised | on --bg-sunken | on its own soft fill |
|---|---|---|---|---|
--fg | Aa | Aa | Aa | – |
--fg-muted | Aa | Aa | Aa | – |
--fg-subtle | Aa | Aa | Aa | – |
--accent | Aa | Aa | Aa | Aa |
--red | Aa | Aa | Aa | Aa |
--blue | Aa | Aa | Aa | Aa |
--violet | Aa | Aa | Aa | Aa |
--green | Aa | Aa | Aa | Aa |
--amber | Aa | Aa | Aa | Aa |
--on-accent | Aa on --accent | |||
The same pairs are checked in both themes by npm run check:contrast, which fails if any text pair drops below 4.5 to 1.
Type
Four roles, filled by two families.
Who’s on first?
Exchange, never forward.
HUMAN PRESENT · 14 STEPS
grant_type=client_credentials
Scale
Who is acting?
Delegation or impersonation
The agent redeems the code directly
Token Exchange
A field guide to every way an AI agent can prove what it is and what it may do.
A service that needs to call another gets a new token for that hop. Passing along the one it received is how a token meant for one place ends up everywhere.
Flows / Human present
aud, act.sub, cnf.jkt
Shape
Three radii, and three widths that every page shares.
--radiusCards, buttons, code blocks.
--radius-pillChips and segmented choices.
--radius-roundStep numbers and markers.
| Token | Value | Use |
|---|---|---|
--wide | 72rem | The widest a page gets. Diagrams and grids use all of it. |
--prose | 44rem | Running text. About 70 characters a line. |
--gutter | 1rem to 2.5rem | Side padding, growing with the viewport. |
Components
The building blocks, with what each is for.
Buttons
One primary action per view. Everything else is a plain button, and tertiary actions are ghosts. Labels say what happens: “Explore the flows,” never “Click here.”
Chips
Short facts about the thing they sit on. A neutral chip states a property. A toned chip states a category, and takes its hue from the table above, so the same color always means the same thing.
Cards
A card is a link to one thing. The whole card is the target. Use cards for a set of peers, not for emphasis: a paragraph does not become more important by getting a border.
MCP authorization
How an MCP client that has never seen a server before discovers where to sign in, identifies itself, and gets a token good for that server alone.
Backchannel approval (CIBA)
The agent asks the authorization server to go and find the user. Approval arrives as a push notification, not a redirect.
SPIFFE + mTLS
Every agent process gets a cryptographic identity from the infrastructure it runs on, and uses it to authenticate both ends of every connection.
Callouts
For the one thing on a page a reader must not miss. If a page has three, none of them is a callout. Red is for a risk, amber for a caveat.
Reading status correctly
A working group draft has been adopted, and can still change in incompatible ways.
The trap in the middle
An unattended agent holding a person’s long-lived token has a human’s authority and no human watching.
Roles
Five kinds of participant appear in every diagram. The class role-human,role-agent and so on sets --role and --role-soft for anything inside it.
Tables
For comparing the same facts across several things. Column heads are labels; the first column names the row. Wide tables scroll inside their own frame, never the page.
| Flow | Human | Token subject | Maturity |
|---|---|---|---|
| Authorization code + PKCE | present | User | Stable standards |
| Backchannel approval (CIBA) | reachable | User, for one action | Stable standards |
| Workload identity federation | absent | Agent, as a workload | Stable standards |
Code
Inline code names a real identifier: a claim, a parameter, a header. Blocks show what is on the wire. Examples are abbreviated for reading and say so.
The resource parameter asks for a token that is valid for one API only, and the API checksaud.
POST /token HTTP/1.1
Host: auth.example
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&resource=https://erp.example/apiComparison
Yes, no and partly are stated in words as well as color, so the answer survives a grayscale print and a color-blind reader.
| Who is the user? | No |
|---|---|
| What software is calling? | Partly |
| Was this credential meant for me? | Yes |
| Is a stolen copy useless elsewhere? | No |
| Can one user or one agent be cut off? | No |
The API sees one caller: whoever holds the key. Every user and every agent run looks the same, the key works from anywhere, and revoking it cuts off everyone at once. Still the most common setup in the wild.
| Who is the user? | Yes |
|---|---|
| What software is calling? | No |
| Was this credential meant for me? | No |
| Is a stolen copy useless elsewhere? | No |
| Can one user or one agent be cut off? | Partly |
The user is visible, but the API is accepting a token that was issued for a different service. Every hop that touched the token can replay it wherever it is accepted. This is the confused-deputy setup, and the MCP specification prohibits it.
| Who is the user? | Yes |
|---|---|
| What software is calling? | No |
| Was this credential meant for me? | Yes |
| Is a stolen copy useless elsewhere? | Partly |
| Can one user or one agent be cut off? | Partly |
Each hop gets a fresh token for the right audience. But the API believes the user is calling directly. Logs show Alice doing things at 3 a.m. that Alice never did, and policy cannot treat software differently from a person.
Token exchange →| Who is the user? | Yes |
|---|---|
| What software is calling? | Yes |
| Was this credential meant for me? | Yes |
| Is a stolen copy useless elsewhere? | Partly |
| Can one user or one agent be cut off? | Yes |
The token names both the user and the chain of software acting for them. The API can apply the user’s permissions and the agent’s limits together, and either can be revoked on its own. A stolen token is still usable until it expires, but only at this one API.
Token exchange →| Who is the user? | Yes |
|---|---|
| What software is calling? | Yes |
| Was this credential meant for me? | Yes |
| Is a stolen copy useless elsewhere? | Yes |
| Can one user or one agent be cut off? | Yes |
As above, and each token is bound to a key held by the caller. Copying a token out of a log or a model’s context gets an attacker nothing. This is where the standards are heading; few deployments are here yet.
Sender-constrained tokens →Diagrams
Sequence diagrams are drawn at build time from data, so they take the tokens like everything else.
One column per participant, colored by role.
Line style is meaning. Solid for a direct call, dashed for a response, dotted for out of band, and the human hue for anything that passes through a person’s browser.
Labels stay short. Two lines at most; the panel carries the explanation.
Sequence diagram3 participants, 7 steps
- Direct call
- Response
- Local action
Drag to see every party and step. Tap a step to jump to its explanation.
The agent creates a signed proof of its identity
Instead of sending a shared secret, the agent signs a short-lived JWT with its private key. Issuer and subject are both the client ID; the audience is the authorization server;
jtimakes it single-use. The private key never leaves the agent.{ "iss": "invoice-agent", "sub": "invoice-agent", "aud": "https://auth.example", "jti": "b1f7c2a0-5d1e-4c8e-9f3a-0d6b8e1a2c44", "iat": 1794268800, "exp": 1794268860 }Who holds what after this step
- Agent
- newprivate key
Parties not listed hold none of this flow’s credentials at this point.
The agent requests a token
One direct call. The
resourceparameter asks for a token that is valid for one API only.POST /token HTTP/1.1 Host: auth.example Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &scope=invoices.read invoices.reconcile &resource=https://erp.example/api &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer &client_assertion=eyJhbGciOiJFUzI1NiJ9…Who holds what after this step
- Agent
- private key
Parties not listed hold none of this flow’s credentials at this point.
The authorization server authenticates the client
It verifies the signature against the public key registered for this client, checks audience, expiry and replay, then decides which of the requested scopes this client is allowed. There is no user to ask, so policy is the only gate.
Who holds what after this step
- Agent
- private key
Parties not listed hold none of this flow’s credentials at this point.
The server issues an access token
The subject of the token is the client itself. Nothing in it refers to a human.
{ "iss": "https://auth.example", "sub": "invoice-agent", "client_id": "invoice-agent", "aud": "https://erp.example/api", "scope": "invoices.read invoices.reconcile", "exp": 1794269400 }Who holds what after this step
- Agent
- private keynewaccess token
Parties not listed hold none of this flow’s credentials at this point.
The agent calls the API
An ordinary bearer request.
Who holds what after this step
- Agent
- private keyaccess token
Parties not listed hold none of this flow’s credentials at this point.
The API authorizes a service, not a person
The API must treat this as a machine caller. Row-level rules that depend on “the current user” have nothing to bind to. If the agent is doing something for a particular user, that fact has to arrive some other way, and a request parameter the agent filled in is not evidence.
Who holds what after this step
- Agent
- private keyaccess token
Parties not listed hold none of this flow’s credentials at this point.
The API responds
The audit log records the agent’s client ID. If several replicas, runs or sub-agents share that ID, the log cannot tell them apart. Workload identity systems such as SPIFFE address this by giving each workload its own identity.
Who holds what after this step
- Agent
- private keyaccess token
Parties not listed hold none of this flow’s credentials at this point.
Annotated tokens
User access token
Authorization server → agent → API
What an agent holds after a person signs in and consents. The JWT profile (RFC 9068) is one common format; opaque tokens checked by introspection carry the same information.
{"iss": "https://auth.example",
The authorization server that issued the token. The API must check it against the issuer it trusts; an agent that talks to many servers must check it against the one it meant to use.
"sub": "user:alice",
The subject: whose authority this token carries. Here, a human. APIs apply this user’s permissions.
"client_id": "https://agent.example/client.json",
The OAuth client the token was issued to. This is usually the closest thing to “which agent” in a plain access token, and it identifies an application, not an individual agent run. With Client ID Metadata Documents the value is a URL the server can fetch to learn about the client.
"aud": "https://calendar.example/api",
The audience: the one API that should accept this token. Agents talk to many servers, so audience restriction (requested with the resource parameter) is what stops a token given to one server from being replayed at another.
"scope": "calendar.read",
What the user consented to. Coarse by design. For anything transaction-shaped, authorization_details (RFC 9396) is more precise.
"exp": 1794269400,
Expiry. Minutes, not days. A leaked short-lived token is a small incident.
"iat": 1794268800,
When the token was issued.
"jti": "8f1c9a52-…"
A unique identifier for this token, used for replay detection, revocation lists and correlating audit logs.
}Delegated token
Authorization server → tool → downstream API
The result of a token exchange. The user is still the subject; the act claim names who is acting for them. Nested act claims record the chain.
{"iss": "https://auth.example",
Issuer.
"sub": "user:alice",
Still Alice. Delegation keeps the original subject so that downstream permissions are hers.
"aud": "https://warehouse.example/api",
A new audience. The token the tool received was addressed to the tool; this one is addressed to the next hop.
"scope": "sales.read",
Narrower than the original grant. Each exchange is a chance to drop privileges the next hop does not need.
"exp": 1794269100,
Shorter-lived than the token it was exchanged for.
"act": {
The actor claim from RFC 8693. Its presence is the difference between delegation (“the tool, acting for Alice”) and impersonation (“Alice”). Without it the API cannot tell software from the person.
"sub": "https://tools.example/mcp",
The current actor: the party presenting this token.
"act": { "sub": "research-agent" }
A prior actor, nested. RFC 8693 says only the outermost actor is relevant to access control; nested actors are history, useful for audit. Several current drafts would make this chain richer for agents.
}}- Who is this for?
- What software is acting?
- Where is it valid?
- What may it do?
- How is possession proven?
- For how long?
Voice
How the site talks.
Say who does what
“The agent redeems the code.” Not “the code is redeemed.” Auth is about who acts; passive sentences hide exactly that.
State it, then stop
One clear sentence beats a hedge. Where something is uncertain, say what is uncertain and why, once, in the place it applies.
Name a thing once
Pick the term, define it in the glossary, and keep using it. A “grant” in one paragraph is not a “permission” in the next.
Status is a fact
“Working group draft, revision 11” is a status. “Emerging standard” is an opinion. Report what a source says and link to it.
The joke is the frame, not the filling
Who, What and I Don’t Know organize the ideas. They do not appear in every heading. A reference page earns trust by being plain.
Cautions come with a way out
Every “watch out” says what to do instead. A warning with no alternative is just worry.