A couple of years ago we all collided with a brand-new technology that grew up alongside agents and LLMs: MCP. Its job is to give a model an interface to some other system, program, or server. The first MCP servers were local and modest: launch a calculator, check a calendar, delete the junk photos from your gallery. They graduated to remote servers fast. You paste a URL into Claude and it immediately knows what the server can do, where to connect, and how to talk to it. Credit goes to the protocol Anthropic published in November 2024. And the moment MCP went remote, authentication stopped being optional. That's what this post is about.
"Add OAuth to your MCP server" sounds like a bounded task. It isn't. You're aiming at a moving target: the spec has changed your server's role twice in about 15 months, another revision lands in three weeks, and the major agents each implement a different revision today. This post is the map: what the standard says now, how it got here, what's coming, and what each big agent actually requires from you.
MCP server authentication today, in 60 seconds
MCP authentication is how a remote MCP server figures out who's calling before it runs a tool. The one-sentence version: your server doesn't hand out credentials of its own, it's a plain OAuth resource server that validates a token some separate authorization server issued.
As of this writing (early July 2026), the current stable spec revision is 2025-11-25, and the auth model is OAuth 2.1. Auth as a whole is optional, but when a server does it, the spec splits the work between two parties: the MCP server is a plain OAuth resource server, and token issuance belongs to a separate authorization server (Keycloak, Hydra, Okta, Entra, whatever your company already runs).
The flow starts with a refusal. An unauthenticated request gets a 401, and that 401 carries everything the client needs to sort itself out: where the resource metadata lives, which authorization server to talk to, what scopes to ask for. From there the agent knows the whole route: how to register, where to get a token, how to refresh it, and how to present it.
How MCP authentication got here
The history is short in months and long in changes.
The very first spec (2024-11-05) didn't mention authorization at all. Roll your own, good luck.
The 2025-03-26 revision fixed that mistake with real OAuth 2.1, but with one fateful decision: the MCP server itself was the authorization server. Default /authorize, /token, and /register endpoints on your server. If you wanted Keycloak or Hydra or any real IdP behind it, everything had to proxy through your MCP server. Self-contained, tempting, and wrong.
That's when smart people from different auth companies started to publishing takedowns of the whole design:
- Aaron Parecki (Okta), "Let's fix OAuth in MCP" (April 2025). His core complaint: the spec forced the MCP server to be the authorization server. Discovery worked by fetching OAuth server metadata from the MCP server's own base URL, so the resource server had no choice but to also play IdP. "That's not necessary," as he put it. His proposed fixes are, almost word for word, what got adopted later: RFC 9728 Protected Resource Metadata (the server just points at its AS), delegation of token issuance to an external AS, and Dynamic Client Registration so clients can self-register.
- Christian Posta, "The updated MCP OAuth spec is a mess". A longer list. The spec conflated the AS and RS roles. It forced servers to become stateful: issue your own tokens, keep token-to-token mappings for third-party flows, track their whole lifecycle. So much for stateless, horizontally scalable backends. Every MCP server had to expose its own
/.well-known/oauth-authorization-serverinstead of one central IdP per company. And delegating to third parties dragged in token-chaining problems: revocation propagation delays, timing attacks. A comment under the post summed up the developer experience: "Just setting this up looks daunting, let alone doing so securely."
The critique got formalized fast. PR #338 (David Soria Parra, Anthropic; merged April 23, 2025) restructured the auth spec, and PR #734 added RFC 8707 resource indicators.
And lo, the complaints were heard. The 2025-06-18 revision shipped the tectonic change (which, by the way, broke backward compatibility rather badly): the MCP server became a resource server, full stop. To be precise about it: RFC 9728+RFC 8707 became MUST (the client sends resource, the server validates the token audience), and token passthrough got banned outright.
That's where the big drama ended, and we arrive at 2025-11-25, today's stable revision. Not without its own plot twist: DCR, which had looked untouchable, got replaced by CIMD (Client ID Metadata Documents) as the default registration method. The difference comes down to who moves the client metadata. With DCR the client pushed its metadata to the AS at registration time; with CIMD the AS goes and fetches the metadata itself from a URL the client controls. The motivation was security and simplification, and I'll show how it works in the next section.
For those who worried: DCR stays in the spec for us peasants who care about backward compatibility.
How MCP OAuth discovery actually works
Step by step:
- The client knocks on the server without a token and gets a 401. The response may carry a
WWW-Authenticateheader withresource_metadata. Since 2025-11-25 that header is optional: if it's missing, the client falls back to probing/.well-known/oauth-protected-resourceon its own. - The client fetches the Protected Resource Metadata and reads
authorization_serversout of it, the list of authorization servers this resource trusts. - The client fetches the AS metadata (RFC 8414 or OIDC Discovery) and runs a regular OAuth 2.1 flow with PKCE, sending the
resourceparameter (RFC 8707) so the token gets bound to this specific MCP server. - The client comes back with the token. The server validates the signature and the audience, and only then starts answering for real.
The main difference from the previous revision is that one arrow: "GET https://client.example/cimd.json". Under DCR the client pushed this data into the registration endpoint itself; under CIMD the AS pulls it from a URL. Put simply, registration moved from a push model to a pull model. According to the protocol developers, this kills client impersonation: you can no longer do anything cute with redirect_uris, because they have to live in a metadata document on a server the client actually controls, and the AS fetches and validates them from there. An attacker who wants to swap the redirect target for their own now needs to control that hosting, not just a registration request. Auth0 has a good breakdown of exactly which attacks this closes.
What's proposed next: the July 2026 revision
tl;dr: the protocol keeps changing and we're forced to change with it.
The next revision is currently a release candidate, scheduled for July 28, 2026, content locked since May 21. On the auth side there's nothing dramatic, just the security folks tightening screws. All standard OAuth hardening, and no compatibility breakage expected in the auth chapter :))
For the meticulous, the six changes:
- Clients must validate the
issfield in the authorization response (RFC 9207, SEP-2468). This defends against mix-up attacks, which matter a lot in MCP's deployment pattern where one client talks to many servers and many authorization servers at once. Without issuer validation, a malicious AS can play man-in-the-middle between the client and an honest one. - Clients should declare
application_typeduring OIDC-style dynamic registration (SEP-837). Why? So the AS knows it's dealing with a desktop or CLI client and doesn't reject its localhostredirect_urias a suspicious web app. - Client credentials get bound to the AS
issuerthat minted them (SEP-2352). One resource server can sit behind several authorization servers, and credentials from one won't work at another. If the resource migrates to a different AS, the client re-registers instead of replaying old credentials. - Actual instructions for requesting refresh tokens from OIDC-style authorization servers (SEP-2207), which the spec previously left as an exercise for the reader.
- Scope accumulation on step-up consent (SEP-2350). This covers the case where a client asks for extra permissions mid-session: first
read, then an hour later the user triggers a tool that needswrite. It was unclear what happened to already-granted scopes on that second consent. Now it's written down: scopes accumulate. A new consent adds permissions to the old ones instead of wiping them. - A clarification of how to construct discovery URLs with the
.well-knownsuffix (SEP-2351), so clients stop guessing where in the path it goes.
Outside of auth, though, the revision is huge. The protocol goes stateless: the Mcp-Session-Id header and the initialize handshake are gone, so any server instance can answer any request without sticky sessions.
This release contains breaking changes. We don't intend for that to be the norm.
(c) the MCP spec developers. We'll see.
MCP authentication across the big agents (mid-2026)
That was the history of how modern MCP auth came to be. Now let's walk through the popular agents and see how well they follow it. Spoiler: NOT VERY.
The picture may well have shifted by the time you read this, but as of early July 2026 it looks like the table below.
The good news: almost everyone supports the modern standard (shoutout to OpenAI's Responses API MCP tool and its technology from the previous geological era).
The bad news: everyone reads the same spec differently. Some clients run the entire OAuth flow for the user; some just wait for a ready-made bearer token and wash their hands. Some require DCR, some push CIMD, some live on static credentials and IAM.
Here's the detailed matrix (all cells verified against primary docs in early July 2026):
| Platform | Who runs the flow | RFC 9728 | Registration | Spec revision |
|---|---|---|---|---|
| Claude API MCP connector | you (pass a bearer) | no | n/a | 2025-11-25 |
| claude.ai / Desktop | Claude (full flow) | yes | DCR / CIMD / Anthropic creds / none | 2025-11-25 |
| OpenAI Responses API | you (pass a bearer) | no | n/a | 2025-03-26 |
| ChatGPT / Apps SDK | ChatGPT (full flow) | yes | CIMD (recommended) + DCR | 2025-11-25 |
| Gemini / Google Cloud | OAuth on Google Cloud IAM | not named | IAM + client creds + API keys | 2025-11-25 |
| VS Code / Copilot | VS Code (full flow) | yes | DCR + client creds fallback | 2025-06-18 |
| Cursor | Cursor (full flow) | yes | DCR + static (no CIMD) | unstated |
| Perplexity | configurable | unspecified | OAuth / API key / open | unstated |
And the notes that didn't fit in the table :):
-
Claude API MCP connector: HTTP transport only, tools only. You pass
authorization_tokenand you handle the refresh. -
claude.ai / Desktop:
static_beareris explicitly not supported. -
OpenAI Responses API: you pass
authorization, the token isn't stored, and the docs still reference the 2025-03-26 revision (the your-server-is-the-AS one). - ChatGPT / Apps SDK: rejects machine-to-machine, API-key, and customer mTLS auth.
- Gemini / Google Cloud: embraces exactly what ChatGPT rejects, client credentials and API keys included.
- VS Code / Copilot: ships built-in GitHub and Entra providers.
- Cursor: re-registers its DCR client on every reconnect.
- Perplexity: the only one offering an explicit "no auth" mode; the flow internals are undocumented.
What to take away from this:
- All the spec revisions are alive in production at the same time. OpenAI's Responses API still references 2025-03-26 (the one where your server is the AS), VS Code sits on 2025-06-18, while Anthropic, ChatGPT, and Google cite 2025-11-25.
- Both Anthropic and OpenAI manage to run two opposite ownership models inside one company. Each ships a "bring your own token" API product: the Claude API MCP connector and the Responses API accept a pre-obtained bearer, and the flow and refresh are your problem. And each also ships a full client (claude.ai and ChatGPT) that walks the whole road from the 401 to the token by itself. A server built for the first model won't work in the second without changes, and vice versa.
- There are direct contradictions. ChatGPT rejects machine-to-machine and API-key auth; Google requires exactly that for some services. Registration is split four ways: DCR, CIMD, static credentials, IAM.
- And for dessert, a real issue from Claude Code: anthropics/claude-code#46640. An unauthenticated HTTP server works in the CLI and 404s in the VS Code extension. One vendor, one server, two different outcomes.
What I don't like and would change
- As someone who maintains a public API for a living, I hate backward-compatibility breaks. Each one creates a wall of migration work, and anyone who can't afford that time becomes a hostage of the old revision.
- OAuth 2.1 is the standard, and nobody cares: half the world still ships API keys.
- The spec updates too often. You have to keep a hand on the pulse in the most literal sense.
- Very few authorization servers can fully support OAuth 2.1 for MCP out of the box (as far as I can tell, really only Keycloak gets close). Everyone else needs wrappers, shims, and duct tape. There is no turnkey solution, and it shows.
And about OAuth 2.1 specifically, there's a separate irony: MCP mandates a standard that formally doesn't exist. OAuth 2.1 is still an IETF draft (draft-ietf-oauth-v2-1), not a published RFC. In substance it's OAuth 2.0 with the implicit flow and password grant removed and PKCE made mandatory. So a protocol that hasn't stabilized yet is built on top of a standard that hasn't been finalized yet. No wonder the AS vendors aren't rushing to ship first-class support.
What this means if you're building a server
Doing authentication in MCP is genuine pain. The format is young, it keeps changing, and there's no sign of a final version where only the details get polished. Different clients support different revisions, which makes sitting on all chairs at once nearly impossible. Someone on Reddit claimed they built an MCP server that worked equally well with every client. No proof was offered, naturally.
The stable core that does hold across the newer clients: be a resource server, serve RFC 9728 discovery, validate the token audience, and never pass tokens through to upstream APIs. Build to that, and treat the rest (registration method, who runs the flow) as per-client configuration. Expect to support both worlds: the "bring your own bearer" API products and the full-flow clients. And test against the clients you actually care about, because passing with one proves nothing about the others.
The good news is that you can commit to OAuth 2.1 with confidence. Just don't expect a smooth ride, and patch the rough edges as they appear.
FAQ
How does MCP authentication work?
Your server refuses first and explains itself second. The client shows up without a token, gets a 401 pointing at the protected resource metadata, figures out which authorization server to talk to, and runs a regular OAuth 2.1 flow with PKCE. It comes back with a token bound to your server, and validating that token (signature, audience) is the whole of your job.
How do I add authentication to my MCP server?
By not building an identity provider. You're a resource server: serve RFC 9728 discovery, point it at an authorization server you already run (Keycloak, Hydra, Entra), validate the token audience, and never forward the client's token to upstream APIs. The annoying part isn't this happy path, it's that every client expects a slightly different setup around it.
What's the difference between the authorization server and the resource server?
The authorization server owns the hard parts: logins, consent screens, issuing and refreshing tokens. The resource server (your MCP server) only checks what it receives. Early MCP made one server do both jobs, and since 2025-06-18 the spec explicitly forbids that.
Which MCP spec revision should I target?
On paper, 2025-11-25. In practice the version number promises you nothing, because production clients are spread across three revisions at once. The honest answer is "whichever revisions your target clients implement", plus the core that holds everywhere: resource server role, RFC 9728 discovery, audience validation, no token passthrough.
If you want to know where your own server stands, I'm building aoa-conformance: a CLI that runs the same authorization steps a real MCP client would (discovery, PKCE, resource indicators, token exchange, DPoP) against your server or its issuer, and prints a scorecard of what passes and what doesn't.
Further reading:
- MCP spec revisions: 2025-03-26, 2025-06-18, 2025-11-25
- Aaron Parecki, "Let's fix OAuth in MCP"
- RFC 9728 (Protected Resource Metadata, published April 2025)
- RFC 8707 (Resource Indicators)
- RFC 8414 (Authorization Server Metadata)

Top comments (0)