sauble.ai

Primer on MCP Authorization

Alice asks her Claude Code instance to check on example.com's network — and Claude connects to AcmeNetworks' MCP Telemetry Server using her corporate identity, with exactly the permissions her admin enabled. No pre-registration, no shared secrets, no new password. Third in the series, building on the OAuth 2.0/OIDC and Headless OAuth primers.

Varma Chanderraju · Sauble Engineering · July 2026

Series: 1 · OAuth 2.0 & OIDC  —  2 · Headless OAuth 2.0  —  3 · MCP Authorization

Part 1 · The scenario

Same company, new player: an AI agent joins the cast

The setup extends the first primer's world. AcmeNetworks.com is a SaaS that provides telemetry services for example.com; employees access it with their corporate identity (Acme's authorization server federates to example.com's Okta, which federates to Microsoft — the full chain from Primer 1). Acme has now shipped an MCP Telemetry Server at mcp.acmenetworks.com/mcp, so customers' AI tools can query telemetry directly. Alice wants her Claude Code instance to use it.

Claude Code
MCP client — OAuth 2.1 client

Runs on Alice's laptop. Speaks MCP to servers it has never seen before — which is the defining constraint of this whole primer. Acme has never heard of this particular client instance.

Acme MCP Telemetry Server
MCP server — OAuth 2.1 resource server

mcp.acmenetworks.com/mcp. Exposes tools like get_device_telemetry and list_alerts. Validates Bearer tokens; never logs anyone in itself — exactly like the Telemetry API of Primer 2.

Acme Authorization Server
authorization server

auth.acmenetworks.com — Acme's own token mint, the same one behind its web app. For example.com users it federates upstream: Okta → Microsoft Entra, precisely Primer 1's chain.

Alice + her browser
resource owner

Appears for one brief moment mid-flow — to authenticate with her corporate identity and approve. Everything else is machinery she never sees.

The punchline, up front: when this flow finishes, Claude Code holds a token that (a) identifies Alice of example.com, (b) works only at Acme's MCP server and nowhere else, and (c) carries only the permissions example.com's admin enabled for her in Acme's tenant. Alice typed her password once — at Microsoft, as always. Claude Code was never pre-registered with Acme. Every piece of that was set up by the machinery of the two previous primers, plus a discovery layer MCP adds on top.

Part 2 · What MCP adds

The bootstrapping problem

Every flow so far had one silent prerequisite: somebody configured the client beforehand. AcmeNetworks' developers registered their web app in Okta by hand — copied a client_id, uploaded keys, whitelisted a redirect URI. That works when one app talks to one identity provider it was built for.

An MCP client breaks that assumption. Claude Code is general-purpose: today Alice points it at Acme's telemetry server, tomorrow at three other MCP servers from vendors nobody predicted. Pre-registering with each is impossible. So MCP's authorization spec — a profile of OAuth 2.1, not a new protocol — must answer three bootstrap questions at runtime:

  1. Who protects this server? → Protected Resource Metadata (RFC 9728)
  2. How do I talk to that protector? → Authorization Server Metadata (RFC 8414 / OIDC Discovery)
  3. How do I introduce myself without an appointment? → Client ID Metadata Documents (with Dynamic Client Registration as the deprecated fallback)

Everything else — PKCE, authorization codes, Bearer tokens, refresh tokens, federation — is exactly the machinery of the first two primers. The role mapping is exact:

OAuth 2.1 roleIn Primers 1–2In this primer
ClientAcme web app / acmectlClaude Code (MCP client)
Resource serverTelemetry APIAcme MCP Telemetry Server
Authorization serverOktaAcme AS — federating to Okta → Entra behind the scenes
Resource ownerAliceAlice, unchanged
GrantAuth code + PKCE / client credentialsAuth code + PKCE (PKCE mandatory — this is 2.1)

Scope note: MCP authorization applies to HTTP-based transports — remote servers like Acme's. Local MCP servers spawned over stdio explicitly do not use this flow; they read credentials from the environment (an API key in an env var), because a subprocess on your own machine has no trust boundary to negotiate. Authorization is optional in the spec — but a server holding customer telemetry will obviously require it.

Part 3 · The three self-describing documents

Discovery: nobody knows anybody, everybody publishes

MCP's answer to bootstrapping is symmetry: each party publishes a machine-readable description of itself at a well-known URL. The resource server describes its protector; the authorization server describes its endpoints; the client describes its own identity. Three documents, three directions of trust:

Protected Resource Metadata

mcp.acmenetworks.com/.well-known/oauth-protected-resource

The MCP server says: "here is who protects me." Mandatory (RFC 9728). This is how the client learns which authorization server to talk to — it never guesses.

{
 "resource":
  "https://mcp.acmenetworks.com/mcp",
 "authorization_servers":
  ["https://auth.acmenetworks.com"],
 "scopes_supported":
  ["telemetry:read","alerts:read"]
}

Authorization Server Metadata

auth.acmenetworks.com/.well-known/oauth-authorization-server

The AS says: "here is how to work with me." RFC 8414, or OIDC Discovery (Primer 1's openid-configuration) — clients must support both.

{
 "issuer":
  "https://auth.acmenetworks.com",
 "authorization_endpoint": "…/authorize",
 "token_endpoint": "…/token",
 "code_challenge_methods_supported":
  ["S256"],
 "client_id_metadata_document_supported":
  true
}

Client ID Metadata Document

claude.ai/.well-known/claude-code-client.json (illustrative)

The client says: "here is who I am." Its client_id is this URL — the AS dereferences it and caches. Registration becomes a fetch. This replaces Dynamic Client Registration (RFC 7591), which MCP's authorization profile deprecates but retains for compatibility.

{
 "client_id": "https://claude.ai/
   .well-known/claude-code-client.json",
 "client_name": "Claude Code",
 "redirect_uris":
  ["http://127.0.0.1:3000/callback",
   "http://localhost:3000/callback"],
 // loopback URIs: the port may vary at
 // authorization time (RFC 8252 §7.3)
 "grant_types":
  ["authorization_code","refresh_token"],
 "token_endpoint_auth_method": "none"
}

Client identification has a defined priority: pre-registered credentials if the client already holds them for this AS → CIMD if the AS advertises support → Dynamic Client Registration as the legacy fallback → asking the user to paste client details. Note token_endpoint_auth_method: none in the client document: Claude Code on Alice's laptop is a public client — it can't hold a client_secret (anything shipped to a laptop is extractable). Its protections are PKCE plus the exact-redirect and audience rules — which is precisely why OAuth 2.1 made PKCE mandatory.

Part 4 · First contact, animated

Claude Code meets a server it has never seen

Alice runs claude mcp add --transport http acme-telemetry https://mcp.acmenetworks.com/mcp and asks Claude about her network. Watch the three discovery documents do their work, then the familiar Primer-1 machinery take over. Dashed arrows are front channel (via a browser), solid are back channel:

Count what Alice did: she typed a URL into a config command, clicked once in a familiar corporate login (her Okta/Microsoft session likely made even that instant), and approved one consent screen. Everything else — six HTTP exchanges of pure discovery and cryptography — was automatic. That ratio (one human moment, full mutual verification) is what the spec's ceremony buys.

Part 5 · Authorization in action

Where Alice's permissions actually come from

Connecting is only half the story. Alice is in — but what may she do? Two independent gates decide, and it's worth keeping them distinct:

Gate 1 — Entitlements set by the admin

In Acme's SaaS console, example.com's tenant admin assigned Alice a role: Network Analyst → may read telemetry and alerts, may not change device configs. This lives in Acme's tenant database, decided long before any token existed. Alice cannot consent her way past it.

Gate 2 — Scopes carried by the token

The access token's scp claim records what this particular client session was granted: the intersection of what Claude Code requested and what Gate 1 allows. Even a fully entitled user can hold a narrowly scoped token — least privilege per session.

The token Claude Code received in the previous diagram encodes both gates plus the audience binding:

ClaimValueMeaning
subalice@example.comWho — proven via Okta → Microsoft, exactly Primer 1
tidexample-comWhich customer tenant — data isolation in a multi-tenant SaaS
audhttps://mcp.acmenetworks.com/mcpWhere it works — this MCP server and nowhere else
scptelemetry:read alerts:readWhat it permits — Gate 1 ∩ Gate 2
exp+1 hourHow long — after which the refresh token (if the AS issued one) takes over silently

(That table shows an illustrative decoded JWT — the contract between the authorization server and the resource server. The client never inspects these claims: to Claude Code the access token is an opaque string, and an AS that issued opaque tokens validated via introspection would work identically.)

Watch the gates operate — a successful tool call, a denied one (with MCP's step-up authorization attempting to help and Gate 1 having the final word), and a silent token refresh:

The step-up dance is a feature, not a failure. The 403 + insufficient_scope challenge tells the client exactly which scope the operation needs, and the client re-authorizes requesting the union of old and new scopes — so permissions grow incrementally instead of every client demanding everything upfront. But consent can only narrow, never expand: when the authorization server checked Gate 1 and found example.com's admin hadn't granted config:write, no amount of Alice-clicking-Approve could mint that scope. Admin entitlements bound user consent — the same principle as admin pre-consent in Primer 1, inverted.

Part 6 · Why the ceremony

The threat model: one client, many strangers

Classic OAuth deployments had one app talking to one IdP it trusted by construction. An MCP client talks to many servers, some of which may be malicious — Alice might add a sketchy MCP server tomorrow. Each mandatory rule in the spec closes a specific door:

Audience binding RFC 8707
The client sends resource= in both the authorize and token requests; the server must verify aud is itself. Without it, a malicious MCP server that received Alice's token could replay it against her other MCP servers — the classic confused-deputy move. With it, a token for Acme is inert everywhere else.
No token passthrough
The MCP server must accept only tokens minted for it, and must never forward the client's token to upstream APIs. If Acme's MCP server needs to call Acme's internal services as Alice, the right shape is Primer 2's token exchange (RFC 8693) — mint a new token, keep the audit chain, never launder the original.
Issuer validation RFC 9207
The client records which AS it expects before opening the browser, and checks the iss parameter on the callback. Defeats mix-up attacks where one rogue authorization server answers a flow another one started — a real risk when a client juggles several ASes at once.
PKCE, mandatory
Claude Code is a public client with a localhost redirect — the code crosses a browser and a loopback port. PKCE (Primer 1's glossary) makes an intercepted code unredeemable. OAuth 2.1 requires it even for confidential clients.
Bearer discipline RFC 6750
The token rides the Authorization: Bearer header on every single request — and never, ever in a URL query string (URLs leak: logs, history, referrers — the same reason Primer 1 kept tokens off the front channel).
Validated discovery
Every metadata document arrives over HTTPS from the domain it describes — PRM and AS metadata at well-known paths (the PRM's location is also handed over directly in the 401's WWW-Authenticate), CIMD at whatever HTTPS path the client_id URL names. CIMD documents are fetched and validated by the AS (redirect URIs checked, contents cached). Nobody in the chain accepts self-asserted identity without dereferencing it.

Part 7 · Reference

Glossary — the MCP additions

MCP host / client / server
The host is the application (Claude Code, an IDE); it runs one MCP client per connection to an MCP server. For authorization purposes the client is the OAuth client; the server is the resource server.
Streamable HTTP
MCP's remote transport — JSON-RPC over HTTP POST (with optional SSE streaming). It's because this transport crosses trust boundaries that the authorization spec exists; stdio servers skip it entirely.
Protected Resource Metadata (PRM)
RFC 9728 document at /.well-known/oauth-protected-resource — the resource server's self-description: its canonical identity, its authorization server(s), its baseline scopes.
Client ID Metadata Document (CIMD)
A client whose client_id is an HTTPS URL pointing at its own metadata. The AS fetches it on first sight. Successor to Dynamic Client Registration, which MCP's authorization profile deprecates but keeps for compatibility (RFC 7591 itself lives on outside MCP).
Canonical server URI
The exact identifier used in resource= and matched against aud — lowercase scheme/host, no fragment, most specific URI for the server (e.g. https://mcp.acmenetworks.com/mcp). Sloppy matching here reopens the replay door.
Step-up authorization
The 403 insufficient_scope challenge + re-authorization loop: the server names the missing scopes, the client re-requests the union of old + new, with retry limits. Permissions grow on demand instead of maximally upfront.
Scopes vs entitlements
Entitlements are what the tenant admin enabled for the user (Gate 1, lives at the SaaS); scopes are what this token carries (Gate 2, lives in the token). Effective permission = intersection.
Authorization extensions
Optional, composable additions layered on the core spec (maintained in the MCP ext-auth repository) — e.g. enterprise-managed authorization patterns. The core flow in this primer is the foundation they all build on.

Part 8 · Go deeper

Sources & further reading