For an HTTP MCP server, discover authorization through Protected Resource Metadata, include the MCP server as the OAuth resource, and reject tokens that were not issued for that resource. Authorize the requested tool and arguments after token validation. If the tool calls another API, obtain a separate upstream token; never forward the inbound MCP token. A proxy that shares one upstream client identity also needs explicit consent tied to each MCP client.
The Dangerous Token Can Be Perfectly Valid
A signature check answers only whether a trusted issuer signed a token. It does not answer whether the token was issued for this MCP server, whether this client may call this tool, or whether an upstream API should accept the same credential. Treating those questions as one boolean creates a confused deputy: a component with legitimate authority is tricked into using that authority for the wrong caller or resource.
Consider an MCP proxy in front of a document service. A client presents a token issued for a different internal API. The token is unexpired and correctly signed, so generic middleware accepts it. The proxy then forwards that token—or uses its own broad document credential—to execute documents.delete. Every individual credential may be genuine, but the authorization chain never proved that this client intended and was permitted to reach this MCP resource and this operation.
“The user is signed in” is not a tool authorization decision. Validate issuer, signature, expiry and intended audience first; then evaluate client identity, user identity, scopes, tenant, tool name, arguments and any step-up approval required by the business action.
Draw Three Separate Trust Boundaries
A secure design becomes easier to review when the request is split into three boundaries instead of one long OAuth flow.
- Client to MCP server: the client requests a token for the canonical MCP resource; the server validates that it is the intended recipient.
- MCP policy to tool: deterministic policy decides whether this principal may invoke this tool with these arguments in this tenant.
- MCP server to upstream API: the server acts as an OAuth client or workload toward the upstream and uses a token intended for that upstream resource.
Do not let the model bridge these boundaries. The model may propose calendar.create_event, but deterministic code selects the credential, enforces scope, validates tenant ownership and records the decision. A prompt cannot grant authority, and tool arguments cannot choose an arbitrary token audience.
Publish the Resource, Then Bind the Token to It
The current MCP Authorization specification requires Protected Resource Metadata for authorization-server discovery. An unauthenticated request can receive 401 Unauthorized with a WWW-Authenticate challenge pointing to the metadata document. The client then discovers the correct authorization server instead of guessing which issuer protects the endpoint.
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"
The client includes the MCP server's canonical URI in the OAuth resource parameter during both authorization and token requests. The MCP server must then validate that the presented token was issued for itself. Exact claim mechanics depend on the token format and issuer, but the invariant does not: a token intended for https://api.example.com must not become valid at https://mcp.example.com merely because both services trust the same issuer.
Keep canonicalization boring and explicit. Configure the accepted resource identifiers; do not derive them from an untrusted Host header. Reject missing, unexpected or ambiguous audiences. If several MCP endpoints represent distinct resources, decide and document whether they share one identifier or require separate tokens.
Validate Before You Dispatch a Tool
Centralize token validation before JSON-RPC routing, then apply a per-tool policy. The following pseudocode is deliberately incomplete as OAuth middleware—it shows the order and the data that policy must receive.
async function authorizeMcpRequest(request, toolCall) {
const token = readBearerToken(request);
const claims = await verifyWithTrustedIssuer(token, {
issuer: config.issuer,
audience: config.mcpResource,
algorithms: config.allowedAlgorithms
});
const decision = policy.evaluate({
subject: claims.sub,
clientId: claims.client_id,
tenant: claims.tenant,
scopes: claims.scope,
tool: toolCall.name,
arguments: toolCall.arguments
});
if (!decision.allowed) throw new ForbiddenError();
return { principal: decision.principal, policyId: decision.policyId };
}
Never let a client supply the issuer, accepted audience, verification key URL or upstream credential name in tool arguments. Those are server configuration. Also avoid a single scope such as tools:call for a server that mixes read-only search with destructive writes. Scopes need not encode every record, but they should support meaningful least-privilege gates, with record-level rules handled by policy.
Never Pass the Inbound Token Upstream
Token passthrough erases the resource boundary. An inbound token proves a relationship between a client, an authorization server and the MCP resource. It is not a portable credential for whatever API the selected tool happens to call. Forwarding it can expose the token and can make an upstream service accept authority that was never requested for that service.
Instead, define the upstream authorization mode per connector. The MCP server might use OAuth token exchange, a delegated user token obtained through the upstream authorization server, or a narrowly scoped workload identity. Whichever mode you choose, the resulting access token must be intended for the upstream API and stored separately from the inbound token.
const mcpPrincipal = await authorizeMcpRequest(req, toolCall);
const upstreamToken = await upstreamCredentials.forConnector({
connector: 'calendar',
principal: mcpPrincipal,
resource: 'https://calendar.example.com'
});
return calendar.createEvent(toolCall.arguments, upstreamToken);
Log identifiers and policy outcomes, not bearer tokens. A useful audit record includes the MCP resource, issuer, subject, client ID, tool, policy version, upstream connector, result and correlation ID. Redact arguments that can contain secrets or personal data.
A Proxy Needs Per-Client Consent
A common integration uses one static OAuth client ID when the MCP proxy connects to a third-party authorization server. Meanwhile, many MCP clients can register with the proxy. If the proxy remembers only that the user once approved the upstream client, a malicious new MCP client can exploit that existing consent and send the user through a flow that no longer clearly asks whether this particular client may use the third-party access.
The MCP security guidance therefore requires the proxy to track consent per user and MCP client_id before starting the third-party flow. The consent screen should identify the requesting MCP client and the upstream scopes. Store the decision server-side or in server-specific protected state; do not treat a generic third-party consent cookie as consent to every MCP client.
A previous approval for one client, one tenant or one set of upstream scopes must not silently authorize another. Re-prompt when the client identity or requested authority changes, and make revocation remove the corresponding server-side grant.
Test the Boundary With Hostile Credentials
A happy-path login test proves little. Build fixtures that are cryptographically valid but contextually wrong, because those are the credentials most likely to pass shallow middleware.
Also verify the negative observability path: logs should show why the request was denied without copying the token. Alert on repeated audience failures, client-ID churn and tool denials that precede upstream calls.
A Pull-Request Authorization Gate
Before a new MCP tool is enabled, reviewers should be able to answer: What is the MCP resource identifier? Which issuers and audiences are accepted? Which scopes and record-level rules protect the tool? Who chooses the upstream credential? Is that credential bound to the upstream resource? Does a proxy flow record consent for this MCP client? Which denial tests prove the answers?
Keep this gate next to the tool definition. Pair it with zero-trust architecture for AI agents for capability isolation and with idempotent tool calls for safe retries. Authorization proves who may attempt an action; idempotency prevents one authorized decision from becoming repeated side effects.
Requirements and terminology were checked against the MCP Authorization specification dated 2025-11-25 and the official MCP Security Best Practices. Resource binding is defined in RFC 8707, and protected-resource discovery in RFC 9728. Sources checked Aug 11 2026.