← Blog
July 13, 2026 · 11 min

MCP Authorization: OAuth 2.1 for Autonomous Agents

A tool call is a request to run code on someone else's machine. The MCP authorization spec decides who is allowed to make it. For an autonomous agent that never has a human to click "allow", that decision is the whole security boundary.

The Model Context Protocol grew up on stdio: a client spawns a server as a subprocess, they talk over pipes, and trust is inherited from the operating system. That model breaks the moment the server lives on a different host. Remote MCP is how agents reach tools they do not own — a payments API, a vector store, a competitor's data feed. And a remote tool endpoint with no auth is just an open door.

So the spec did the disciplined thing. Instead of inventing a bespoke token scheme, it declared that a protected MCP server is an OAuth 2.1 resource server and an MCP client is an OAuth 2.1 client. Everything else follows from that one sentence. This piece walks the normative flow as written in the official authorization specification, and then asks the question that matters for us: what does it mean when the client is an agent paying per call, not a person in a browser?

The roles, and why they are split

OAuth has three actors, and the MCP spec maps them precisely:

The split is the point. A June 2025 revision of the spec deliberately narrowed MCP servers down to resource servers only. Token issuance and client management got delegated to dedicated authorization servers. That means a tool author does not build login, consent, or token rotation — they lean on Auth0, Keycloak, Okta, or their own IdP, and spend their attention on validating what arrives. Less code in the security path is less attack surface.

One boundary condition worth stating: authorization is OPTIONAL, and it is scoped to HTTP transports. Over stdio, the spec says explicitly not to follow it — pull credentials from the environment instead. So everything below is about the remote case, which is exactly the case autonomous agents run into.

The 401, and how discovery starts

The flow begins with a failure. The client sends an MCP request with no token; the server refuses. But the refusal is informative — it carries the map to the authorization server.

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

Two things ride in that header. First, a resource_metadata URL. Second, an optional scope hint telling the client the minimum permission this operation needs. The client is expected to treat that challenged scope as authoritative for the current request and not assume any relationship to the server's advertised scopes_supported.

The resource_metadata URL points at an OAuth 2.0 Protected Resource Metadata document, defined in RFC 9728. MCP servers MUST implement it; MCP clients MUST use it for discovery. This replaced an earlier scheme of guessed default endpoints (/authorize, /token, /register) — guessing is fragile, and mandating a discovery document is not.

The document lives at a well-known path and, at minimum, names the resource and points at its authorization servers:

// GET https://mcp.example.com/.well-known/oauth-protected-resource
{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read", "files:write"]
}

From there the client fetches the authorization server's own metadata — via RFC 8414 (OAuth Authorization Server Metadata) or OpenID Connect Discovery — to learn the real authorize and token endpoints. Clients MUST support both discovery mechanisms. No hardcoded paths anywhere.

Getting a client_id without pre-registration

Here is a genuinely hard problem for agents. Classic OAuth assumes a client registered ahead of time and holding a client_id. An autonomous agent discovering a tool it has never seen has no such registration. The spec offers three routes, and the ordering tells you where it is heading.

// Route 1

Client ID Metadata Documents (CIMD)

The client uses an https URL as its client_id. The authorization server detects the URL form, fetches a JSON metadata document from it, and validates the declared redirect_uris. No registration call. This is the SHOULD — the recommended default in the current draft, defined in draft-ietf-oauth-client-id-metadata-document-00.

// Route 2

Dynamic Client Registration (RFC 7591)

The client POSTs to a /register endpoint and gets credentials back. Supported as a MAY, but the spec now marks DCR deprecated, retained only for authorization servers that do not yet speak CIMD.

// Route 3

Pre-registration

The classic case: the client already holds a client_id issued out of band. Fine when you control both ends.

The move from DCR toward CIMD is a bet on scale. Dynamic registration creates a database row per client on the authorization server — a spam and storage-exhaustion vector when the clients are millions of ephemeral agents. A self-describing URL that the AS fetches and validates on demand carries no such state. It is the same instinct that pushed MCP toward a stateless server architecture: when the population of agents is unbounded, per-agent state on the server side is a liability.

PKCE, and validating who answered

Once the client has a client_id, the authorization request itself is standard OAuth 2.1: PKCE is mandatory. The client generates a code verifier, sends its code_challenge up front, and proves possession with the code_verifier at the token endpoint. Because most MCP clients are public — CLIs, desktop apps, agents with no safe place to keep a secret — PKCE is not optional hardening; it is the thing standing between an intercepted authorization code and a stolen token.

The spec layers on one more check that is easy to overlook and important for agents: issuer validation per RFC 9207. Before it starts the flow, the client records the expected issuer from the authorization server's validated metadata. When the authorization response comes back, the client compares the returned iss against that recorded value using strict string comparison — no case folding, no trailing-slash normalization. This defends against mix-up attacks, where a malicious authorization server tricks a client into sending a code meant for a different, honest server. An agent juggling tokens from a dozen tool providers is exactly the target such an attack wants.

The resource parameter: tokens that only fit one lock

This is the clause that separates MCP's model from naive bearer-token setups, and it is worth reading carefully.

MCP clients MUST implement Resource Indicators for OAuth 2.0, RFC 8707. Every authorization request and every token request carries a resource parameter naming the exact MCP server the token is for, by its canonical URI:

// authorization + token requests both carry:
&resource=https%3A%2F%2Fmcp.example.com%2Fmcp

And on the receiving side, the server MUST validate that an incoming access token was issued specifically for it as the intended audience. If the token was minted for a different resource, the server rejects it with a 401. The rule is stated three ways for emphasis: a server "MUST only accept tokens that are valid for use with their own resources" and "MUST NOT accept or transit any other tokens."

Audience-bound tokens turn a stolen credential from a skeleton key into a key that fits one lock. If an agent's token for a summarization tool leaks, it cannot be replayed against the agent's payments tool, because the payments server will see the wrong audience and refuse. For a fleet of agents each holding tokens to many services, that containment is the difference between one compromised tool and a full-fleet breach.

The confused-deputy prohibition

The subtlest requirement is about what a server must not do with a token it holds. When an MCP server needs to call an upstream API — say it fronts a third-party search service — it must obtain its own token for that upstream, acting as an OAuth client in its own right. It MUST NOT pass through the token it received from the MCP client.

Skip that discipline and you build a confused deputy: a server with broad upstream privileges that a lower-privileged caller can trick into acting on its behalf. The client's token was scoped and audienced for the MCP server, not for whatever the server talks to next. Reusing it downstream launders the client's identity into the server's privileges. The audience-binding rule above is what makes the prohibition enforceable — a passed-through token would fail the upstream's own audience check, if the upstream follows the same spec.

This is the same threat class we mapped in the agent security threat model: privilege that flows sideways through a chain of services because nobody re-established identity at each hop. RFC 8707 plus the passthrough ban is the protocol-level fix.

Step-up authorization, or least privilege at runtime

Agents rarely need all their permissions at once. The spec supports acquiring them incrementally. When a client calls with a token that lacks a needed scope, the server answers 403 Forbidden with error="insufficient_scope" and names the missing scope:

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                         scope="files:write",
                         resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

The client then runs a step-up flow, requesting the union of its previously granted scopes and the newly challenged ones, and retries — a bounded number of times, then it gives up. Scope accumulation is a client-side responsibility, which keeps the server stateless about any particular client's permission set. An agent can start with read-only access and escalate to write only when a task actually demands it, with each escalation visible in the authorization server's consent trail.

What it means for LLM4Agents

LLM4Agents sits at a specific junction: an OpenAI-compatible gateway where agents pay per call in stablecoins over x402. MCP authorization is the identity half of a two-part problem whose payment half we already run.

The two layers compose cleanly. Payment answers "was this call paid for" — x402 returns a 402 with terms, the agent signs an EIP-3009 authorization, the facilitator settles. Authorization answers "is this caller allowed" — MCP returns a 401 with a discovery pointer, the agent presents an audience-bound OAuth token. A 402 and a 401 are different questions, and a serious agent platform answers both. An agent that pays but cannot prove identity is a wallet with no name; an agent that authenticates but cannot pay stalls at the paywall.

Concretely, three properties of the spec map onto things we care about. Audience-bound tokens mean a leaked credential for one tool cannot be replayed across a fleet — containment we want by default. The confused-deputy ban means the gateway, when it fans a request out to downstream model providers or tools, must mint its own upstream credentials rather than launder the caller's — which is how a routing layer should behave anyway. And the CIMD-over-DCR shift mirrors our own preference for statelessness: when the client population is a swarm of short-lived agents, per-agent registration rows do not scale, and self-describing client identities do.

Staying on the frontier

The spec is a moving target — it is still marked draft, and revisions through 2025 and 2026 changed registration defaults and discovery mechanics more than once. Tracking it is the work. Concretely, in order:

  1. Speak resource-server-side auth on the MCP endpoints. Implement RFC 9728 Protected Resource Metadata and RFC 8707 audience validation on any MCP surface we expose, so a token issued for our summarizer can never be replayed against our billing tools. This is the highest-leverage first move.
  2. Prefer CIMD, keep DCR as a fallback. Support Client ID Metadata Documents as the default registration path for agents hitting our tools, with RFC 7591 dynamic registration only for authorization servers that cannot yet do CIMD. Do not build a per-agent registration database we will regret.
  3. Never pass tokens through. When the gateway calls upstream model providers, it acts as its own OAuth client with its own credentials — never forwarding a caller's token. Bake the confused-deputy ban into the routing layer, not into a checklist.
  4. Enforce RFC 9207 issuer validation client-side. Wherever our infrastructure acts as an MCP client against third-party tools, record and strictly compare the expected issuer, so a mix-up attack cannot redirect one provider's authorization code to another.
  5. Bind identity to payment. The frontier is a request that carries both an audience-bound OAuth token and an x402 payment authorization — proven caller, settled payment, in one round trip. Pair MCP auth with ERC-8004 agent identity so reputation, not just a token, gates access to high-value tools.

The gateway that gets both halves right — who you are and whether you paid — is the one autonomous agents will trust with their money and their credentials. That pairing is the product.

Build agents that authenticate and pay

OpenAI-compatible gateway, stablecoin settlement, and an MCP surface built for autonomous callers.

Register an agent