Before March 2025, remote MCP server authentication was a free-for-all. Some servers used API keys in headers, some used custom bearer tokens, none of it was standardized. The MCP spec update in March 2025 changed that by adding OAuth 2.0 with PKCE as the standard auth mechanism for remote servers. Then in June 2025, OAuth Resource Servers were added to go even further. Here's what all of it actually means and what you need to implement.

Why OAuth? What Was Wrong Before?

When you connect to a remote MCP server, something has to prove you're allowed to use it. Before the March 2025 spec, implementors improvised: static API keys passed in environment variables, custom Authorization headers with no shared format, or just no auth at all. The problems were real:

  • No revocation. A static API key can't be revoked per-session — you have to rotate the whole key.
  • No scoping. An API key is all-or-nothing. You can't grant a client read-only access versus full write access.
  • Passwords in config files. Many implementations ended up with secrets stored in plaintext config, committed to repos.
  • No delegation. If Claude is acting on your behalf with a third-party service, you want you to authorize that, not paste your credentials into a config file.

OAuth solves all of these. The user delegates access through a proper login flow. No passwords are ever sent to the MCP server itself. Tokens can be scoped to specific capabilities and revoked at any time.

The Two OAuth Additions to MCP

The MCP spec grew OAuth support in two phases. Understanding the difference matters if you're reading the spec or deciding what to implement.

Phase 1 — March 2025: OAuth 2.0 with PKCE

The March 2025 spec update added OAuth 2.0 Authorization Code flow with PKCE as the standard authorization mechanism for remote MCP servers. Every remote HTTP server should implement this. It defines how the client discovers the auth endpoint, how the authorization flow works, and how the resulting access token is attached to MCP requests.

Phase 2 — June 2025: OAuth Resource Servers

The June 2025 update introduced the concept of OAuth Resource Servers. This separates the MCP server (which handles tool calls) from the authorization server (which issues tokens). An MCP server can now declare itself a protected resource managed by an external auth provider — like your company's existing identity platform — rather than bundling its own auth logic. More on this below.

The PKCE Flow Step by Step

PKCE (Proof Key for Code Exchange) is an extension to the standard OAuth Authorization Code flow. Here's exactly what happens when an MCP client authenticates with a remote server.

Step 1: Discovery

Before anything else, the MCP client fetches the authorization server's metadata to find the right endpoints:

GET /.well-known/oauth-authorization-server HTTP/1.1
Host: api.your-mcp-server.com
The client discovers OAuth endpoints by fetching the well-known metadata document.

The server responds with a JSON document describing authorization_endpoint, token_endpoint, and supported scopes. This is how clients find the auth URL automatically without hardcoding it.

Step 2: Generate the Code Verifier and Challenge

The client generates a cryptographically random code_verifier string (43–128 characters), then hashes it with SHA-256 to produce the code_challenge:

// Pseudocode — real implementations use a crypto library
code_verifier  = base64url(randomBytes(32))
code_challenge = base64url(sha256(code_verifier))
The code_verifier is kept secret locally; only the hashed code_challenge is sent to the auth server.

Step 3: Redirect the User to the Authorization Endpoint

GET /authorize
  ?response_type=code
  &client_id=mcp-client-abc
  &redirect_uri=http://localhost:3000/callback
  &scope=tools:execute+resources:read
  &state=random-csrf-token
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
Host: auth.your-mcp-server.com
The authorization request includes the code_challenge but NOT the code_verifier — that stays on the client.

The user logs in and approves the requested scopes. The auth server redirects back to the redirect_uri with a short-lived code.

Step 4: Exchange the Code for a Token

POST /token HTTP/1.1
Host: auth.your-mcp-server.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=http://localhost:3000/callback
&client_id=mcp-client-abc
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The token exchange sends the original code_verifier — the auth server hashes it and verifies it matches the stored challenge.

The auth server hashes the code_verifier and checks it against the code_challenge stored from step 3. If they match, it returns an access_token and optionally a refresh_token.

Step 5: Attach the Bearer Token to MCP Requests

POST /mcp HTTP/1.1
Host: api.your-mcp-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{"jsonrpc": "2.0", "method": "tools/list", "id": 1}
Every MCP request to a remote server includes the access token in the Authorization header.

What PKCE Protects Against

Without PKCE, OAuth Authorization Code flow has a vulnerability: if an attacker can intercept the authorization code (through a malicious app registered with the same redirect URI, or a compromised redirect), they can exchange it for a token. PKCE closes this gap because the code alone is worthless — to exchange it, you also need the code_verifier that only the legitimate client ever knew.

This matters especially for MCP clients because they are public clients: they don't have a client secret (or the secret can't be kept confidential in a desktop app or CLI tool). Standard OAuth requires a client secret for the token exchange step. PKCE replaces that requirement.

Scopes in MCP

Scopes define what a client is allowed to do once authorized. MCP doesn't mandate specific scope names, but the established convention uses resource:action pairs. Common examples:

  • tools:read — list available tools, read tool definitions
  • tools:execute — actually call tools
  • resources:read — read resource contents
  • resources:write — write or modify resources
  • prompts:read — list and retrieve prompt templates

Your server enforces scopes by inspecting the token's scope claim on every request. A client with only tools:read that attempts a tools/call should get a 403 response.

OAuth Resource Servers (June 2025)

The June 2025 addition is for organizations that already have OAuth infrastructure and don't want each MCP server to run its own auth server. The idea: your MCP server declares itself an OAuth Resource Server — a protected API — and points to a separate, existing authorization server.

The MCP server publishes its resource metadata at a well-known URL:

GET /.well-known/oauth-resource-server HTTP/1.1
Host: api.your-mcp-server.com

// Response:
{
  "resource": "https://api.your-mcp-server.com",
  "authorization_servers": ["https://auth.your-company.com"],
  "scopes_supported": ["tools:execute", "resources:read"]
}
The OAuth Resource Server metadata document tells MCP clients which authorization server to use and what scopes are available.

The MCP client fetches this metadata, sees that it should get a token from auth.your-company.com, completes the PKCE flow there, and then uses the resulting token with api.your-mcp-server.com. Your MCP server only needs to validate incoming tokens — it never has to issue them.

When You Don't Need OAuth

If you're building or using a local MCP server — one that runs as a subprocess via stdio and is configured in claude_desktop_config.json — you need zero OAuth. The server is launched directly by the MCP client on your local machine. There's no network, no HTTP, no bearer tokens. The process trust model handles security at the OS level.

OAuth only becomes relevant when the MCP server is:

  • Running on a remote host and accessed over HTTP/HTTPS
  • Shared between multiple users or clients
  • Handling resources that belong to specific users and need per-user authorization

Common Mistakes to Avoid

Confusing the Authorization Server with the MCP Server

These are two different things. The authorization server issues tokens. The MCP server validates them and handles tool calls. They can be the same process for small servers, but conceptually they're separate. When reading the spec, keep track of which role is being discussed.

Not Implementing PKCE

Some developers implement plain OAuth Authorization Code flow without PKCE because it's simpler. For remote MCP servers, this is a security risk. The March 2025 spec mandates PKCE. If your auth library doesn't support it, find one that does — don't skip it.

Not Handling Token Refresh

Access tokens expire (typically in 1 hour). If your MCP client doesn't use the refresh_token to get a new access token before expiry, users will get mysterious 401 errors mid-session. Store the refresh token, watch for 401 responses, and refresh proactively before expiry using the token's expires_in value.

Hardcoding the Auth Endpoint

Always use the /.well-known/oauth-authorization-server discovery document rather than hardcoding the /authorize URL. Auth server deployments vary, and discovery is what makes your client portable.

Frequently Asked Questions

No. Local MCP servers that run over stdio (spawned as a subprocess by the MCP client) require zero authentication. OAuth is only relevant for remote HTTP-based MCP servers where the server is hosted elsewhere and the client connects over a network. If you're running a local server like the filesystem or GitHub server via npx in claude_desktop_config.json, you don't need OAuth at all.

PKCE (Proof Key for Code Exchange) is an extension to OAuth 2.0 that prevents authorization code interception attacks. Instead of sending just a redirect URI, the client generates a random code_verifier, hashes it into a code_challenge, and sends the challenge with the initial authorization request. When exchanging the code for a token, the client sends the original verifier — only the legitimate client who started the flow can complete it. MCP requires PKCE because MCP clients are often public clients (no client secret), making the standard auth code flow vulnerable.

An OAuth Resource Server is a concept added to MCP in June 2025. It allows an MCP server to be a separate protected resource from the authorization server that issues tokens. The MCP server publishes its own metadata at /.well-known/oauth-resource-server describing which authorization server to use and what scopes apply. This separation lets organizations reuse existing OAuth infrastructure (like an internal identity provider) to protect multiple MCP servers without each server needing to implement its own full auth stack.

When the access token expires, the MCP client should use the refresh_token returned during the initial token exchange to get a new access token by calling the token endpoint with grant_type=refresh_token. Your MCP server implementation should return a 401 Unauthorized response when a token is expired — the client layer (not your server logic) is responsible for detecting this and performing the refresh. Most MCP SDK clients handle token refresh automatically if you store the refresh token alongside the access token during initial authorization.