Skip to main content
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
  • RFC 9207 — Authorization Server Issuer Identification

Discovery

Each transport path has its own document because a client accepts an advertised resource only when the URL it called sits under that path. The 401 challenge names the document for the path the client actually called. Clients that build that URL themselves instead of following the challenge find it there; both documents are served, so a client that discovered the origin-wide one keeps working. /.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:
Response:
Redirect URI allowlist: http://localhost:<port> / http://127.0.0.1:<port> (any port), or an exact match for a hosted MCP client callback listed in MCP server → Redirect URI allowlist. Any other entry returns 400 invalid_redirect_uri for the whole registration. At most 8 redirect_uris per registration; more returns 400 invalid_request. 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 entitlement. The Pro sign-in leg of the consent flow is served by the sibling GET /oauth/authorize-pro (HTML; not called directly by clients). It admits Pro subscribers and confirmed free accounts; a provider-confirmed lapse is reclassified onto the free-account path, so authorization continues with a restricted, allowance-metered token. Retryable verification failures return 503, while genuinely insufficient states such as an expired or disabled paid row without a confirmed lapse render the Pro-required page. 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)
Authorization response: redirects to redirect_uri with code, state (when one was sent), and iss. iss is the issuer from the AS metadata of the host where the flow started (RFC 9207); the metadata advertises authorization_response_iss_parameter_supported: true. Code TTL: 10 minutes. Single-use (atomic GETDEL on exchange). The Use API key instead option accepts dashboard-issued wm_ keys and enterprise keys. Dashboard-key OAuth tokens retain the key owner’s identity and use the same MCP entitlement and quota checks as X-WorldMonitor-Key requests. Only the key hash is stored; bearer use revalidates the key through the shared validator, whose revocation cache lasts up to 60 seconds. API-key creation requires apiAccess; MCP calls follow mcpAccess and the applicable account allowance. Pro users can use sign-in without creating an API key. Company Monitoring keys with restricted scopes are not accepted by this generic MCP flow.

POST /api/oauth/token

Exchanges an authorization code for an access token, or refreshes an existing token. Grant type: authorization_code:
Response:
Grant type: refresh_token:
Grant type: client_credentials (operator-issued enterprise keys only):
Validates the client_secret against the deployment’s operator key allowlist and returns a bearer token with scope: "mcp" and the standard 3600 s TTL. Not available for dashboard wm_… keys — those are sent directly as X-WorldMonitor-Key instead. 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. All three grant types fail open when the limiter is unconfigured or throws; the response then carries X-RateLimit-Mode: degraded (listed in Access-Control-Expose-Headers) so operators and cross-origin clients can tell that traffic apart from healthy limiter grants. A Redis storage outage still fails token persistence closed. 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:
Tokens are bound to the user’s account and re-check entitlement on every call. A provider-confirmed lapse removes paid capability but keeps the OAuth identity on the restricted, allowance-metered free-account path; an expired or disabled paid entitlement without that confirmed lapse, or another genuinely insufficient non-free state, is denied on the next request.

Error responses

Per RFC 6749 §5.2:
Common errors: invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_scope.