# auth.md: agent authentication on mcp.platformpilot.ai

This file tells an agent how to get its own credentials for the MCP servers hosted at https://mcp.platformpilot.ai. Each server is its own protected resource at `https://mcp.platformpilot.ai/<server>/`, and each has an OAuth 2.1 authorization server at `https://mcp.platformpilot.ai/<server>` that fronts the PlatformPilot identity provider. OAuth-aware servers: `agents`, `apollo`, `brain`. Servers that accept only the PlatformPilot gateway key (`meta-ads`) are internal and have no self-service credential path.

## Discover

1. Call the MCP endpoint with no credentials, for example `POST https://mcp.platformpilot.ai/brain/mcp`. The response is `401` with `WWW-Authenticate: Bearer resource_metadata="https://mcp.platformpilot.ai/brain/.well-known/oauth-protected-resource"`.
2. Fetch that protected-resource metadata (RFC 9728). `authorization_servers[0]` is `https://mcp.platformpilot.ai/brain`.
3. Fetch `https://mcp.platformpilot.ai/brain/.well-known/oauth-authorization-server` (RFC 8414; `/.well-known/openid-configuration` is an alias). It lists `registration_endpoint`, `authorization_endpoint`, `token_endpoint`, `scopes_supported`, and `code_challenge_methods_supported: ["S256"]`. The copies at `https://mcp.platformpilot.ai/.well-known/...` describe the default server.

## Pick a method

- You act for a person with a PlatformPilot account: use dynamic client registration and the authorization code flow with PKCE (the only supported method).
- You have no person to authorize: there is no anonymous path and no `identity_assertion` (id-jag) exchange; the authorization-server metadata carries no `agent_auth` block and no `register_uri` for agents. Stop and ask the user to authorize.
- You are PlatformPilot infrastructure: send the `X-Gateway-Key` header (not self-service).

## Register

`POST https://mcp.platformpilot.ai/brain/oauth/register` (RFC 7591) with JSON: `client_name`, `redirect_uris`, `grant_types: ["authorization_code", "refresh_token"]`, `response_types: ["code"]`, `token_endpoint_auth_method: "none"`. The response carries `client_id`. Clients are public: no client secret is issued. Register once per server; the same registration works for later sessions.

## Claim

Open `authorization_endpoint` in the user's browser with `response_type=code`, `client_id`, `redirect_uri`, `code_challenge` (S256), `code_challenge_method=S256`, `scope=openid profile email offline_access`, and `state`. The user signs in and consents; the gateway redirects to `redirect_uri` with `code` and `state`.

## Use the credential

Exchange the code at `token_endpoint`: `grant_type=authorization_code`, `code`, `code_verifier`, `client_id`, `redirect_uri`. The response carries `access_token` and, with `offline_access`, `refresh_token`. Send `Authorization: Bearer <access_token>` on every MCP request. Renew with `grant_type=refresh_token`.

## Errors

| Status | Response | Meaning | What to do |
| --- | --- | --- | --- |
| 401 | `invalid_token` + `WWW-Authenticate` | Missing or expired token | Refresh, or restart at Discover |
| 403 | `invalid_client`, "Invalid or missing gateway key" | Server is infrastructure-only | No self-service path |
| 404 | "Unknown MCP server" | Name is not routed | Read https://mcp.platformpilot.ai/llms.txt |
| 429 | "Rate limit exceeded" | Per-organization window | Back off, then retry |
| 502 / 504 | "Backend unavailable" / "Backend timeout" | Backend is down or slow | Retry later |

## Revocation

There is no `revocation_uri`. Access tokens expire on their own; discard the refresh token to end a session. Client registrations are not revocable by the client.
