← Back to Patterns

OAuth PKCE for hosted MCP

Availablemcp•intermediate

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 …         │                              │
        │──────────────────────────────────►│                              │

    MethodPathPurpose
    GET/oauth/authStart PKCE; store challenge in KV (10 min TTL)
    POST/oauth/console-grantConsole callback (server-to-server Bearer)
    POST/oauth/tokenExchange authorization_code or refresh_token
    GET/.well-known/oauth-authorization-serverDiscovery (agent_auth.register_uri, mcp_endpoint)
    GET/auth.mdProse registration guide per org/personal host
    Delegation mint body (Worker → API):

    {
      "to_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

  • Claude Code first connect — no delegation in config; PKCE opens Console; stream starts after token exchange.
  • Scope upgrade — re-run OAuth with expanded scope param when Companion needs workforce or admin tools.
  • Org workspace — discovery on {slug}.mcp.haio.run pins 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
  • Same route in the other SDKs

    The TypeScript tab above is the contract. These calls hit that same route.

    >
    SDK: