/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:
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=codeclient_id— from DCRredirect_uri— must match the one registeredcode_challenge— PKCE S256code_challenge_method=S256state— opaquescope(optional)
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:
refresh_token:
client_credentials (operator-issued enterprise keys only):
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
Cache-Control: no-store, Pragma: no-cache.
Using tokens
Pass the access token on every MCP request: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:invalid_request, invalid_client, invalid_grant, unsupported_grant_type, invalid_scope.