OAuth PKCE for hosted MCP
Overview
Hosted MCP clients that cannot embed a pre-minted delegation (Claude Code, some IDE plugins) use OAuth 2.0 with PKCE against the Worker at mcp.haio.run. You approve scopes in the Console; the Worker exchanges the authorization code for a session-scoped delegation JWT via POST /v1/delegation-tokens on the API.
Passport remains sovereign: the human chooses scopes; the token is time-bound and revocable like any other HUMΛN delegation. The Worker only stores PKCE state and refresh handles in KV — canonical scope and identity logic lives at api.haio.run.
Why PKCE + Passport delegation?
- No static secrets in the AI client — short-lived access tokens instead of long-lived API keys in config files
- Explicit consent — Console shows requested scopes before mint
- PKCE — public clients prove possession without a client secret leak
- Refresh — opaque refresh tokens re-mint access tokens without re-prompting every hour
Think of it like: "Sign in with HUMΛN" for your MCP server — same delegation model as SDK apps, different transport.
OAuth flow (shipped)
Implementation: apps/mcp-worker/src/oauth.ts
AI client mcp.haio.run Console
│ │ │
│ GET /oauth/auth?client_id& │ │
│ redirect_uri&code_challenge& │ │
│ state&scope │ │
│──────────────────────────────────►│ 302 → console/oauth/mcp-authorize
│ │─────────────────────────────►│ user approves
│ │◄ POST /oauth/console-grant ──│
│◄ redirect_uri?code&state ─────────│ │
│ │ │
│ POST /oauth/token │ │
│ grant_type=authorization_code │ │
│ code + code_verifier │ │
│──────────────────────────────────►│ POST /v1/delegation-tokens │
│◄ access_token + refresh_token ────│ │
│ │ │
│ GET /sse or POST /mcp │ │
│ Authorization: Bearer … │ │
│──────────────────────────────────►│ │
| Method | Path | Purpose |
|---|---|---|
| GET | /oauth/auth |
Start PKCE; store challenge in KV (10 min TTL) |
| POST | /oauth/console-grant |
Console callback (server-to-server Bearer) |
| POST | /oauth/token |
Exchange authorization_code or refresh_token |
| GET | /.well-known/oauth-authorization-server |
Discovery (agent_auth.register_uri, mcp_endpoint) |
| GET | /auth.md |
Prose registration guide per org/personal host |
Delegation mint body (Worker → API):
{
"to_passport_id": "<granted_passport_id>",
"scopes": ["companion:chat", "kb:read:public"],
"surface": "mcp_hosted",
"ttl_seconds": 3600
}
Try it
1) Generate PKCE verifier + challenge (client-side)
2) Use the access token on MCP transport
export HUMAN_DELEGATION_TOKEN="$ACCESS_TOKEN"
curl -s -X POST 'https://mcp.haio.run/mcp' \
-H "Authorization: Bearer $HUMAN_DELEGATION_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Org-scoped OAuth discovery
curl -s "https://YOUR_ORG.mcp.haio.run/.well-known/oauth-authorization-server" | jq '.agent_auth'
# → register_uri, mcp_endpoint, mcp_sse_endpoint, org_slug
Agent registration (alternative to interactive OAuth): POST https://api.haio.run/v1/agent-auth?org={slug} — see /auth.md on the same host.
Use cases
- Claude Code first connect — no delegation in config; PKCE opens Console; stream starts after token exchange.
- Scope upgrade — re-run OAuth with expanded
scopeparam when Companion needs workforce or admin tools. - Org workspace — discovery on
{slug}.mcp.haio.runpins registration to that org's agent-auth surface. - Token refresh — long-running SSE sessions refresh access tokens without kicking the user back to Console every hour.
Security considerations
DO
Use S256 code_challenge_method only (Worker rejects plain)
Treat authorization codes as single-use (Worker deletes PKCE state after exchange)
Store refresh tokens securely — they re-mint delegations
Revoke delegations in Console when offboarding
DON'T
Do not embed OAUTH_CLIENT_SECRET in public AI clients (Console grant is server-to-server only)
Do not skip scope review — mint minimal packages for MCP exploration
Do not reuse code_verifier across flows
See also
- Delegate Access · Scope vocabulary (alias)
- Hosted MCP transport
- Org MCP endpoint
- Community: MCP Passport delegation trust
- Code:
apps/mcp-worker/src/oauth.ts· API:POST /v1/delegation-tokens·POST /v1/agent-auth