Back to Worldmonitor

OAuth 2.1 Server

docs/api-oauth.mdx

2.10.04.0 KB
Original Source

WorldMonitor runs a minimal OAuth 2.1 authorization server whose only client-facing purpose today is granting access to the MCP server at /api/mcp. It implements:

  • RFC 7591 — Dynamic Client Registration
  • RFC 7636 — PKCE (required, S256 only)
  • RFC 8414 — Authorization Server Metadata
  • RFC 9728 — Protected Resource Metadata

Discovery

URLPurpose
/.well-known/oauth-authorization-serverAS metadata (endpoints, supported grants, PKCE methods)
/.well-known/oauth-protected-resourceResource metadata (authorization servers, scopes)

/.well-known/oauth-protected-resource currently advertises the public resource scope mcp. Pro authorization-code grants return the internal scope value mcp_pro; legacy API-key grants and client_credentials return mcp.

Endpoints

POST /api/oauth/register

Dynamic Client Registration. Returns a client_id (public clients, no secret).

Request:

json
{
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "client_name": "Claude Desktop",
  "token_endpoint_auth_method": "none"
}

Response:

json
{
  "client_id": "7c3b08f0-0c1f-4a9c-8a52-69e13d2a5d5e",
  "client_name": "Claude Desktop",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

Redirect URI allowlist: only these prefixes are accepted:

  • https://claude.ai/api/mcp/auth_callback
  • https://claude.com/api/mcp/auth_callback
  • http://localhost:<port> / http://127.0.0.1:<port> — any port

Rate limit: 5 registrations / 60 s / IP.

Client TTL: 90 days sliding (every successful token exchange refreshes).

GET /api/oauth/authorize

Starts the OAuth flow. Renders a consent page that redirects to Clerk for sign-in, then issues an authorization code bound to the caller's PRO entitlement.

Required query params:

  • response_type=code
  • client_id — from DCR
  • redirect_uri — must match the one registered
  • code_challenge — PKCE S256
  • code_challenge_method=S256
  • state — opaque
  • scope (optional)

Code TTL: 10 minutes. Single-use (atomic GETDEL on exchange).

POST /api/oauth/token

Exchanges an authorization code for an access token, or refreshes an existing token.

Grant type: authorization_code:

grant_type=authorization_code
code=<from /authorize>
code_verifier=<PKCE>
client_id=<from DCR>
redirect_uri=<same as /authorize>

Response:

json
{
  "access_token": "6f13d8fa-89b6-4a02-a527-7f6f61a2df55",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "6ba38313-9a4d-4797-9186-3d2c3c1cfe02",
  "scope": "mcp_pro"
}

Grant type: refresh_token:

grant_type=refresh_token
refresh_token=<from previous exchange>
client_id=<from DCR>

Rate limit: 10 token requests / minute. The limiter is keyed by client_secret hash for client_credentials, by client_id when present (authorization_code and refresh_token), and falls back to caller IP only when neither identifier is available.

Token TTLs:

  • Access token: 1 hour
  • Refresh token: 7 days

Access and refresh tokens are opaque UUIDs. All token-endpoint responses include Cache-Control: no-store, Pragma: no-cache.

Using tokens

Pass the access token on every MCP request:

Authorization: Bearer 6f13d8fa-89b6-4a02-a527-7f6f61a2df55

Tokens are bound to the user's account and re-check entitlement on every call — a downgrade revokes access on the next request.

Error responses

Per RFC 6749 §5.2:

json
{ "error": "invalid_grant", "error_description": "..." }

Common errors: invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_scope.