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.
Mohammed Kafeel
Machine Learning Researcher
On this page
- TL;DR - Key Takeaways
- What Is MCP (and Why Does It Need Auth)?
- The Two Roles: Resource Server vs Authorization Server
- Why the Separation Matters
- The Full MCP Authorization Flow, Step by Step
- MCP Protected Resource Metadata Explained
- Comparison Table: Resource Server vs Authorization Server
- 5 Common Mistakes to Avoid
- Real-World Example: Gmail Tool in Claude
- FAQ
- Ready to Implement?
- Useful Sources
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: Bearerheader. 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-serveror/.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:
https://auth.example.com/.well-known/oauth-authorization-serverhttps://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:
- Use the
scopefrom theWWW-Authenticateheader (if present) - Fall back to all scopes in
scopes_supportedfrom 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 attacksstate- CSRF protection; the client must verify this on the callback
Step 7 - User Consent
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:
- Signature - verified against the AS's public keys (JWKs)
- Expiry - token must not be expired
- Audience - token must include this MCP server as the intended audience (RFC 8707)
- 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 Server → Google OAuth (
accounts.google.com). Google authenticates the user, shows a consent screen asking for thegmail.sendscope, and issues an access token. - MCP Resource Server → Your MCP server. It receives the Google-issued token, validates the signature against Google's JWKs, checks that the
gmail.sendscope 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
- MCP Authorization Specification (2025-11-25) - official spec, Model Context Protocol
- RFC 9728 - OAuth 2.0 Protected Resource Metadata - IETF
- RFC 8707 - Resource Indicators for OAuth 2.0 - IETF
- RFC 8414 - OAuth 2.0 Authorization Server Metadata - IETF
- OAuth 2.1 Draft (draft-ietf-oauth-v2-1-13) - IETF
- MCP Authorization in 5 Easy OAuth Specs - WorkOS
Keep reading
MCP Authentication: Implementing OAuth 2.1 with PKCE
A complete guide to securing MCP servers with OAuth 2.1 and PKCE — the auth spec, dynamic client registration, bearer tokens, and token rotation.
How to Audit Third-Party MCP Servers Using mcp-scan
A step-by-step guide to auditing third-party MCP servers with mcp-scan — installation, CLI commands, threat types, tool pinning, CI/CD integration, and security best practices.
MCP Per-Tool Kill Switches: Disable Individual Tools Without Server Downtime
Running 91 GitHub MCP tools can burn 46,000 tokens before your LLM writes a line. Here's how to disable individual MCP tools at runtime — no server restart required.



