MCP Resource Server vs Authorization Server: Why the Separation Matters

MCP's auth spec draws a hard line between the Resource Server and the Authorization Server. Here's what each role does, how the OAuth 2.1 flow works end-to-end, and why the split is smart.

MK

Mohammed Kafeel

Machine Learning Researcher

June 24, 202613 min read
On this page

So you've built an MCP server - or you're about to - and now you're staring at the auth spec wondering: do I need to build my own login system? The short answer is no. The longer answer is what this post is about.

The MCP Resource Server vs Authorization Server split is the single most important architectural decision in the Model Context Protocol's OAuth 2.1 framework. Get it right and you inherit decades of hardened identity infrastructure for free. Get it wrong and you're reinventing auth from scratch - badly.

Let's break it down.


TL;DR - Key Takeaways

  • MCP Resource Server = your MCP server. It validates tokens and enforces scopes. It does not log users in or issue tokens.
  • Authorization Server = a separate OAuth 2.1 entity (Auth0, Okta, Azure AD, Keycloak, Google OAuth…). It authenticates users, collects consent, and issues access tokens.
  • The separation is mandatory by design in the MCP spec (updated June 2025) - not optional.
  • PKCE (Proof Key for Code Exchange) is required for every authorization code flow.
  • Tokens go in the Authorization: Bearer header. Never in the query string.
  • Discovery is automatic via /.well-known/oauth-protected-resource (RFC 9728).

What Is MCP (and Why Does It Need Auth)?

MCP (Model Context Protocol) is an open protocol from Anthropic that lets AI applications connect to external tools, data sources, and systems using standardized JSON-RPC 2.0 messaging. Think of it as a USB-C port for AI: one standard interface, any peripheral. (New here? Start with what MCP is.)

The core components are:

  • MCP Host - the AI application itself (Claude Desktop, Cursor, VS Code)
  • MCP Client - the component inside the Host that maintains a 1:1 connection to an MCP Server
  • MCP Server - the external program exposing Tools, Resources, and Prompts
  • Transport Layer - stdio for local, SSE/HTTP for remote

Early MCP (spec version 2024-11-05) had no auth framework at all. Servers ran on localhost, credentials lived in plaintext config files, and security was basically "don't share your laptop." That worked for demos. It doesn't work for production.

The June 2025 spec (version 2025-06-18, now updated to 2025-11-25) changed everything. It introduced a full OAuth 2.1 authorization framework - and with it, the clean separation between Resource Server and Authorization Server.


The Two Roles: Resource Server vs Authorization Server

This is the heart of MCP authorization. Two entities. Two jobs. Zero overlap.

The MCP Resource Server

Your MCP server - the one exposing tools and data - acts as an OAuth 2.1 Resource Server as defined in RFC 6750.

Here's what it does:

  • Validates access tokens on every incoming request
  • Enforces access control based on token scopes and claims (RBAC)
  • Returns HTTP 401 when a token is missing, expired, or invalid
  • Returns HTTP 403 with WWW-Authenticate: Bearer error="insufficient_scope" when the token is valid but lacks required permissions

Here's what it does not do:

  • ❌ Authenticate users
  • ❌ Show login screens
  • ❌ Issue or refresh tokens
  • ❌ Manage sessions or consent

The MCP Resource Server is stateless. It receives a request, checks the token, and either serves the resource or returns an error. That's it.

The Authorization Server

The Authorization Server is a separate OAuth 2.1 entity - typically an existing identity provider (IdP) like Auth0, Okta, Azure AD, Keycloak, or Google OAuth.

Here's what it does:

  • Authenticates the user (login screen, MFA, SSO)
  • Obtains consent for the requested scopes
  • Issues access tokens (and optionally refresh tokens)
  • Manages the authorization code flow with PKCE
  • Publishes metadata at /.well-known/oauth-authorization-server or /.well-known/openid-configuration (RFC 8414 / OIDC Discovery)

The Authorization Server is stateful - it manages sessions, grants, and token lifecycles. It's also the component where all the security-critical identity logic lives, including your token rotation strategy.


Why the Separation Matters

The short answer: because mixing auth into your MCP server is how you create vulnerabilities, maintenance nightmares, and scalability walls.

Here are the six concrete reasons the MCP spec mandates this split:

1. Security

Authentication is hard. Storing credentials, managing sessions, preventing brute-force attacks, rotating secrets - that's a full-time job. Auth0, Okta, and Azure AD have entire security teams dedicated to exactly this. When you delegate to a trusted IdP, you're not just saving time. You're reducing your attack surface dramatically.

2. Flexibility - Reuse Your Existing SSO

Most organizations already have an IdP. The separation means your MCP server plugs straight into that existing infrastructure. No new auth system. No new user database. Your users log in the same way they log in to everything else. (Our guide to enterprise SSO integration covers wiring MCP to corporate identity providers.)

3. Simplicity

Your MCP server only needs to answer one question: "Is this token valid and does it have the right scopes?" All the identity complexity - login flows, consent screens, token issuance, refresh logic - lives in the Authorization Server. Separation of concerns at its best.

4. Compliance

The MCP auth spec is built on OAuth 2.1 (draft-ietf-oauth-v2-1-13), which incorporates the latest security best practices. Delegating to a compliant IdP means you inherit that compliance posture automatically - important for enterprise deployments and regulated industries.

5. Scalability - Solving the M×N Problem

Without this separation, every MCP client needs a custom auth integration with every MCP server. That's M clients × N servers = M×N custom integrations. With a shared Authorization Server, any client that speaks OAuth 2.1 can talk to any MCP server. The math collapses from M×N to M+N.

6. Separation of Concerns

Auth logic lives in one place. Resource logic lives in another. When you need to rotate signing keys, add MFA, or update your consent flow, you do it in the Authorization Server - and every MCP server that trusts it benefits automatically.


The Full MCP Authorization Flow, Step by Step

Here's the complete Model Context Protocol OAuth flow, from first request to authorized resource access. We'll use mcp.example.com as the MCP server and auth.example.com as the Authorization Server. (For a focused look at the code-exchange step, see our MCP OAuth with PKCE guide.)

Step 1 - Initial Request Returns HTTP 401

The MCP client sends a request to the MCP server without a token (or with an expired one). The server responds:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

The resource_metadata URL tells the client exactly where to find discovery information. The scope parameter gives an immediate hint about what permissions are needed.

Step 2 - Fetch Protected Resource Metadata (RFC 9728)

The client fetches the MCP Protected Resource Metadata document at the well-known URI:

GET https://mcp.example.com/.well-known/oauth-protected-resource

The response is a JSON document containing:

{
  "resource": "https://mcp.example.com",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read", "files:write"],
  "bearer_methods_supported": ["header"]
}

The critical field is authorization_servers - this is how the client discovers which Authorization Server to use.

Step 3 - Discover Authorization Server Metadata

The client now fetches the Authorization Server's own metadata. For a root-level issuer like https://auth.example.com, it tries these endpoints in order:

  1. https://auth.example.com/.well-known/oauth-authorization-server
  2. https://auth.example.com/.well-known/openid-configuration

This document reveals the authorization_endpoint, token_endpoint, jwks_uri, code_challenge_methods_supported, and more.

Step 4 - Client Registration

The client needs to identify itself to the Authorization Server. The MCP spec supports three approaches, in priority order:

  • Option A (Recommended): Client ID Metadata Documents - the client hosts a JSON metadata document at an HTTPS URL and uses that URL as its client_id. No pre-registration needed.
  • Option B: Pre-registration - static credentials supplied beforehand (useful when client and server have an existing relationship).
  • Option C: Dynamic Client Registration (RFC 7591) - the client registers dynamically at the registration_endpoint. Included for backwards compatibility.

Step 5 - Scope Selection

The client picks scopes using this priority:

  1. Use the scope from the WWW-Authenticate header (if present)
  2. Fall back to all scopes in scopes_supported from the Protected Resource Metadata

Step 6 - Authorization Request with PKCE

The client initiates the authorization code flow. PKCE (Proof Key for Code Exchange, a security extension to OAuth) is mandatory - no exceptions. The client generates a random code_verifier, hashes it to produce a code_challenge, and sends:

GET https://auth.example.com/oauth/authorize?
  client_id=https://app.example.com/client-metadata.json
  &redirect_uri=http://localhost:3000/callback
  &scope=files:read
  &resource=https://mcp.example.com
  &code_challenge=<S256_hash>
  &code_challenge_method=S256
  &state=<random_csrf_token>
  &response_type=code

Two parameters deserve special attention:

  • resource (RFC 8707) - binds the token to this specific MCP server, preventing token mix-up attacks
  • state - CSRF protection; the client must verify this on the callback

The Authorization Server redirects the user to a consent screen. The user approves. The AS redirects back to the redirect_uri with code and state.

Step 8 - Token Exchange

The client exchanges the authorization code for an access token:

POST https://auth.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<auth_code>
&client_id=https://app.example.com/client-metadata.json
&code_verifier=<original_verifier>
&resource=https://mcp.example.com

The AS verifies the code_verifier against the code_challenge from Step 6. If they match, it issues:

{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Step 9 - Authenticated Resource Request

The client sends every subsequent request to the MCP server with the token in the Authorization header:

GET /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGci...

The token MUST be in the header. Never in the query string. This applies to every single request, even within the same logical session.

Step 10 - Token Validation by the MCP Server

The MCP server validates the token by checking:

  1. Signature - verified against the AS's public keys (JWKs)
  2. Expiry - token must not be expired
  3. Audience - token must include this MCP server as the intended audience (RFC 8707)
  4. Issuer - token must have been issued by the correct Authorization Server

If any check fails → HTTP 401. If the token is valid but scopes are insufficient → HTTP 403.

Step 11 - Step-Up Authorization

If a specific tool requires a scope the current token doesn't have, the MCP server returns:

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
                         scope="files:read files:write",
                         resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

The client re-initiates the authorization flow with the expanded scope set. This is called step-up authorization - you only ask for more permissions when you actually need them.


MCP Protected Resource Metadata Explained

MCP Protected Resource Metadata (defined in RFC 9728) is the discovery mechanism that makes the whole flow self-service. Without it, clients would need hardcoded configuration for every MCP server they talk to.

The metadata document lives at:

https://<your-mcp-server>/.well-known/oauth-protected-resource

It's a JSON document that tells clients:

  • Which Authorization Server(s) to use (authorization_servers)
  • What scopes are supported (scopes_supported)
  • How tokens should be sent (bearer_methods_supported)
  • Where to verify token signatures (jwks_uri, optional)

MCP servers MUST implement this endpoint. MCP clients MUST use it for Authorization Server discovery. It's not optional - it's how the whole discovery chain starts.

The /.well-known/oauth-protected-resource endpoint is what enables the MCP auth spec to be truly plug-and-play: any compliant client can connect to any compliant server without out-of-band configuration.


Comparison Table: Resource Server vs Authorization Server

Feature MCP Resource Server Authorization Server
OAuth 2.1 Role Resource Server (RFC 6750) Authorization Server
Primary Job Validate tokens, enforce scopes Authenticate users, issue tokens
Manages Login? ❌ No ✅ Yes
Issues Tokens? ❌ No ✅ Yes
Handles Consent? ❌ No ✅ Yes
Stateful? No (stateless token validation) Yes (sessions, grants)
Examples Your MCP server (tools, data) Auth0, Okta, Azure AD, Keycloak
On Bad Token Returns HTTP 401 N/A
On Missing Scope Returns HTTP 403 Issues token with requested scopes
Discovery Endpoint /.well-known/oauth-protected-resource /.well-known/oauth-authorization-server

5 Common Mistakes to Avoid

MCP security failures usually come down to one of these five errors. Don't make them. (Our MCP server authentication guide covers the implementation details for each.)

Mistake 1: Building Auth Into Your MCP Server

The temptation is real - just add a login endpoint, issue a JWT, done. But now you're maintaining a credential store, a session manager, and a token signing system. Delegate to a real IdP. That's what the spec is for.

Mistake 2: Skipping PKCE

PKCE is mandatory in the MCP OAuth 2.1 spec. Without it, an attacker who intercepts the authorization code in transit can exchange it for a token before you do. Always use code_challenge_method=S256. If the Authorization Server's metadata doesn't include code_challenge_methods_supported, the MCP spec says you must refuse to proceed.

Mistake 3: Putting Tokens in Query Strings

Tokens in query strings end up in server logs, browser history, and HTTP referrer headers. The spec is explicit: tokens go in the Authorization: Bearer header. Every request. No exceptions.

Mistake 4: Not Validating the resource Parameter (RFC 8707)

If your MCP server accepts tokens without checking their intended audience, an attacker can reuse a token issued for a different service. Validate that the token's audience claim matches your server's canonical URI. This prevents token mix-up attacks.

Mistake 5: Ignoring the /.well-known/oauth-protected-resource Endpoint

Clients rely on this endpoint to discover your Authorization Server. If you don't implement it, compliant clients can't connect automatically. It's not optional - it's the entry point to the entire discovery chain.


Real-World Example: Gmail Tool in Claude

Let's make this concrete. Say you're building an MCP server that exposes a Gmail tool - it lets Claude send emails on behalf of a user.

Here's how the roles map:

  • Authorization ServerGoogle OAuth (accounts.google.com). Google authenticates the user, shows a consent screen asking for the gmail.send scope, and issues an access token.
  • MCP Resource ServerYour MCP server. It receives the Google-issued token, validates the signature against Google's JWKs, checks that the gmail.send scope is present, and then calls the Gmail API.

Your MCP server never sees the user's password. It never manages a session. It never touches the consent flow. It just asks: "Is this token valid? Does it have gmail.send?"

That's the separation in action. And it means you can swap Google OAuth for Okta tomorrow without touching a single line of your MCP server code.


FAQ

What is an MCP Resource Server?

An MCP Resource Server is your MCP server acting as an OAuth 2.1 Resource Server (per RFC 6750). It validates access tokens on every incoming request and enforces scope-based access control. It does not handle user authentication, login flows, or token issuance - those are the Authorization Server's job.

What is the Authorization Server in MCP?

The Authorization Server is a separate OAuth 2.1 entity - typically an existing identity provider like Auth0, Okta, Azure AD, or Keycloak - that authenticates users, obtains their consent for requested scopes, and issues access tokens to MCP clients. The MCP spec doesn't care which AS you use, as long as it's OAuth 2.1 compliant.

Why does MCP separate the Resource Server from the Authorization Server?

For three core reasons: security (trusted IdPs have hardened auth infrastructure your MCP server doesn't need to replicate), scalability (any OAuth 2.1 client can talk to any MCP server without custom integrations), and flexibility (organizations reuse their existing SSO infrastructure). It also aligns with OAuth 2.1 best practices and enterprise compliance requirements.

Is PKCE mandatory in MCP authorization?

Yes. The MCP spec mandates PKCE (Proof Key for Code Exchange) with code_challenge_method=S256 for all authorization code flows. If the Authorization Server's metadata doesn't advertise code_challenge_methods_supported, the MCP client must refuse to proceed with the flow.

What happens if an MCP server receives an invalid token?

It returns HTTP 401 Unauthorized. If the token is valid but lacks the required scopes for a specific operation, it returns HTTP 403 Forbidden with a WWW-Authenticate header containing error="insufficient_scope" and the scopes needed to proceed.

Can an MCP server act as its own Authorization Server?

Yes, but only in simple or legacy scenarios. The MCP spec explicitly recommends using a third-party Authorization Server for production deployments. Building auth into your MCP server means taking on all the complexity and security responsibility that dedicated IdPs already handle.

What is Protected Resource Metadata in MCP?

MCP Protected Resource Metadata is a JSON document served at /.well-known/oauth-protected-resource (defined in RFC 9728). It tells MCP clients which Authorization Server to use, what scopes are supported, and how tokens should be transmitted. MCP servers must implement this endpoint; MCP clients must use it for Authorization Server discovery. It's the foundation of the entire self-service discovery chain.


Ready to Implement?

The MCP auth architecture isn't complicated once you internalize the core principle: your MCP server validates tokens, it doesn't create them. Pick an OAuth 2.1-compliant IdP you already trust, implement the /.well-known/oauth-protected-resource endpoint, enforce PKCE, validate the resource parameter - and you're aligned with the spec.

Read the official MCP Authorization Specification to go deeper on any of the steps above. And if you want to understand the full OAuth 2.1 stack that underpins MCP auth, the WorkOS breakdown of MCP Authorization in 5 OAuth specs is an excellent companion read.


Useful Sources