Skip to main content

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)

>
SDK:

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

  1. Claude Code first connect — no delegation in config; PKCE opens Console; stream starts after token exchange.
  2. Scope upgrade — re-run OAuth with expanded scope param when Companion needs workforce or admin tools.
  3. Org workspace — discovery on {slug}.mcp.haio.run pins registration to that org's agent-auth surface.
  4. 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

← All patterns