| # WorldMonitor β Agent Authentication (auth.md) |
|
|
| How agents authenticate with the WorldMonitor API and MCP server |
| (`https://worldmonitor.app/mcp`), per the WorkOS **auth.md** spec: |
| <https://workos.com/auth-md>. Discovery is open; data calls need a bearer token |
| or API key. |
|
|
| **Before anything else β send a descriptive `User-Agent`** (e.g. |
| `mytool/1.0 (+https://yoursite.example)`). Default HTTP-library UAs (`curl/*`, |
| `python-requests/*`, empty/short strings) may be challenged with an HTML 403 by |
| the edge firewall before your request reaches the API β a 403 does not mean the |
| endpoint is missing or your credentials are wrong. |
|
|
| ## Discover |
|
|
| Learn the auth requirements from one unauthenticated request, then follow the |
| chain: |
|
|
| 1. Call any data method without credentials; read the `WWW-Authenticate` header: |
|
|
| ``` |
| 401 Unauthorized |
| WWW-Authenticate: Bearer resource_metadata="https://worldmonitor.app/.well-known/oauth-protected-resource" |
| ``` |
|
|
| 2. `GET /.well-known/oauth-protected-resource` (RFC 9728) β the `resource` id and |
| its `authorization_servers`. |
| 3. `GET /.well-known/oauth-authorization-server` (RFC 8414) β the OAuth endpoints |
| plus the `agent_auth` block that points back here: |
|
|
| ```json |
| { "issuer": "https://worldmonitor.app", |
| "agent_auth": { |
| "skill": "https://worldmonitor.app/auth.md", |
| "register_uri": "https://worldmonitor.app/oauth/register", |
| "claim_uri": "https://worldmonitor.app/oauth/authorize", |
| "identity_types_supported": ["anonymous"], |
| "anonymous": { "credential_types_supported": ["access_token"], |
| "claim_uri": "https://worldmonitor.app/oauth/authorize" } } } |
| ``` |
|
|
| Metadata is per-host: `issuer` and endpoints match the origin you fetched |
| (`worldmonitor.app` or `api.worldmonitor.app`). |
|
|
| ## Pick a method |
|
|
| `identity_types_supported` advertises **`anonymous`** β register without |
| asserting a user identity; a human consents interactively at authorization (see |
| [Claim](#claim)). Two credentials: |
|
|
| - **`access_token`** (anonymous) β OAuth 2.1 bearer via Dynamic Client |
| Registration + authorization-code + PKCE (see [Register](#register)). For |
| interactive agents such as Claude. |
| - **`api_key`** β *not* anonymous: a signed-in user mints it from the dashboard, |
| so it carries that identity. For headless, server-to-server use. |
|
|
| **`identity_assertion` is not supported.** No pre-issued identity assertion (e.g. |
| an `urn:ietf:params:oauth:token-type:id-jag` id-jag token) is exchanged for |
| credentials β there is no identity or token-exchange endpoint. Identity is always |
| established interactively. |
| |
| ## Register |
| |
| **RFC 7591 Dynamic Client Registration** at the `register_uri`: |
| |
| ``` |
| POST /oauth/register {"client_name":"My Agent","redirect_uris":["https://claude.ai/api/mcp/auth_callback"]} |
| β 201 {"client_id":"β¦","token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"]} |
| ``` |
| |
| `redirect_uris` are allowlisted (Claude callbacks + `http://localhost` / |
| `http://127.0.0.1` on any port). Clients are public β no secret; use PKCE |
| (`S256`). **API-key path:** start at <https://worldmonitor.app/pro>, then use |
| the signed-in dashboard's API Keys settings to self-issue or revoke keys β no |
| registration call. |
| |
| ## Claim |
| |
| Anonymous agents are claimed **at authorization time**, not via a separate |
| endpoint β so `agent_auth.claim_uri` is the authorization endpoint itself. Start |
| the authorization-code flow with a PKCE challenge: |
| |
| ``` |
| GET /oauth/authorize?response_type=code&client_id=β¦&code_challenge=β¦&code_challenge_method=S256&scope=mcp |
| ``` |
| |
| The user signs in and approves; that binds the issued token to their account. For |
| API keys the claim is implicit β the key belongs to its dashboard creator. |
| |
| ## Use the credential |
| |
| Exchange the code for a bearer token, then send it on every request: |
| |
| ``` |
| POST /oauth/token grant_type=authorization_code&code=β¦&code_verifier=β¦&client_id=β¦ |
| β {"access_token":"β¦","token_type":"Bearer","expires_in":3600,"refresh_token":"β¦"} |
| |
| POST /mcp Authorization: Bearer <access_token> (or) X-WorldMonitor-Key: <api_key> |
| ``` |
| |
| The same credentials authorize the REST API. Catalog: |
| <https://worldmonitor.app/.well-known/api-catalog>. |
| |
| ## Errors |
| |
| - **401** β missing/expired/invalid credential; response carries |
| `WWW-Authenticate: Bearer β¦ resource_metadata="β¦"` β restart at |
| [Discover](#discover). Over MCP the JSON-RPC code is `-32001`. |
| - **400** β `invalid_redirect_uri` (outside the allowlist), |
| `unsupported_grant_type` (only `authorization_code` / `refresh_token` / |
| `client_credentials`), or `invalid_grant` (expired/consumed/revoked token). |
| - **429** β registration/token endpoints are rate-limited per IP; back off. |
|
|
| ## Revocation |
|
|
| - **Expiry** β access tokens last 1 hour, refresh tokens 7 days; let them lapse |
| to de-authorize an agent. |
| - **User revoke** β a signed-in user revokes an agent from the dashboard's API |
| Keys or Connected MCP Clients settings; start at <https://worldmonitor.app/pro>. |
| The token is then rejected with `401` |
| / `invalid_grant`. |
| - **Refresh rotation** β refresh tokens rotate on every use with token-family |
| revocation, so a stolen token dies once the real client next refreshes. |
|
|
| No standalone machine revocation endpoint β revoke via the dashboard or let the |
| credential expire. |
|
|