AI AgentsDevelopment

How MCP Server Authentication Works: OAuth Flow Step by Step

A request-by-request walk through MCP server authentication under the 2026-07-28 spec: the 401 challenge, metadata discovery, client registration, PKCE and issuer checks, when stdio needs no OAuth, and the token mistakes the spec forbids.

Toolbit AI - Team
13 min read
How MCP Server Authentication Works: OAuth Flow Step by Step

MCP server authentication depends on where the server runs. A local server on the stdio transport should skip OAuth and read its credentials from the environment. A protected server reached over HTTP acts as an OAuth 2.1 resource server, and the client earns access in this order: it gets a 401, discovers the authorization server through Protected Resource Metadata, obtains a client ID, runs an authorization code flow with PKCE, checks the issuer, and then sends the access token on every request.

Everything below follows the 2026-07-28 revision of the Model Context Protocol spec. If you want the protocol basics before the auth details, our developer guide to how MCP works covers them.

Can an MCP server run without OAuth?

An MCP server can run without OAuth: as of November 2026, the MCP spec makes authorization optional, tells stdio servers to skip its OAuth flow, and says HTTP-based servers that support authorization should follow it.

The local rule is unusually direct: "Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment." The MCP security best practices also recommend stdio for servers meant to run locally, because it limits access to just the MCP client.

Your setupTransportWhat the 2026-07-28 spec saysBest for
The server runs only on the user's machinestdioSkip the OAuth flow and read credentials from the environmentSingle-user tools: local files, a local database, a CLI wrapper
The server is reachable over a networkHTTP-based, such as Streamable HTTPShould conform to the authorization spec, acting as an OAuth 2.1 resource serverTeam servers, hosted connectors, anything holding per-user data
A network server with nothing private behind itHTTP-basedAuthorization is optional in MCP; if you add it over HTTP, follow the specShort-lived demos on a network you control

The decision rule fits on a sticky note. If the server runs only on the user's machine, use stdio and an environment variable such as TRACKER_API_KEY. If requests cross a network and anything private sits behind the server, build the flow below.

For local servers inside a governed setup, see our walkthrough of multi-agent workflows with n8n and local MCP servers.

How are MCP servers authenticated, and who issues the tokens?

MCP servers are authenticated with standard OAuth 2.1 roles: under the 2026-07-28 spec, a protected MCP server is a resource server that accepts access tokens, and an authorization server issues them. That authorization server may be hosted with the MCP server or be a separate entity.

The MCP server points clients to an authorization server, which issues the tokens it checks
The client does the legwork; the server only points the way and checks the key.

Three parties, three jobs:

  • MCP server (the resource server). Points clients to its authorization server and checks every token they bring.
  • Authorization server. Logs the user in and issues tokens. The spec does not say how to build one, which identity provider to use, or how many minutes a token should live.
  • MCP client (the OAuth client). The app your user works in. It runs discovery, registration, PKCE and the issuer check, and it stores the tokens.

Think of a hotel where the front desk may sit in a different building from your room. The room door never hands out keys: it tells you where the front desk is and checks the key you come back with.

For company rollouts, Anthropic's post on bringing 2026-07-28 to Claude says enterprise-managed auth "lets admins provision MCP connectors for their whole organization through their identity provider." That extension's protocol details are outside this guide.

How does MCP server authentication work, request by request?

As of November 2026, MCP server authentication under the 2026-07-28 authorization spec runs as eight moves (our breakdown, not the spec's), from the first 401 to a token the server accepts. Notice who does the work: the client drives every middle step, while the server points the way and checks tokens.

StepWho actsWhat happensRule in the 2026-07-28 spec
1. ChallengeClient, then serverClient calls the MCP server without a valid token; server replies 401 UnauthorizedInvalid or expired tokens MUST get a 401. The WWW-Authenticate header can carry resource_metadata and SHOULD carry scope
2. Resource metadataClientFetches the server's Protected Resource MetadataServers MUST implement RFC 9728 and list at least one entry in authorization_servers. Clients use resource_metadata when present, otherwise well-known URIs, path-specific first, then root
3. Server metadataClientFetches the authorization server's metadataFor an issuer with no path, try /.well-known/oauth-authorization-server, then /.well-known/openid-configuration. Drop metadata whose issuer does not match. Refuse to continue if code_challenge_methods_supported is absent
4. RegistrationClientGets a client IDPre-registered details, then a Client ID Metadata Document, then Dynamic Client Registration (deprecated), then ask the user
5. Authorization requestClient and userSends the user to log in and consentPKCE with S256, the resource parameter, a redirect URI on localhost or HTTPS, and the issuer recorded before redirecting
6. Issuer checkClientReceives the authorization codeApply the RFC 9207 Section 2.4 check before sending the code to any token endpoint
7. Token requestClientSwaps the code for an access tokenSend resource again. Never assume a refresh token will be issued
8. Authenticated callsClient and serverCalls MCP with the access tokenToken in the Authorization header on every HTTP request, never in the query string. Server checks the token was issued for it, per RFC 8707 Section 2

Steps 1 to 3: a discovery example

Protected Resource Metadata (RFC 9728) is the document where an MCP server names its authorization servers, in a field called authorization_servers. Here is how a client finds it.

Say your team runs a remote MCP server for its ticket tracker at https://mcp.example.com/tracker, and an agent in your editor tries to list open tickets. The server answers 401, and its WWW-Authenticate header points to the resource metadata and names the scope it wants, for example tickets:read.

The client reads https://auth.example.com from authorization_servers. It then requests /.well-known/oauth-authorization-server on that host and, if that fails, /.well-known/openid-configuration.

The spec's rule for this stage: "MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC9728)." Clients must support both discovery routes, the header URL and the well-known fallback, and use the header URL whenever it is present.

If the 401 carries no scope, the client asks for every scope in scopes_supported, or leaves scope out if that list is undefined. Remember that rule, because it comes back as a server-side mistake later.

Steps 5 and 6: why PKCE is not enough on its own

PKCE (Proof Key for Code Exchange) has the client present a code_verifier when it redeems the authorization code. Clients must use its S256 method whenever they technically can. If the authorization server's metadata lacks code_challenge_methods_supported, the spec reads that as no PKCE support, and the client must refuse to continue.

How a mix-up attack slips past PKCE and how the issuer check stops it
The fix is bookkeeping: record the issuer before redirecting, check it before redeeming.

PKCE still has a blind spot: the mix-up attack, where the client is fooled into sending its code to an attacker's token endpoint. The MCP security best practices put it plainly: "PKCE alone does not prevent this attack because the client transmits the code_verifier to the attacker's token endpoint."

The defense is boring bookkeeping. "Before redirecting the user-agent, the client MUST record the issuer value from the selected authorization server's validated metadata document," and when the code comes back, it runs the RFC 9207 Section 2.4 check before the code goes to any token endpoint.

One gap remains. The MCP rule makes clients validate iss when the response carries it, but authorization servers only SHOULD send it today, so confirm that yours does.

Steps 7 and 8: what the server checks on every call

When the client redeems the code, it sends the resource parameter again, because the spec requires it in both the authorization request and the token request. The spec leaves no wiggle room: "MCP clients MUST send this parameter regardless of whether authorization servers support it." The server must then confirm the token was issued for it as the audience, meaning the intended recipient.

From there, every call carries the token in the Authorization header. A 401 means the token is invalid or expired, and a 403 Forbidden means it lacks the scope for what you just asked.

Back to the tracker: your agent can read tickets, then you ask it to close one. The server should answer 403 with every scope that operation needs in a single challenge. The client then re-authorizes for the union of old and new scopes, here tickets:read tickets:write, not just the new one.

How does an MCP client get a client ID now that DCR is deprecated?

As of the 2026-07-28 spec, an MCP client that supports every option should pick its client ID in this priority order: pre-registered details, then a Client ID Metadata Document (CIMD), then Dynamic Client Registration (DCR), then asking the user. DCR is deprecated but still works for backward compatibility, and the official MCP blog says it will be removed in a future version of the spec.

The client registration page sets the order below.

MethodThe client uses it whenStatus as of November 2026Best for
Pre-registered clientIt already holds client details for that serverFirst choiceClients built for one known authorization server
Client ID Metadata DocumentThe authorization server advertises client_id_metadata_document_supportedPreferred replacement for DCRAny client that can host a small JSON file over HTTPS
Dynamic Client RegistrationThe authorization server has a registration_endpointDeprecated, kept for backward compatibilityAuthorization servers without CIMD support
Ask the userNothing else is availableLast resortHand-wired custom servers

CIMD in one sentence: your client_id is an HTTPS URL, and that URL serves a JSON document describing your client. The document must contain at least client_id, client_name and redirect_uris:

{
  "client_id": "https://editor.example.com/oauth/client.json",
  "client_name": "Example Editor",
  "redirect_uris": ["http://localhost/callback"]
}

The payoff shows up when a server moves to a new authorization server. The client registration page says of CIMD: "No re-registration is needed when the authorization server changes." Stored client credentials work the other way: clients key them by issuer, never reuse them with another authorization server, and re-register when it changes.

If you still rely on DCR, set application_type explicitly. Under OpenID Connect, leaving it out defaults to web, which can conflict with native-style redirect URIs. The MCP blog says this is the likely cause if your CLI client's OAuth flow has ever hit a redirect_uri error.

What changed in MCP authorization in the 2026-07-28 spec?

The 2026-07-28 MCP spec tightened authorization around issuers and client registration compared with the previous revision, 2025-11-25: clients now check a present iss, set application_type in DCR, bind stored credentials to an issuer, and should move from DCR to CIMD. Anthropic calls it the fifth spec release of MCP.

The official changelog lists the items below, with what each means for your code.

ChangeReferenceWhat it means for you
Clients validate issSEP-2468Check a present iss against the recorded issuer before redeeming the code; the MCP blog says this closes an authorization-server mix-up hole
application_type required in DCRSEP-837Set it on every registration to avoid OpenID Connect redirect URI conflicts
Issuer-bound credentialsSEP-2352Key stored credentials by issuer and re-register when the authorization server changes
DCR deprecatedSpec and MCP blogShip CIMD for new clients; DCR stays only for authorization servers without CIMD support
Sessions and initialize removedChangelogMCP is now stateless, with no Mcp-Session-Id header to lean on; authorization already rides on every HTTP request

One rule is still moving: the spec expects a future revision to upgrade authorization servers' inclusion of iss from SHOULD to MUST.

SDK support is only partly mapped. The MCP blog says all four Tier 1 SDKs (TypeScript, Python, Go and C#) spoke 2026-07-28 on release day. We could not confirm which SDK versions implement CIMD, so check your SDK's release notes before you rely on it.

What are the MCP server authentication best practices for tokens?

MCP server authentication best practices start with five token mistakes that the MCP spec and its security best practices forbid outright as of November 2026: accepting tokens issued for someone else, passing a client's token through to an upstream API, putting tokens in the query string, assuming refresh tokens, and treating a state handle as authentication. A few SHOULD rules then keep your scopes sane.

The client's token is valid only at the MCP server; upstream calls need separate authorization
Wrapping an API means authorizing that call separately, never reusing the client's token.
MistakeWhat the spec saysDo this instead
Accepting any valid-looking tokenServers MUST NOT accept tokens that were not explicitly issued for themCheck the audience on every request
Forwarding the client's token upstreamToken passthrough is explicitly forbiddenAuthorize the upstream call separately
Token in a URL such as ?access_token=...Access tokens MUST NOT go in the URI query stringUse the Authorization header only
Assuming a refresh token arrivesClients MUST NOT assume one; the authorization server decidesPlan for a fresh login when the access token expires
Treating a state handle as loginServers MUST NOT treat possession of a state handle as authenticationAuthenticate with the access token on each request
Publishing every possible scope in scopes_supportedListed as a common scope mistake in the security best practicesPublish the scopes clients actually need
Asking for scopes one at a timeServers SHOULD put every scope an operation needs in a single challengeOne 403, full list
Fetching OAuth URLs blindlyClients should block link-local 169.254.0.0/16, which includes cloud metadata endpointsFilter that range in the client's HTTP fetcher

The passthrough rule deserves a picture. Your MCP server wraps a CRM, and the token the client sends says you may call this MCP server, not you may call the CRM. The spec's line is blunt: "The MCP server MUST NOT pass through the token it received from the MCP client."

The scopes row connects back to discovery. Because a client facing a scope-less 401 requests everything in scopes_supported, a bloated list turns any scope-less challenge into a request for every permission you have ever defined. The MCP security best practices call this out by name.

One more SHOULD for whoever runs the authorization server: issue short-lived access tokens, so a leaked one does less damage.

Where should you start, server side or client side?

Where you start with MCP server authentication depends on which side of the connection you ship: server builders begin with metadata and token validation, client builders with discovery, registration and the issuer check.

If you build an MCP server:

  1. Pick the transport first. Local only means stdio plus environment credentials; anything networked that needs authorization means the OAuth flow.
  2. Publish Protected Resource Metadata with at least one authorization server, and return 401 with resource_metadata and scope in WWW-Authenticate.
  3. Validate the audience on every request, answer 403 with every scope the operation needs when permissions fall short, and never forward the client's token.

If you build an MCP client:

  1. Support both discovery routes and follow the registration order, with CIMD ahead of DCR.
  2. Enforce S256 PKCE, send resource in both requests, and run the iss check before redeeming any code.
  3. Store credentials per issuer, and re-register when a server switches authorization servers.

Your first test works on either side: send a request with no token and watch what comes back. A compliant server answers 401, and a compliant client follows WWW-Authenticate to the resource metadata, or tries the well-known URIs when the header has none.

Pick stdio if your server never leaves the user's machine, the full OAuth flow the moment it serves private data over a network, and CIMD over DCR for any new client. For more on the protocol, browse all our MCP guides.

Spec rules checked against the 2026-07-28 pages in November 2026; the iss rule is expected to tighten, so check again before your next release.

Share this article

Related articles

Continue exploring similar guides and insights