On this page
OAuth 2.1 authentication for MCP servers
The specification's own authentication method: an authorization-code flow the MCP client discovers and runs for itself, given nothing but the server's URL.
- Method
- OAuth 2.1
- Platform
- Any platform
- MCP specification
- 2026-07-28
How it works
The client sends an unauthenticated request and gets 401 with a WWW-Authenticate header naming the server's RFC 9728 protected resource metadata. That document names the authorization server. The client discovers the authorization server's own metadata, obtains a client_id, and runs an authorization-code flow with PKCE, sending an RFC 8707 resource parameter on both the authorization and the token request. The server checks that the token's audience is itself. Every step but that one happens inside the MCP client, which is why the flow works in Claude, ChatGPT, Cursor, and VS Code, and fails in an agent framework whose MCP configuration accepts only a URL and a headers map.
- Best for
- Servers that must be reachable by MCP clients you don't control, or listed in a connector directory
- Specification
- The specification's own flow — though the specification still makes authorization OPTIONAL
- Works with
- Claude, ChatGPT, Cursor, and VS Code run it themselves; among agent SDKs, only those shipping an OAuth provider
- Effort
- Days — most of it is the authorization server, not MCP
The exchange
- MCP client in your agent or host
- Browser the user signs in
- Authorization server issues the token
- MCP server resource server
-
MCP client to MCP server
POST /mcp no Authorization header -
MCP server to MCP client
401 Unauthorized WWW-Authenticate: Bearer resource_metadata=… -
Two documents, two hops. RFC 9728 protected resource metadata names the authorization server; RFC 8414 metadata, or OpenID Connect Discovery, describes it. If code_challenge_methods_supported is absent from that second document, a conforming client MUST refuse to proceed.
MCP client to MCP server
GET …/oauth-protected-resource/mcp -
MCP server to MCP client
200 OK authorization_servers, scopes_supported -
MCP client to Authorization server
GET …/oauth-authorization-server then /.well-known/openid-configuration -
Authorization server to MCP client
200 OK code_challenge_methods_supported: [S256] -
The browser step. There is no headless variant of it: a framework whose MCP configuration takes only a URL and headers stops here, and its request never reaches /authorize at all.
Inside MCP client
Get a client_id, build the PKCE pair, open a browser pre-registered, then CIMD, then DCR -
Browser to Authorization server
GET /authorize code_challenge + resource=https://mcp.example.com/mcp -
Authorization server to Browser
302 to redirect_uri code + iss -
Browser to MCP client
Authorization code callback code, state, iss -
The resource parameter goes on both the authorization request and the token request, and the server checks the audience it produced. That check is what stops this token working at a different MCP route.
MCP client to Authorization server
POST /token code_verifier + resource -
Authorization server to MCP client
200 OK (carries the credential) access_token, aud = the MCP server -
MCP client to MCP server
POST /mcp (carries the credential) Authorization: Bearer ACCESS_TOKEN -
Inside MCP server
Check the token was issued for this server -
MCP server to MCP client
200 OK tool result
Connect your agent
Pick your language and SDK.
When to use something else
Use this when
- The server has to be reachable by MCP clients you don't control, or listed in a connector directory: Anthropic's submission requirements say to use OAuth 2.0 for authenticated services.
- The agent calls on behalf of a named person, and the audit trail must identify that person rather than a key.
- You already run an identity provider and want its sign-in, its consent screen, and its revocation to govern MCP access too.
Use something else when
- The caller is a cron job or a CI step. The authorization-code grant needs a browser and a person, and there is no headless variant of it.
- Your framework's MCP configuration takes only a URL and a headers map. The Claude Agent SDK documents this: it doesn't open a browser or run an interactive OAuth flow, and reports a challenged server as
needs-authwhile the run continues without its tools. - Both ends belong to you and no end user's identity needs to reach the API behind the server, where a static key is less machinery for the same result.
This is the specification's own flow, and the specification still makes it OPTIONAL: "Authorization is OPTIONAL for MCP implementations", with HTTP transports SHOULD — not MUST — conforming. 2026-07-28 is the current protocol version, and two of its changes alter this flow: Dynamic Client Registration is marked Deprecated in favor of Client ID Metadata Documents, and clients MUST validate a present RFC 9207 iss parameter against the recorded issuer before redeeming the authorization code.
A token proves who the caller is, and nothing about what they can call, what they did call, or how often — the next three tickets.
- Step 1.
Let the gateway answer the 401
The whole discovery chain — the
401, the protected resource metadata, the authorization server, PKCE, theresourceparameter — is what the gateway serves. Point clients at the route and it publishes RFC 9728 metadata for that route itself."url": "https://
api. example. com/ mcp" - Step 2.
Add the policies
A list on the route, and the options that go with it. No SDK, no middleware, nothing imported into your server.
"inbound": [ "mcp-oauth-inbound", "require-user-claims-inbound", "mcp-capability-filter-inbound", "rate-limit-inbound" ]
- Step 3.
Deploy
Your MCP server keeps its code and stops having to implement the discovery chain itself.
zuplo deploy
-
The token validates. Which of our tools can that caller reach?
-
mcp-capability-filter-inboundOnly what you expose — and the list can differ per caller, keyed on the claims in the token you just validated. A hidden tool is refused with
MethodNotFoundbefore anything is forwarded. -
Only the finance group should reach this route. Where does that live?
-
require-user-claims-inboundOn the route, as one rule on the token's claims — group, role, tenant — answered with
403before your server runs. Per-object questions ("may they refund this order?") go to an FGA or AuthZEN policy beside it. -
A token was rejected at 2 AM. Which one, and why?
-
mcp_auth_downstream_token_validatedThat's one query: rejections carry named reason codes —
missing_token,invalid_audience— tied to the subject and the route. Bearer tokens, authorization codes, and PKCE verifiers are never logged. -
One agent looped and made 40,000 tool calls. Can we cap that per user?
-
rate-limit-inboundYes — the cap follows the identity the token established rather than an egress IP, so the runaway agent exhausts its own budget and nobody else's.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps
the code and the authentication it has today.
Common questions
Does MCP require OAuth?
No. The specification states that authorization is OPTIONAL for MCP implementations, and that HTTP-based transports SHOULD — not MUST — conform to it. What OAuth buys you is reach: a hosted client completes the whole flow given nothing but your URL, and Anthropic's connector-directory submission requirements say to use OAuth 2.0 for authenticated services.
Which specification revision is current, and what changed for OAuth?
2026-07-28 is the current protocol version. For authorization, it deprecates Dynamic Client Registration in favor of Client ID Metadata Documents, requires clients to validate a present RFC 9207 iss parameter, requires an application_type on dynamic registration, and requires client credentials to be keyed by the issuer that granted them.
Client ID Metadata Documents or Dynamic Client Registration?
Try a pre-registered client ID first, then a Client ID Metadata Document if the authorization server advertises client_id_metadata_document_supported, then RFC 7591 registration if it advertises a registration_endpoint, and only then prompt the user. CIMD is a SHOULD in both revisions; registration is a MAY and is Deprecated as of 2026-07-28. A CIMD client ID is also portable: registered credentials must be keyed by the issuer that granted them and re-registered when the authorization server changes, where a CIMD client ID is an HTTPS URL with nothing to re-register.
Why does my agent framework never open a browser for OAuth?
Because it has no browser and no callback URL. The Claude Agent SDK says so directly, and reports a server that answers with an authorization challenge as needs-auth while the run continues without its tools. Complete the flow in your own application and pass the resulting access token in the server's headers.
Do I still send the resource parameter if my provider ignores it?
Yes. MCP clients MUST send this parameter regardless of whether authorization servers support it, on both the authorization request and the token request, using the MCP server's canonical URI. The Microsoft identity platform is the common case that doesn't honor it: its v2.0 protocol uses scopes instead, so you ask for the audience with a scope — and Entra can refuse the parameter outright rather than ignoring it, with AADSTS901002, 'The resource request parameter isn't supported', which is in Microsoft's own error-code reference.
Can my MCP server forward the client's token to the API behind it?
No, and this is the one prohibition in the whole flow. The security best practices document says MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server, and the authorization specification adds that a server acting as a client to an upstream API MUST NOT pass through the token it received. Mint or exchange a separate upstream credential.
What if the 401 has no WWW-Authenticate header?
The client keeps going. A server MUST implement one of two discovery mechanisms, and clients MUST support both: use the resource_metadata URL from the header when present, otherwise construct and request the well-known URIs — the path-scoped form /.well-known/oauth-protected-resource/mcp first, then the root. So a missing header is not fatal on its own; a missing metadata document is.
Is PKCE optional if the authorization server doesn't advertise it?
No — absence is a stop condition. Clients MUST implement PKCE and MUST verify support before proceeding, and because neither OAuth 2.1 nor PKCE defines a discovery mechanism, that verification is the presence of code_challenge_methods_supported in the authorization server's metadata. If it is absent, clients MUST refuse to proceed. S256 is required wherever the client is technically capable of it.
One policy engine for APIs, AI, and MCP
Put your MCP servers behind a gateway that speaks every identity provider, filters tools per role, and logs every call.