June 21, 2026
MCP in production
A conversational walkthrough of what changes when a remote MCP server moves beyond localhost: OAuth 2.1, discovery, CIMD, DCR, scopes, PKCE, resource indicators, audience checks, and step-up authorization.
The first MCP demo feels almost unfairly simple.
You expose a tool. The model sees it. The model calls it. Something useful happens.
It is a lovely little magic trick.
Then you try to put the same thing in front of real users, real accounts, and real data. Suddenly the interesting part is not the tool call anymore. The interesting part is the doorway around it.
Think of remote MCP like a busy conference venue.
There are rooms inside: search, files, tickets, calendars, reports, payments, databases. MCP is the map that tells an assistant which rooms exist and how to ask for help inside them.
OAuth is the badge desk at the entrance.
The assistant does not get a master key. It gets a badge, for one user, with specific rooms printed on it.
The demo is the side door.
Production is the badge desk.
The side door
What changes when MCP leaves localhost?
Local MCP can be wonderfully direct. A desktop app starts a local server over stdio. Secrets can come from environment variables or local config. The trust boundary is mostly the user's machine.
Remote MCP is different. Now an HTTP server is receiving requests for user-owned data. It may be called by clients it has never seen before. It has to know whether the user approved the call, whether the token is meant for this server, and whether the requested tool is inside the user's permission.
That is where the production shape begins.
The MCP authorization spec says HTTP-based transports should follow its authorization profile when auth is supported, while stdio should generally use credentials from the environment.1
Who is standing at the badge desk?
In OAuth terms, the badge desk is the authorization server.
The MCP server is the protected venue. It has the rooms and tools. The MCP client is the visitor's assistant. The user is the person who owns the badgeable access.
The authorization server handles consent and issues the access token. The MCP server validates that token before letting the request reach the tool.
So the shape is:
- MCP client: "I want to help this user."
- Authorization server: "Let me ask the user and mint a badge."
- MCP server: "Let me inspect the badge before opening the room."
The locked door
How does the client find the badge desk?
It should not guess.
When a client reaches a protected MCP server without a valid token, the server can return 401 Unauthorized and point the client to its Protected Resource Metadata.
That metadata tells the client which authorization server can issue tokens for this MCP server.
In the venue analogy, the locked door has a small sign:
Need a badge?
Go to https://auth.example.com
This door belongs to https://mcp.example.comIn protocol terms, MCP uses OAuth 2.0 Protected Resource Metadata. Discovery can happen through a WWW-Authenticate header with a resource_metadata URL, or through a well-known metadata URL. The current draft requires clients to support both paths.2
The well-known route is worth spelling out because this is where a lot of integrations quietly break:
https://example.com/.well-known/oauth-protected-resourceIf the MCP endpoint lives under a path, the metadata can be path-specific:
MCP endpoint:
https://example.com/public/mcp
Protected resource metadata:
https://example.com/.well-known/oauth-protected-resource/public/mcpSo a client can first follow the resource_metadata URL from WWW-Authenticate when the server gives one. If not, it can fall back to constructing the well-known route for the exact MCP endpoint, and then the root well-known route.
What is written on the door sign?
Protected Resource Metadata is the MCP server publishing the auth facts a client needs before it can start the OAuth flow.
It may look roughly like this:
{
"resource": "https://mcp.example.com",
"authorization_servers": ["https://auth.example.com"],
"scopes_supported": ["search.read", "items.write", "admin"]
}The exact fields depend on the server, but the idea is simple:
- this is the protected resource
- this is where tokens come from
- these are the kinds of permissions I understand
That one sign saves every new client from hardcoded setup.
What does the badge desk publish?
The authorization server has its own metadata too.
The client fetches Authorization Server Metadata to learn:
- the authorization endpoint
- the token endpoint
- supported scopes
- supported grant types
- supported response types
- supported PKCE methods
- whether Client ID Metadata Documents are supported
- whether Dynamic Client Registration is supported
Here too, there are well-known routes. For an issuer like:
https://auth.example.comthe client can try:
https://auth.example.com/.well-known/oauth-authorization-server
https://auth.example.com/.well-known/openid-configurationFor an issuer with a path, like:
https://auth.example.com/tenant1the draft discovery order keeps the path in view:
https://auth.example.com/.well-known/oauth-authorization-server/tenant1
https://auth.example.com/.well-known/openid-configuration/tenant1
https://auth.example.com/tenant1/.well-known/openid-configurationThe client should reject metadata whose issuer does not exactly match the issuer it was trying to discover. Otherwise a malicious or misconfigured metadata document can point the flow at the wrong badge desk.
The MCP stable auth spec builds on OAuth 2.1, OAuth Authorization Server Metadata, Dynamic Client Registration, Protected Resource Metadata, and Resource Indicators.1
The venue version: the badge desk publishes its operating rules before visitors queue up.
The stranger at the desk
How does an unknown client introduce itself?
This is the part many MCP explainers skip.
Traditional OAuth often assumes the client has already registered with the authorization server. MCP cannot always assume that.
An MCP client may discover a remote server today and its authorization server ten seconds later. There may be no prior relationship. No shared dashboard setup. No pre-created OAuth app.
So the client needs a way to say, "this is who I am," without forcing manual setup for every server-client combination.
There are three practical paths:
- use pre-registered client details when the relationship already exists
- use CIMD when the authorization server supports it
- use DCR as a fallback or backwards-compatible path
The MCP draft now lists that priority explicitly: pre-registration first when available, Client ID Metadata Documents next, Dynamic Client Registration as fallback, then manual client details if nothing else works.3
What is CIMD?
CIMD means Client ID Metadata Document.
It turns the OAuth client_id into an HTTPS URL. That URL points to a JSON document describing the client.
{
"client_id": "https://client.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"client_uri": "https://client.example.com",
"redirect_uris": [
"http://127.0.0.1:3000/callback",
"http://localhost:3000/callback"
],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}The authorization server sees the URL-shaped client_id, fetches the document, checks that the document's client_id exactly matches the URL, and uses the metadata inside.
The venue version: the visitor carries a public card. The card is not a secret. It tells the badge desk the visitor's name, homepage, redirect addresses, and the kind of OAuth flow it wants to use.
This avoids creating a fresh registration record for every unknown client.
Is CIMD just free trust?
No. CIMD is discovery, not blind trust.
The authorization server is fetching a URL supplied by an unknown client. That means it has to defend itself.
Good production behavior includes:
- prevent SSRF when fetching metadata
- limit response size
- require HTTPS client IDs
- validate JSON structure
- require exact
client_idmatch - validate redirect URIs against the metadata
- display hostnames clearly on consent screens
- apply allowlists or reputation rules when the deployment needs them
The IETF Client ID Metadata Document draft calls out SSRF, phishing, client metadata changes, and display risks as security considerations.4
The badge desk still decides whom to admit.
Where does DCR still fit?
DCR means Dynamic Client Registration.
Instead of the client hosting a metadata document, the client sends registration metadata to the authorization server and receives a client_id in response. RFC 7591 defines this flow.5
The venue version: the badge desk creates a visitor profile on the spot.
DCR was central in earlier MCP authorization guidance. The stable 2025-06-18 spec says MCP clients and authorization servers should support DCR because clients may not know every MCP server and authorization server ahead of time.1
The draft direction is different: new implementations should prefer CIMD when available and keep DCR for fallback and compatibility.3
That distinction matters if you are shipping now. Stable clients and servers may still expect DCR. Newer implementations should be ready for CIMD.
The badge itself
Why does the badge need the venue address?
Because a badge for one venue should not open another.
OAuth Resource Indicators let the client include a resource parameter in both the authorization request and the token request.
For MCP, the resource is the MCP server the client wants to access:
resource=https%3A%2F%2Fmcp.example.comThe stable MCP auth spec says clients must include resource and servers must validate that tokens were issued for them.1
This is audience binding in everyday language. The badge says "Main Hall" on it. The person at the door checks that it says "Main Hall", not "Parking Garage", not "Another Conference", not "Some API from the same company."
What stops a stolen authorization code?
PKCE.
Before the client sends the user to the authorization server, it creates a private code_verifier and a derived code_challenge.
The authorization server sees the challenge. Later, when the client exchanges the authorization code for tokens, it must present the verifier.
If someone steals the code but not the verifier, the code is not useful.
The venue version: when you entered the badge queue, you tore off half of a claim ticket and kept it. A stolen pickup slip is not enough without the matching half.
The MCP draft says clients must implement PKCE and verify PKCE support before proceeding.6
What are scopes really?
Scopes are the rooms printed on the badge.
Bad scope design looks like this:
allBetter scope design looks like this:
search.read
items.create
items.update
comments.delete
adminThe exact names depend on the product, but the shape should match risk. A read-only search tool should not need the same permission as a destructive delete tool.
For MCP servers, scopes can map to tool families, read vs write actions, sensitive resources, tenant boundaries, admin operations, or workflow stages.
The useful question is not "how many scopes can we invent?" It is "what permission boundary would a user actually understand?"
What if the badge is not enough?
Then the server should ask for a better badge, not fail mysteriously.
Imagine the user first approves:
search.readLater the assistant tries to create something:
items.createThe MCP server can return 403 Forbidden with an insufficient_scope challenge that tells the client which scope is missing. The client can then take the user back through authorization for the additional permission.
That is step-up authorization.
The draft guidance recommends returning all scopes needed for the current operation in one challenge, instead of making the client discover them one painful retry at a time.7
Should the client ask for everything at the start?
Usually no.
If the first consent screen asks for every possible permission, users learn to click through scary prompts. If the client asks for too little, the workflow becomes a maze of interruptions.
A practical middle:
- start with the smallest useful read scope
- request write scopes when the user begins a write workflow
- group scopes that naturally belong to the same task
- step up at meaningful risk boundaries
- explain the new permission in product language, not protocol language
The badge should grow with the user's intent.
The thing you should not do
Can the MCP server pass the badge to another API?
It should not pass through the token it received from the MCP client.
Token passthrough is dangerous because the downstream API may interpret the token differently. That can create a confused deputy problem: the MCP server accidentally helps the caller use authority somewhere else.
The safer pattern is:
- the client gets a token for the MCP server
- the MCP server validates that token
- if the MCP server needs an upstream API, it uses a separate credential or obtains a separate upstream token
One badge per venue.
The MCP draft is explicit that servers must validate tokens for themselves and must not pass through tokens received from MCP clients.6
What about refresh tokens?
An access token is today's badge.
A refresh token is permission to ask the badge desk for another badge later.
That can be useful. It can also be risky. If a refresh token leaks, the attacker may keep coming back.
Production defaults:
- store refresh tokens carefully
- rotate refresh tokens for public clients
- do not assume every authorization server will issue one
- request offline access only when the experience needs it
- do not make refresh tokens a resource requirement of the MCP server
Long-lived access should feel earned, not accidental.
The whole dance
Here is the compact version:
Client tries MCP server.
Server says: 401, get auth metadata here.
Client fetches protected resource metadata.
Client finds the authorization server.
Client fetches authorization server metadata.
Client identifies itself with pre-registration, CIMD, or DCR.
Client starts authorization code flow with PKCE.
Client includes resource=https://mcp.example.com.
User approves scopes.
Client exchanges code for tokens.
Client calls MCP server with Authorization: Bearer <token>.
Server validates issuer, expiry, audience, resource, and scopes.
Tool runs.
If a later tool needs more scope, server triggers step-up.That is the bridge from "the model can call my tool" to "the model can safely use my tool for the right user."
What should fail loudly?
Production MCP auth should be generous with recovery hints and strict with trust.
| Moment | Response | Why |
|---|---|---|
| No token, expired token, or invalid token | 401 | The client needs authorization or a fresh token. |
| Valid token, missing permission | 403 | The user is known, but this tool needs more scope. |
| Bad authorization request | 400 | The request is malformed and should be fixed. |
| Token for another resource | 401 | The badge is real, but it is not for this venue. |
The best error is not just a wall. It is a locked door with the correct sign next to it.
Before calling it production
For the MCP server:
- Protected Resource Metadata exists
- authorization servers are advertised clearly
- tokens are accepted only through the
Authorizationheader - issuer, expiry, audience, resource, and scopes are validated
- token passthrough is rejected
401and403responses carry useful challenges- scopes match real tool risk
- logs do not contain tokens
For the MCP client:
WWW-Authenticatechallenges are parsed- well-known resource metadata discovery works
- authorization server metadata discovery works
- pre-registration is used when configured
- CIMD is used when supported
- DCR fallback still works where needed
- PKCE is mandatory
resourceis sent in authorization and token requests- token and registration state are bound to the authorization server
- step-up authorization feels understandable
For the authorization server:
- metadata is published
- CIMD or DCR support is advertised accurately
- redirect URIs are exact-match validated
- PKCE support is visible and enforced
- unknown clients go through trust policy
- CIMD fetches are SSRF-protected
- tokens are audience-bound
- refresh tokens are rotated for public clients
- consent screens show the client and requested access clearly
The quiet part
MCP makes the rooms discoverable.
OAuth decides who can enter them.
Protected Resource Metadata is the sign on the locked door.
Authorization Server Metadata is the badge desk manual.
CIMD is a public visitor card.
DCR is on-the-spot visitor registration.
PKCE is the matching claim ticket.
The resource parameter prints the venue address on the badge.
Audience validation keeps badges from wandering into the wrong venue.
Scopes are the rooms on the badge.
Step-up authorization adds a room only when the user actually walks toward it.
That is the part that turns MCP from a clever demo into a production doorway.
Footnotes
-
Model Context Protocol, "Authorization", version
2025-06-18, https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization ↩ ↩2 ↩3 ↩4 -
Model Context Protocol draft, "Authorization Server Discovery", https://modelcontextprotocol.io/specification/draft/basic/authorization/authorization-server-discovery ↩
-
Model Context Protocol draft, "Client Registration", https://modelcontextprotocol.io/specification/draft/basic/authorization/client-registration ↩ ↩2
-
IETF Internet-Draft, "OAuth Client ID Metadata Document", https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/ ↩
-
RFC 7591, "OAuth 2.0 Dynamic Client Registration Protocol", https://datatracker.ietf.org/doc/rfc7591/ ↩
-
Model Context Protocol draft, "Authorization Security Considerations", https://modelcontextprotocol.io/specification/draft/basic/authorization/security-considerations ↩ ↩2
-
Model Context Protocol draft, "Authorization", https://modelcontextprotocol.io/specification/draft/basic/authorization ↩