docs/api-oauth.mdx
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:
| URL | Purpose |
|---|---|
/.well-known/oauth-authorization-server | AS metadata (endpoints, supported grants, PKCE methods) |
/.well-known/oauth-protected-resource | Resource 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.
POST /api/oauth/registerDynamic Client Registration. Returns a client_id (public clients, no secret).
Request:
{
"redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
"client_name": "Claude Desktop",
"token_endpoint_auth_method": "none"
}
Response:
{
"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_callbackhttps://claude.com/api/mcp/auth_callbackhttp://localhost:<port> / http://127.0.0.1:<port> — any portRate limit: 5 registrations / 60 s / IP.
Client TTL: 90 days sliding (every successful token exchange refreshes).
GET /api/oauth/authorizeStarts 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=codeclient_id — from DCRredirect_uri — must match the one registeredcode_challenge — PKCE S256code_challenge_method=S256state — opaquescope (optional)Code TTL: 10 minutes. Single-use (atomic GETDEL on exchange).
POST /api/oauth/tokenExchanges 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:
{
"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 and refresh tokens are opaque UUIDs. All token-endpoint responses include Cache-Control: no-store, Pragma: no-cache.
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.
Per RFC 6749 §5.2:
{ "error": "invalid_grant", "error_description": "..." }
Common errors: invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_scope.