OAuth 2.1 + DCR
When Require auth is on, each MCP install (each Claude / ChatGPT user,
effectively) gets its own OAuth client via Dynamic Client Registration —
no human login, no consent screen by default. With a branded consent
screen for email capture if you enable it.
The flow
Agent aimiles
│ GET /.well-known/oauth-protected-resource/... │
├──────────────────────────────────────────────────▶│
│◀──────────────────────────────────────────────────┤ PRM (RFC 9728)
│ GET /.well-known/oauth-authorization-server │
├──────────────────────────────────────────────────▶│
│◀──────────────────────────────────────────────────┤ ASM (RFC 8414)
│ POST /api/oauth/register (RFC 7591) │
├──────────────────────────────────────────────────▶│
│◀──────────────────────────────────────────────────┤ client_id (+ optional secret)
│ GET /api/oauth/authorize ?code_challenge=… │
├──────────────────────────────────────────────────▶│ (branded consent screen)
│◀──────────────────────────────────────────────────┤ 302 redirect with code
│ POST /api/oauth/token + code_verifier (PKCE) │
├──────────────────────────────────────────────────▶│
│◀──────────────────────────────────────────────────┤ access_token (1h, opaque)
│ POST /api/mcp/<pubId> Authorization: Bearer │
├──────────────────────────────────────────────────▶│ meter keyed by client_id
Standards we implement
- RFC 7591 — Dynamic Client Registration
- RFC 8414 — Authorization Server Metadata
- RFC 9728 — Protected Resource Metadata
- OAuth 2.1 — auth_code + PKCE (S256), no implicit grant
- RFC 8707 — Resource indicators (token bound to one publisher)
Token storage
We store sha256(token) only — a DB leak doesn't yield usable tokens.
Tokens are 1h; refresh tokens (30d) rotate on each use.
Per-publisher consent
The consent screen at /api/oauth/authorize renders with your logo,
primary color, and font — set on the Publisher row during onboarding.
It captures:
- Reader's email (lands on
OAuthClient.email, attributes every
subsequentMcpCallto that person) - Terms acceptance
- Optional: "Already a subscriber? Link my subscription" button if you've
configured subscription linking
Identity keying
The meter ledger uses oauth:{client_id} as the install identity when a
bearer token is present; falls back to sess:{mcp-session-id} otherwise.
Same install across multiple agent sessions = same client_id = same
meter row. That's the whole point of OAuth here.