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:

TEXT
Need a badge?
Go to https://auth.example.com
This door belongs to https://mcp.example.com

In 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:

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

If the MCP endpoint lives under a path, the metadata can be path-specific:

TEXT
MCP endpoint:
https://example.com/public/mcp
 
Protected resource metadata:
https://example.com/.well-known/oauth-protected-resource/public/mcp

So 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:

JSON
{
  "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:

TEXT
https://auth.example.com

the client can try:

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

For an issuer with a path, like:

TEXT
https://auth.example.com/tenant1

the draft discovery order keeps the path in view:

TEXT
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-configuration

The 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.

JSON
{
  "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_id match
  • 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:

TEXT
resource=https%3A%2F%2Fmcp.example.com

The 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:

TEXT
all

Better scope design looks like this:

TEXT
search.read
items.create
items.update
comments.delete
admin

The 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:

TEXT
search.read

Later the assistant tries to create something:

TEXT
items.create

The 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:

TEXT
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.

MomentResponseWhy
No token, expired token, or invalid token401The client needs authorization or a fresh token.
Valid token, missing permission403The user is known, but this tool needs more scope.
Bad authorization request400The request is malformed and should be fixed.
Token for another resource401The 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 Authorization header
  • issuer, expiry, audience, resource, and scopes are validated
  • token passthrough is rejected
  • 401 and 403 responses carry useful challenges
  • scopes match real tool risk
  • logs do not contain tokens

For the MCP client:

  • WWW-Authenticate challenges 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
  • resource is 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

  1. Model Context Protocol, "Authorization", version 2025-06-18, https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization 2 3 4

  2. Model Context Protocol draft, "Authorization Server Discovery", https://modelcontextprotocol.io/specification/draft/basic/authorization/authorization-server-discovery

  3. Model Context Protocol draft, "Client Registration", https://modelcontextprotocol.io/specification/draft/basic/authorization/client-registration 2

  4. IETF Internet-Draft, "OAuth Client ID Metadata Document", https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/

  5. RFC 7591, "OAuth 2.0 Dynamic Client Registration Protocol", https://datatracker.ietf.org/doc/rfc7591/

  6. Model Context Protocol draft, "Authorization Security Considerations", https://modelcontextprotocol.io/specification/draft/basic/authorization/security-considerations 2

  7. Model Context Protocol draft, "Authorization", https://modelcontextprotocol.io/specification/draft/basic/authorization