MCP OAuth 2.1 Security: Authorization Enforcement for Remote Servers
MCP''s authorization spec builds on OAuth 2.1, which sets the token rules but leaves the per-call decision to you. This walks through the MCP OAuth flow for remote servers, the confused-deputy risk it introduces, and why an enforcement point that evaluates every tool call against the acting identity is the control that OAuth alone does not provide.

The Model Context Protocol's authorization specification builds on OAuth 2.1. A remote MCP server acts as an OAuth resource server, the client obtains an access token through an authorization server, and each tool call carries that token in an HTTP Authorization header. OAuth 2.1 sets the rules for how the token is issued, scoped, and presented. It does not decide whether a given tool call, made by a given agent on behalf of a given user, should proceed. MCP OAuth 2.1 security is the flow plus the enforcement that fills that gap, and the enforcement runs on the HTTP traffic the token rides on.
I want to walk through the flow, the authorization gaps it leaves open, and where the per-call decision has to live.
What OAuth 2.1 fixed and what it left to you
OAuth 2.1 consolidates a decade of hard-won practice into the current draft. It makes PKCE mandatory for authorization-code flows, removes the implicit grant, and tightens redirect-URI matching. For MCP, that means the token-issuance path for a remote server starts from safer defaults than an ad-hoc scheme would.
The specification governs how a token is obtained and presented. It is deliberately silent on the authorization question a deployer actually cares about at runtime: does this scope, presented by this client, acting for this user, permit this specific tool call with these arguments. OAuth scopes are coarse. A scope like tools:call grants the ability to call tools, not the judgment about which tool, on whose behalf, with what data. That judgment is a policy decision, and OAuth 2.1 leaves it to the resource server or a layer in front of it.
The MCP OAuth flow for a remote server
The MCP authorization spec describes the sequence. The remote server publishes protected-resource metadata pointing at its authorization server. The client discovers that metadata, registers or is pre-registered, and runs an authorization-code flow with PKCE to obtain an access token scoped to the server. Subsequent tool calls present the token as a bearer credential over Streamable HTTP.
Two properties of this flow shape the security model. The token is a bearer token, so anything that presents it is treated as the authorized client, which puts weight on transport security and short token lifetimes. And the token's scope is fixed at issuance, while the sensitivity of a tool call varies per request. A single valid token can front a harmless search call and a destructive delete call in the same session. The flow authenticates the client. It does not evaluate the call, and the gap between those two is where authorization failures live.
The confused-deputy problem sits directly on the OAuth boundary
MCP's confused-deputy risk is an authorization problem expressed in OAuth terms. An agent holds a validly issued token with broad scope. Through a crafted tool description or an injected instruction, the agent is induced to make a call that the end user behind it is not entitled to make. The server validates the token, sees a legitimate scope, and executes. Every OAuth check passes. The authorization decision is still wrong. I covered the attack pattern in the MCP confused-deputy breakdown and the broader consent risk in OAuth consent for AI agents.
The reason token validation alone cannot catch this is structural. The token encodes what the client was granted, not what the acting user is permitted at call time. Closing the gap requires evaluating each call against the acting identity's authorization, which is a decision OAuth issues the credential for but never makes.
Per-call enforcement is the missing layer
The control that OAuth 2.1 does not supply is a policy decision point on the tool traffic. It reads the presented token and, more importantly, the acting identity context the request carries, then checks the specific tool and arguments against a per-role authorization policy before the call reaches the server. This is the same delegated-authority principle NIST describes as Pillar 2 of its agent identity and authorization framework: authority is evaluated per request, per role, under a policy, rather than granted once as a standing credential.
A standing token with broad scope violates least privilege by design, in the same way a static service credential does. The enforcement layer restores least privilege at runtime by making the permit decision on each call instead of trusting the scope stamped into the token at login.
The authorization decision needs an independent record
Every enforced call should leave a per-decision audit record: the acting identity, the token's client and scope, the target tool, the argument classification, the policy version, the outcome, and a timestamp. The record commits before the call reaches the server, independent of both the agent and the server.
That independence is the point. An OAuth resource server that logs its own authorization decisions is attesting to its own behavior, which is the self-attestation problem in a different costume. A decoupled enforcement layer that records the decision on a write path neither the agent nor the server controls produces evidence a reviewer can trust. For regulated agent deployments, that record is what answers the question of who authorized a given action, under which policy, at what moment.
DeepInspect
This is where DeepInspect operates. DeepInspect is a stateless proxy on the HTTP path between your agents and the remote MCP servers and LLM endpoints they reach. It sits after OAuth has issued the token and evaluates the call the token is being used for: it resolves the acting identity, checks the specific tool and arguments against per-role policy, and returns a permit or deny decision before the request reaches the server. OAuth handles issuance. DeepInspect handles the per-call authorization OAuth leaves open.
Every decision produces a signed, per-decision audit record committed ahead of the call, so each authorized action carries independent evidence of who authorized it and under what policy.
If your remote MCP servers rely on OAuth scopes alone to decide what agents may do, book a demo today.
Frequently asked questions
- Does OAuth 2.1 make an MCP server secure on its own?
OAuth 2.1 secures how tokens are issued and presented, and MCP adopting it raises the baseline over an ad-hoc scheme. It mandates PKCE, drops the implicit grant, and tightens redirect handling. What it does not do is decide, per request, whether a specific tool call by a specific agent on behalf of a specific user should proceed. OAuth scopes are coarse and fixed at issuance, while the sensitivity of a tool call varies call by call. So a server that validates tokens correctly can still execute an authorization mistake, because the token was valid and the decision about this particular call was never made. Token validation is necessary and not sufficient. The per-call authorization decision is a separate layer.
- What is the confused-deputy problem in MCP OAuth?
It is the case where an agent holding a validly issued token is manipulated into making a call the end user behind it is not entitled to make. A crafted tool description or an injected instruction induces the agent to act, the server validates the bearer token, sees a legitimate scope, and executes. Every OAuth check passes and the authorization outcome is still wrong, because the token encodes what the client was granted rather than what the acting user is permitted at that moment. The fix is to evaluate each call against the acting identity's authorization at an enforcement point in front of the server, so a call outside the user's permission is denied even though the token itself is valid.
- Where should MCP authorization be enforced?
At a policy decision point on the HTTP path to the remote server, evaluated per call. Enforcing only inside the resource server couples the authorization decision to the component being protected and produces self-attested logs. Enforcing at a decoupled layer in front of the server lets you apply one authorization policy across many MCP servers, evaluate the acting identity rather than only the token scope, and record the decision independently. The enforcement point reads the token and the identity context, checks the specific tool and arguments against policy, and returns a decision before the server acts. This is the runtime authorization layer OAuth issues credentials for but does not itself perform.
- How do OAuth scopes relate to per-role policy?
Scopes are the grant stamped into a token at issuance; per-role policy is the decision applied to each call at runtime. A scope like
tools:callsays the client may call tools in general. A per-role policy says which tools this role may call, with which arguments, on whose behalf, and under what conditions, evaluated at the moment of the call. Scopes are useful for coarse gating and token minimization, but they cannot express the per-request judgment a real authorization model needs, and they do not change as the acting user changes within an agent session. Per-role policy at an enforcement point provides that judgment, using the scope as one input rather than the whole decision.- Does per-call enforcement add latency to MCP tool calls?
The enforcement decision is a policy evaluation on the request, not another model inference, so its overhead is small relative to the work the tool call itself does. In DeepInspect internal testing, enforcement overhead measures under 50 ms, while the network round trip and server-side execution of a tool call typically dominate the timeline. The evaluation reads the token and identity context, checks the tool and arguments against policy, and returns a decision. Because it runs inline on the path the call already travels, it does not add a separate hop that doubles latency. For a deployment already making remote HTTP tool calls, the enforcement step sits inside the existing request budget rather than on top of it.