On this page
- Why this matters now
- The production architecture
- Discovery: make the MCP server identify itself
- The resource parameter is not optional glue
- Token passthrough is the failure mode
- Tool schemas do not replace authorization
- Scope design: start small, elevate deliberately
- Discovery is also an SSRF surface
- Rollout checklist for production teams
- Server contract
- Client contract
- Operations contract
- Common pitfalls
- The engineering posture
- Pre-publish implementation checks
- References
Remote Model Context Protocol servers are becoming shared infrastructure. That changes the authorization problem.
When MCP lived mostly as a local stdio bridge inside a developer workstation, many teams could treat credentials as environment configuration: a token in a local config file, a narrowly scoped service account, maybe a wrapper script that knew which API it was allowed to touch. That model does not survive contact with hosted MCP endpoints, multi-tenant agent platforms, browser-based clients, or enterprise identity systems.
A protected remote MCP server is not just another API endpoint that happens to speak JSON-RPC. Under the MCP authorization specification, it acts as an OAuth protected resource. The client discovers which authorization server to use, requests a token for a specific MCP resource, sends the token on every protected request, and the server validates that the token was issued for that MCP server. That boundary is the difference between an agent platform and a token relay.
The practical takeaway: if you are putting MCP servers on the network, design the auth layer around resource identity, audience validation, consent, and least privilege. Do not build a thin API-key passthrough and call it production-ready.
Why this matters now
Agent infrastructure expands the number of places where a token can move.
A single user prompt can cause a host to choose a client connector, discover a server, list tools, invoke a tool, follow a resource link, and ask the user for more information. Each step creates an opportunity to confuse identities: user identity, client identity, MCP server identity, downstream API identity, and model-facing tool identity. If the system blurs those boundaries, a compromised client or malicious server can turn one valid token into broader access than the user intended.
MCP's 2025-06-18 authorization model is explicit about this. Authorization is optional for MCP overall, but when an HTTP-based protected MCP server supports authorization, the server is treated as an OAuth resource server. It must publish protected resource metadata, the client must discover authorization server metadata, and tokens must be requested for the MCP server as the intended resource.
There is one caveat worth making explicit: the MCP spec references OAuth 2.1 draft behavior alongside established OAuth specifications such as RFC 8414, RFC 7591, RFC 8707, and RFC 9728. Production teams should implement the normative MCP requirements, but they should also test their chosen identity provider for exact support around dynamic client registration, resource indicators, PKCE, token audience claims, and metadata discovery. Do not assume every enterprise IdP supports the same subset.
That sounds like OAuth plumbing, but the engineering implication is larger: the MCP server has its own security perimeter. It is responsible for validating inbound tokens, applying access controls to tools and resources, and acquiring separate downstream credentials when it calls another API. It must not accept or forward arbitrary user tokens as if it were an untrusted pipe.
The production architecture
A production remote MCP deployment has at least four roles:
- Host: the application the user interacts with, such as an IDE, internal agent console, or chat product.
- MCP client: the connector inside the host that speaks MCP to one server.
- MCP server: the protected resource exposing tools, resources, prompts, and protocol utilities.
- Authorization server: the identity component that authenticates the user and issues access tokens for the MCP server.
The authorization server may be co-hosted with the MCP server, but it is still a distinct role. That distinction is useful because the MCP server should be able to say, "These are the authorization servers that can issue tokens for me," and the client should be able to request a token whose audience is this MCP server, not a random downstream API.
sequenceDiagram
autonumber
participant User
participant Host as Agent host
participant Client as MCP client
participant MCP as Protected MCP server
participant AS as Authorization server
participant API as Downstream API
User->>Host: Connect data/tool source
Host->>Client: Create MCP connection
Client->>MCP: Request protected resource
MCP-->>Client: 401 + WWW-Authenticate metadata URL
Client->>MCP: Fetch protected resource metadata
Client->>AS: Discover auth metadata
Client->>AS: Authorize with resource=MCP canonical URI
AS-->>Client: Access token for MCP server
Client->>MCP: MCP request + Authorization: Bearer ***
MCP->>MCP: Validate issuer, audience, scopes, expiry
MCP->>API: Call downstream API with separate credential
API-->>MCP: API response
MCP-->>Client: Tool/resource result
Client-->>Host: Result with policy context
The important line is not the OAuth redirect. It is the handoff from MCP to the downstream API. The MCP server validates a token issued for itself, then uses a separate credential or delegated flow for the downstream system. It does not pass the client's token through.
A useful design review is to ask which boundary owns each decision:
| Decision | Owner | Failing closed should mean |
|---|---|---|
| Which MCP server is being accessed? | MCP client + protected resource metadata | No token request until the resource is known |
| Which identity provider issues tokens? | Protected resource metadata + auth server metadata | Reject unknown or downgraded issuers |
| Is this token for this server? | MCP server validation layer | Reject before JSON-RPC dispatch |
| May this caller run this tool? | MCP server authorization policy | Return a scoped authorization challenge or denial |
| May the server call the downstream API? | MCP server + downstream credential policy | No passthrough; use a separate credential path |
This table is also a good incident-response map. When a bad call lands in production, you can identify whether discovery, token issuance, token validation, tool authorization, or downstream delegation failed.
Discovery: make the MCP server identify itself
Protected resource metadata is the contract that lets a client understand a protected MCP server before it asks for a token.
RFC 9728 defines a metadata document for OAuth protected resources. In MCP, a protected server must use this mechanism to indicate which authorization servers can issue tokens for it. The metadata can also describe supported scopes, bearer-token methods, documentation URLs, terms, policy URLs, JWKs, and human-readable resource names.
A minimal metadata response for an MCP server might look like this:
{
"resource": "https://mcp.example.com/mcp",
"resource_name": "Example Production MCP Server",
"authorization_servers": [
"https://auth.example.com"
],
"scopes_supported": [
"mcp:tools.read",
"mcp:tools.execute",
"tickets.read",
"tickets.write"
],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://docs.example.com/mcp"
}
The first production check is simple: the resource value must be the canonical URI the client uses in OAuth resource parameters. If the server is available at multiple paths or behind a gateway, standardize the resource identifier and test it. Inconsistent resource strings create intermittent failures, duplicate consent prompts, and weak audience validation.
The second check is operational: unauthenticated requests should fail in a way clients can use. MCP authorization relies on WWW-Authenticate to point clients at protected resource metadata during a 401 Unauthorized response. If your gateway strips that header, your OAuth flow may work in staging and fail in real clients.
The resource parameter is not optional glue
The MCP authorization specification requires clients to include the OAuth resource parameter in both authorization requests and token requests. That parameter identifies the MCP server the token is intended for.
This matters because without audience binding, tokens become portable bearer strings. A token minted for one service can be replayed against another service that forgets to validate audience. In agent systems, that is especially dangerous because tool calls can hide the path a credential takes. The user sees "read tickets" or "summarize repository"; the runtime may be crossing several services.
Production clients should treat resource as a first-class field in their connection configuration:
MCP server URL: https://mcp.example.com/mcp
OAuth resource: https://mcp.example.com/mcp
Authorization issuer: https://auth.example.com
Allowed redirect URI: http://127.0.0.1:3917/oauth/callback
Baseline scopes: mcp:tools.read tickets.read
Production servers should reject tokens that are missing the expected audience, have the wrong issuer, have expired, or lack the required scope for the requested tool/resource. This validation belongs before JSON-RPC dispatch. The tool handler should not be the first line of authorization defense.
Token passthrough is the failure mode
The most tempting remote MCP implementation is also the most dangerous:
- The client obtains a token for a downstream SaaS API.
- The client sends it to the MCP server.
- The MCP server forwards that same token to the downstream API.
- The MCP server returns the result as a tool response.
That model makes the MCP server a confused proxy. It cannot reliably prove the token was issued for it. It may not know which client initiated the call. Its logs lose accountability. Downstream services may see access patterns that bypass the controls the MCP server was supposed to enforce. If the token leaks inside MCP logs, traces, prompts, or tool errors, the blast radius is the downstream API, not just the MCP layer.
The MCP security guidance calls token passthrough an anti-pattern and explicitly forbids accepting tokens that were not issued for the MCP server. In production, treat this as a deploy gate: an MCP server should either validate an audience-bound token issued for itself or refuse the request.
When the MCP server needs a downstream API, use one of these patterns instead:
| Pattern | Use when | Operational requirement |
|---|---|---|
| Server-owned service credential | The server performs bounded backend operations | Strict server-side authorization, audit logs, and rate limits per user/client |
| On-behalf-of exchange | The identity provider supports delegated token exchange | Token exchange policy binds user, client, resource, scopes, and expiry |
| Per-user linked account | The user connects a SaaS account through the MCP service | Store refresh tokens securely, rotate, audit, and let users revoke |
| Human-approved elevation | The tool performs sensitive writes or admin actions | Step-up consent, visible tool input, short-lived scope, and callback audit trail |
The pattern is less important than the boundary: inbound MCP token and outbound downstream credential are not the same artifact.
Tool schemas do not replace authorization
MCP tools can expose input schemas and output schemas. Structured tool output helps clients validate results and helps models reason about returned data. This is good engineering, but it is not an authorization model.
A tool schema can say that ticket_id is a string. It cannot prove the caller may read that ticket. A tool annotation can describe a tool as read-only. Clients must treat annotations as untrusted unless they come from trusted servers. A model may choose when to call a tool, but the server must still enforce access controls.
Put authorization checks on the server side:
- Map scopes to tool families, not only to endpoints.
- Check object-level permissions inside tool handlers.
- Revalidate user/client identity on long-running operations.
- Treat tool output as data that can leak secrets; sanitize before returning.
- Rate-limit high-cost tools by user, client, and tenant.
For sensitive operations, keep a human in the loop. The tools specification recommends visible UI and confirmation prompts for operations where the user should understand what the model is about to do. That UX requirement is part of the security system, not decorative polish.
Scope design: start small, elevate deliberately
The bad scope model is easy to recognize: mcp:all, files:*, or admin granted during the first connection because the team did not want to revisit OAuth later.
A better model starts with discovery and low-risk reads, then elevates at the moment of need. For example:
mcp:tools.readfor listing tools and reading tool metadata.tickets.readfor search and summarization.tickets.writefor creating or updating tickets.tickets.adminfor workflow configuration.
When a user asks the agent to perform a write, the server can return an authorization challenge for the missing scope. The client can request only that scope, show the user the reason, and continue if approved. The result is better security and better consent comprehension: users approve the operation they actually asked for, not a wall of speculative permissions.
Servers should also tolerate down-scoped tokens. If an authorization server grants fewer scopes than requested, the MCP server should allow what the token permits and challenge only when the user crosses a boundary.
Discovery is also an SSRF surface
OAuth metadata discovery requires clients to fetch URLs. A malicious or compromised MCP server can try to abuse that behavior by pointing metadata fields at internal addresses, cloud metadata endpoints, or unexpected hosts.
Client implementers should apply the same network controls they would use for webhook delivery or URL unfurling:
- Require HTTPS for authorization metadata except explicit localhost development flows.
- Block private IP ranges, link-local addresses, and cloud metadata endpoints.
- Resolve DNS safely and defend against DNS rebinding.
- Enforce allowlists for enterprise-managed deployments.
- Set short timeouts and small response limits.
- Log metadata URL, resolved IP, redirect chain, and decision.
This is not theoretical hygiene. Agent clients often run in privileged environments: developer laptops, CI runners, internal admin consoles, or cloud workers with metadata credentials. OAuth discovery must not become a general-purpose internal network scanner.
Rollout checklist for production teams
Use this checklist before exposing a remote MCP server beyond a single trusted development environment.
Server contract
- Publish protected resource metadata for the canonical MCP resource.
- Return
401with a usableWWW-Authenticatechallenge when authorization is missing. - Accept bearer tokens only in the
Authorizationheader. - Validate issuer, audience/resource, expiry, signature, tenant, and scopes before dispatch.
- Reject tokens issued for downstream APIs or other MCP resources.
- Keep inbound MCP tokens out of logs, traces, tool results, and prompts.
Client contract
- Discover protected resource metadata and authorization server metadata.
- Send the
resourceparameter in both authorization and token requests. - Use PKCE for authorization-code flows.
- Register exact redirect URIs; do not rely on wildcard matching.
- Apply SSRF protections during metadata discovery.
- Show meaningful consent and elevation UI for sensitive tools.
Operations contract
- Version resource identifiers intentionally.
- Alert on audience-validation failures, repeated scope challenges, and token validation errors.
- Sample traces for tool latency and failures without logging bearer tokens.
- Contract-test the
WWW-Authenticateheader through every gateway and CDN path. - Load-test metadata discovery and token validation caches.
- Run periodic negative tests with wrong-audience, expired, and over-scoped tokens.
Common pitfalls
Using the public URL as the resource one day and the internal URL the next. Pick the canonical resource URI and stick to it. If paths distinguish tenants or server instances, document that choice.
Letting the gateway terminate auth but not forwarding identity safely. If a gateway validates tokens, the MCP server still needs a trustworthy identity context and defense against header spoofing. Mutual TLS, signed identity headers, or colocated validation are safer than plain forwarded headers.
Treating dynamic client registration as universally available. The MCP spec recommends support because clients may not know servers in advance. Some enterprise authorization servers still require explicit registration. Build a fallback onboarding path rather than silently skipping audience binding.
Mixing consent layers. User consent to connect an MCP client is not the same as consent for a proxy server to access a third-party API, and neither is the same as confirmation for a destructive tool call. Keep the layers visible.
Publishing scopes as a product catalog. scopes_supported can help clients, but it should not become an invitation to request every possible privilege. Design baseline scopes and challenge for elevation.
The engineering posture
Remote MCP is useful because it standardizes how agents reach tools and context. That standardization only pays off if teams also standardize the security boundary.
The boundary is straightforward:
- The MCP server identifies itself as a protected resource.
- The client asks for a token for that resource.
- The server validates that the token was issued for that resource.
- The server performs tool/resource authorization before execution.
- The server uses separate credentials for downstream systems.
Everything else is implementation detail. Important detail, but detail.
If your MCP server is still accepting long-lived API keys, forwarding user tokens to downstream APIs, or relying on tool schemas as a substitute for access control, treat that as technical debt. The production pattern is audience-bound OAuth, explicit consent, least-privilege scopes, and observable authorization failures.
That is what lets MCP become agent infrastructure instead of another place where secrets go to sprawl.
Pre-publish implementation checks
Before this pattern becomes a platform standard, automate the checks that tend to regress:
- A request without a token returns
401and a parseableWWW-Authenticatechallenge. - A token with the wrong audience is rejected before the MCP method handler runs.
- A token for the right audience but missing scope receives a precise denial or elevation challenge.
- Metadata discovery refuses private IP ranges and unexpected redirects.
- Tool execution logs include correlation IDs, user/client identity, tool name, and authorization decision, but never bearer tokens.
- Documentation tells client teams the canonical resource URI, supported scopes, and redirect URI requirements.
These checks are mundane. That is the point. Authorization boundaries should be boring enough to test in CI and visible enough to debug at 2 a.m.