OAuth 2.1 flow
Every generated server is an OAuth 2.1 Resource Server. The control plane is the Authorization Server. Dynamic Client Registration, PKCE, and Resource Indicators per the 2025 MCP authorization spec.
Standards we follow
- OAuth 2.1 draft (
draft-ietf-oauth-v2-1) — no implicit, mandatory PKCE - RFC 8414 — Authorization Server Metadata at
/.well-known/oauth-authorization-server/oauth - RFC 9728 — Protected Resource Metadata at
/.well-known/oauth-protected-resource/<server-path> - RFC 8707 — Resource Indicators (audience binding)
- RFC 7591 — Dynamic Client Registration
End-to-end walkthrough
First request from a fresh client to a fresh server is unauthenticated. The server replies with a 401 plus a WWW-Authenticate header pointing to its resource metadata.
$ curl -i http://localhost:4103/mcp -d '{}' -H 'content-type: application/json'
HTTP/1.1 401 Unauthorized
www-authenticate: Bearer resource_metadata="http://localhost:4103/.well-known/oauth-protected-resource/mcp"
content-type: application/json
{"error":"unauthorized"}The client fetches that resource metadata, sees the authorization server, then fetches the AS metadata to discover registration, authorize, token and JWKS endpoints.
$ curl http://localhost:4103/.well-known/oauth-protected-resource/mcp
{
"resource": "http://localhost:4103/mcp",
"authorization_servers": ["http://localhost:4000/oauth"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["mcp:read"]
}The client registers itself dynamically. No human in the loop, no preconfigured client IDs. Each AI surface gets its own ephemeral identity.
POST /oauth/register HTTP/1.1
{
"client_name": "Claude Desktop",
"redirect_uris": ["claude://oauth/callback"],
"token_endpoint_auth_method": "none",
"resource": "http://localhost:4103/mcp"
}
201 Created
{ "client_id": "bmm_8aee2fe0…", "redirect_uris": […] }Authorization Code with PKCE. The user gives consent, the AS returns a one-time code, the client exchanges it for an RS256-signed JWT bound to the resource (audience).
POST /oauth/token HTTP/1.1
{
"grant_type": "authorization_code",
"code": "4uNk_SCU8…",
"code_verifier": "riSU-w1DT…",
"client_id": "bmm_8aee2fe0…",
"redirect_uri": "claude://oauth/callback",
"resource": "http://localhost:4103/mcp"
}
200 OK
{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "pQR…"
}Subsequent /mcp calls carry the JWT. The runner verifies the signature against the AS's JWKS, checks the iss, the aud(RFC 8707 — must match the runner's MCP resource URL), and the expiry. No token passthrough; the runner never forwards the client's token to a downstream API.
Why this matters
Without audience binding, a token issued for one customer's MCP server could be replayed against another customer's server. RFC 8707 closes that. Without PKCE, a public OAuth client on a desktop is exposed to interception of the authorization code. OAuth 2.1 closes that.