
DEEP DIVE
MCP, OAuth, and the Agent Permissioning Problem
The 2026-07-28 MCP revision makes servers strict OAuth 2.1 resource servers with audience-bound tokens and 403 scope challenges. It still does not say which agent is acting for which user.
TL;DR
MCP's authorization story is no longer vague. The 2026-07-28 revision makes an MCP server a strict
OAuth 2.1 resource server: tokens audience-bound through RFC 8707, discovery through RFC 9728, issuer
checks from RFC 9207, and a 403 insufficient_scope challenge for stepping up. What it still does not
answer is which agent, acting for which user, may take a given action.
The spec is explicit about that boundary. An MCP server "MUST validate that access tokens were issued specifically for them as the intended audience", and it "MUST NOT pass through the token it received from the MCP client". Both rules govern where a token may be spent. Neither says who is behind the call.
The common reading is that this is an oversight the next revision will close. We don't think it will. Dynamic Client Registration was deprecated in this revision in favour of Client ID Metadata Documents, which identify the client software by URL. That is a statement about which program is calling, not about the person it is working for.
None of this is a complaint about MCP. Audience binding and per-operation scope challenges remove real attacks, and the confused-deputy write-up in the security guidance is sharper than most vendor threat models. The point is narrower: those are protocol mechanics, and one specific action is not a protocol question.
Today: return scope in every WWW-Authenticate challenge, keep scopes_supported to the minimum your
server needs, and put the user-and-agent decision in one enforcement point instead of in each server.
Overview
Take a procurement agent that reconciles supplier statements against purchase orders. It reaches three MCP servers: one in front of the ERP, one in front of a document store, and one a teammate wrote in an afternoon to query a warehouse. All three implement the current protocol revision correctly. All three validate token audiences. And nothing in that system can answer whether this agent, working for this buyer, should be allowed to issue a credit note at 02:00.
The interesting thing is how much smaller that gap has become. MCP's authorization chapter used to be
one page of advice; the 2026-07-28 revision cites ten specifications, requires RFC 9728 Protected
Resource Metadata on the server, requires the RFC 8707 resource parameter on the client, adds RFC 9207
issuer validation to the authorization response, and deprecates Dynamic Client Registration in favour of
Client ID Metadata Documents. If you last looked at this in 2025, we would assume most of what you remember has
changed.
The general case for why OAuth alone does not cover autonomous agents is covered separately. This post is narrower and more mechanical: what the current MCP revision genuinely settles, what it deliberately leaves to the implementer, and which of those leftovers show up as production incidents rather than as design debates.
MCP and OAuth are protocols. Permissioning is a decision. The protocols now do a good job of making sure a token is well-formed, correctly addressed, and hard to steal. None of that is the same as deciding that a particular action should happen.
What the Spec Actually Requires
An MCP server over HTTP "acts as an OAuth 2.1 resource server", and the client acts as an OAuth 2.1 client. That framing does most of the work, because it pulls in a pile of requirements that are easy to skip when you are writing auth by hand.
| Concern | What the revision requires | Consequence if you skip it |
|---|---|---|
| Finding the authorization server | Servers MUST implement RFC 9728 and return WWW-Authenticate with a resource_metadata URL on a 401 | Clients cannot connect without out-of-band configuration |
| Naming the intended recipient | Clients MUST send the RFC 8707 resource parameter in authorization and token requests, "regardless of whether authorization servers support it" | Tokens stay general-purpose and replayable across services |
| Audience checking | Servers MUST validate that tokens "were issued specifically for them as the intended audience" | Any token from the same issuer works on your server |
| Not laundering tokens | The MCP server "MUST NOT pass through the token it received from the MCP client" | Confused deputy, and downstream logs that name the wrong caller |
| Identifying the client | Pre-registration first where it exists, then Client ID Metadata Documents; Dynamic Client Registration deprecated in 2026-07-28 | Registration records pile up and cannot be attributed |
| Mix-up defence | Clients record the issuer and validate the iss authorization-response parameter per RFC 9207 | An attacker-controlled authorization server can swap itself in |
| State handles | MCP is stateless, and servers "MUST NOT treat possession of a state handle as authentication" | A guessed cart or workflow ID reads another user's state |
Table 1 — The requirements the current revision pins down, and what breaks without each.
Two of those rows are worth sitting with. The resource parameter is RFC 8707, which exists because
"scope is typically about what access is being requested rather than where that access will be
redeemed". MCP requires clients to send it whether or not the authorization server understands it, which
is a bet on adoption: send the parameter now, get audience-restricted tokens later when the server
catches up. The canonical form is the server's own URI, so https://mcp.example.com/mcp is valid and
mcp.example.com is not.
The registration row is the bigger change. Dynamic Client Registration, RFC 7591, was how every 2025-era
MCP client got a client_id from a server it had never met. It is now listed in the deprecated-features
registry with a migration path to Client ID Metadata Documents and an earliest removal of "first
revision released on or after 2027-07-28". Under the replacement draft, the client_id is an HTTPS URL
that "refers to a document containing the necessary client metadata", and the authorization server
fetches and validates it, including checking that the document's own client_id matches the URL exactly.
The practical effect is that client identity becomes portable and inspectable instead of being a row in
somebody's registration table.
The Scope Challenge Loop
The part of the revision that changes day-to-day code is the error contract. Three status codes, and they mean different things.
| Status | When | What the client should do |
|---|---|---|
| 400 | Malformed authorization request | Fix the request. Not retryable |
| 401 | No token, or the token is invalid or expired | Read resource_metadata from WWW-Authenticate, discover, authorize |
| 403 | Token is valid but lacks the scopes for this operation | Union the challenged scopes with what you already hold, re-authorize, retry |
Table 2 — The MCP authorization error contract, and the correct client response to each.
The 403 is the one people get wrong, and RFC 6750 is unambiguous about it: insufficient_scope means
"the request requires higher privileges than provided by the access token", and it maps to 403, not 401.
MCP builds a step-up flow on top. Servers SHOULD include a scope parameter in the challenge, and
clients MUST treat the challenged scopes "as authoritative for the current operation".
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="files:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
error_description="File write permission required for this operation"Two details in that flow save real outages. First, the client computes the union of what it already requested and what the challenge asks for, because a server emitting per-operation challenges will otherwise walk the client down to a single narrow scope and break every other tool it had working. Second, servers SHOULD emit every scope needed for the current operation in one challenge. The spec says why: challenging incrementally "forces multiple authorization round-trips for a single operation and degrades user experience". A user clicking through four consent screens to send one message is a bug in your challenge construction, not in their patience.
The complement is on the server side. scopes_supported is meant to be "the minimal set of scopes
necessary for basic functionality", with everything else requested incrementally. The security guidance
is blunter about the failure this prevents: publishing every scope you own leads to "expanded blast
radius", "audit noise: single omnibus scope masks user intent per operation", and "consent abandonment:
users decline dialogs listing excessive scopes". Wildcard and omnibus scopes such as *, all and
full-access are named as common mistakes.
A scope challenge is a per-operation question with a per-grant answer. The server can compute the required scopes from the specific request arguments, which is genuinely fine-grained. What comes back is a token carrying that scope for every future call, and nothing in the protocol narrows it again.
What Is Left Open
Here is our honest accounting of what none of the above decides.
Who the agent is. A validated token tells the server two things: a subject, and a client. The client is the software, identified in the new model by a metadata-document URL. Neither field distinguishes an autonomous agent running unattended from the same user's IDE making the same call, and the spec does not ask them to. If your audit record needs to say "the reconciliation agent, acting for this buyer", the protocol will not supply the first half.
Whether this specific action is allowed. Scope is the only vocabulary on the wire, and the guidance
itself lists "treating claimed scopes in token as sufficient without server-side authorization logic" as
a common mistake. Scopes describe classes of operation. files:write does not distinguish a draft folder
from a signed contract, and the server is left to decide.
Who holds the upstream credential. Because token passthrough is forbidden, an MCP server that talks to a real SaaS API "may act as an OAuth client to them", and "the access token used at the upstream API is a separate token". That is correct, and it quietly assigns every MCP server its own credential store, its own refresh logic, and its own disconnect semantics. Ten servers, ten vaults.
How fast revocation lands. The protocol has no revocation primitive at all, so the answer is
whatever the identity provider gives you. Microsoft's revokeSignInSessions invalidates all of a user's
refresh tokens, and the documentation warns that "there might be a small delay of a few minutes before
tokens are revoked". A few minutes is a long time for something making calls in a loop.
The security guidance is at its best on what happens when these leftovers are handled badly. The
confused-deputy attack needs four conditions together: a proxy using a static client ID upstream, MCP
clients registering dynamically, a third-party authorization server that sets a consent cookie, and no
per-client consent at the proxy. Then a malicious link reuses the victim's consent cookie, the consent
screen is skipped, and the authorization code lands on the attacker's redirect_uri. The mitigation is
a registry of approved client_id values per user, checked before the upstream flow, with the state
cookie set only after consent is approved.
one enforcement point -> resolve which agent, acting for which user -> intersect user scopes AND agent scopes -> attach the upstream credential server-side, per user -> forward to MCP server A or B -> both write to one audit record -->
Figure 1 — The leftovers collected in one place. The protocol work still happens per server; the identity, intersection, and audit decisions stop being duplicated.
Carrying the User Into the Call
There is a standards answer taking shape for the identity half, and it is worth knowing before building your own.
RFC 8693 draws the distinction the problem needs. Under impersonation, a token makes A indistinguishable
from B, so that "insofar as any entity receiving such a token is concerned, they are actually dealing
with B". Under delegation, both parties stay visible: "A is an agent for B". The wire form is the act
claim, which "provides a means within a JWT to express that delegation has occurred and identify the
acting party to whom authority has been delegated", plus may_act, which "makes a statement that one
party is authorized to become the actor and act on behalf of another party". Two identities in one
token, and a claim saying the pairing was allowed in advance.
MCP's own extension repository takes a different route to the same place. The stable
Enterprise-Managed Authorization extension has the user sign in at the enterprise identity provider over
OpenID Connect or SAML, exchanges that assertion for an Identity Assertion JWT Authorization Grant, and
requires that issued access tokens "MUST be audience-restricted to the MCP Server identified by the
resource claim". User identity rides in the grant's sub and scope claims; the client identifies
itself with a client_id that may be a metadata-document URL. The reason it lives in an extension rather
than the core is instructive: extensions are optional and additive by design, so an enterprise can have
this while the base protocol stays simple.
Figure 2 — The enterprise-managed path. The audience is pinned by the grant; the action decision is still the server's own.
Neither mechanism decides anything for you. They are both plumbing for carrying two identities to the place where a decision gets made. That place is the thing you have to build or buy.
Failure Modes
These are the ones we would instrument, because each looks like a different bug than it is.
| Symptom | Likely cause | How to tell |
|---|---|---|
| Client loops 401 forever | resource_metadata not returned on the 401, or the client is not parsing WWW-Authenticate | Curl the endpoint without a token and read the header |
| Consent screen appears four times for one task | Server challenges one scope at a time instead of emitting all required scopes together | Count distinct scope values across consecutive 403s |
| A tool that worked yesterday now 403s | Client re-authorized with only the challenged scope and dropped previously granted ones | Compare the token's scope before and after the last step-up |
| Token accepted by the wrong server | No audience validation; any token from the same issuer passes | Present a token minted for a different resource and see if it works |
| Upstream logs name the MCP server, not the agent | Token passthrough, or the upstream credential is shared across all users | Check whether the upstream call uses a per-user credential |
| Access continues after offboarding | Revocation latency at the identity provider plus long-lived upstream tokens | Time the gap between revocation and the first rejected call |
Table 3 — Six MCP authorization failures and the cheapest signal that identifies each.
The offboarding row is the one that turns into an audit finding. Nothing in MCP revokes anything, and the identity provider's revocation only reaches tokens that provider issued. Upstream credentials held by individual MCP servers are outside that blast radius entirely, which is why the count of places credentials live matters more than any single one of them being well built.
Every MCP server you add is another copy of the same four decisions. Identity resolution, action authorization, credential custody, and audit. The protocol makes the wire format identical across servers. It does nothing to stop those four from diverging.
Where Fabriq Fits
Agentic Fabriq holds credentials per user in a vault and attaches them server-side at the moment of the call, so the agent's reasoning loop never handles the secret. Every call carries two identities, the agent and the acting user, and produces an audit record naming both. An agent's effective tool list is the intersection of the user's scopes and the agent's own, which is the concrete version of the "user-and-agent" check this post keeps pointing at.
What it does not do is reach into a provider on your behalf. Clearing a vault copy of a credential is not the same act as invalidating a grant upstream, so the call to the identity provider's revocation endpoint stays yours to make, and the latency above is still the latency you get.
Frequently Asked Questions
Does MCP support OAuth? Yes, and specifically OAuth 2.1 over HTTP transports. Authorization is OPTIONAL in the spec, but when it is implemented an MCP server is an OAuth 2.1 resource server and the client is an OAuth 2.1 client. STDIO implementations are told not to follow the authorization spec at all and to "retrieve credentials from the environment" instead.
What is the difference between 401 and 403 in MCP? A 401 means authorization is required or the token
is invalid, and the client restarts discovery from the WWW-Authenticate header. A 403 means the token
is fine but lacks scope, carries error="insufficient_scope", and should trigger a step-up rather than a
fresh discovery round.
Do I still need Dynamic Client Registration? Not for new work. It is deprecated as of 2026-07-28,
with Client ID Metadata Documents as the migration path, and it remains in the spec for at least twelve
months under the deprecation policy. Keep supporting it as a fallback for authorization servers that
advertise registration_endpoint but not client_id_metadata_document_supported.
Can an MCP server forward my token to the upstream API? No. "MCP servers MUST NOT accept any tokens that were not explicitly issued for the MCP server", and the token used upstream must be a separate one the server obtained as an OAuth client in its own right.
What is the resource parameter for? It names the server the token is meant for, so the
authorization server can audience-restrict it. RFC 8707 defines invalid_target for the case where "the
requested resource is invalid, missing, unknown, or malformed". MCP clients must send it even when the
authorization server ignores it.
Is a session or state handle enough to authenticate? No. MCP is stateless and has no protocol-level
sessions. Where a server mints a handle for a cart or a workflow, servers "MUST NOT treat possession of a
state handle as authentication", and the handle should be bound server-side to the verified user, keyed as
<user_id>:<handle>.
Which scopes should my MCP server publish? The minimum set needed for basic functionality, with
everything else requested through step-up challenges. Publishing the full catalogue in scopes_supported
is listed as a common mistake, alongside wildcard scopes and bundling unrelated privileges "to preempt
future prompts".
How do I know which user an agent's tool call was for? Not from the protocol. Either carry it in the
token, through RFC 8693 delegation semantics or the Enterprise-Managed Authorization extension's identity
grant, or resolve it at an enforcement point in front of the servers. What you cannot do is infer it from
a client_id, which names the software.
Conclusion
The version of this argument we would have written a year ago was that MCP punts on authorization. That is no longer true, and pretending otherwise misleads people into rebuilding things the spec now handles: audience binding, discovery, issuer validation, and a step-up flow with real error semantics. Implement those properly and a large class of attack goes away.
What remains is not a protocol gap so much as a category difference. Audience binding answers where a token may be spent. Scope answers what class of operation it covers. Neither answers whether this agent, working for this person, should do this thing now, and both answers you might want are per-call judgements rather than properties of a credential.
So the practical advice is unglamorous. Follow the spec exactly, including the parts that feel like
paperwork, because iss validation and audience checks are cheap and the attacks they stop are not. Then
decide where the per-call judgement happens, once, and make every MCP server you connect route through
it. Doing that late is how organisations end up with a dozen servers, a dozen credential stores, and no
single answer to what the agent did.
A protocol tells you the call is well-formed and correctly addressed. It was never going to tell you whether the call should happen. Build the place that decides before the first incident makes the case for you.
Sources
All URLs read 2026-09-29.
- MCP Specification: Authorization, revision 2026-07-28 — the OAuth 2.1 resource-server role, the ten referenced specifications, the
resourceparameter requirement, audience validation, the error table and the step-up flow. - MCP Specification: Client Registration, revision 2026-07-28 — the registration priority order, the Client ID Metadata Document requirements, and the deprecation warning on RFC 7591.
- MCP Security Best Practices, 2026-07-28 — confused deputy conditions and mitigations, the token-passthrough prohibition, state-handle hijacking, mix-up attacks, and the scope-minimization guidance.
- MCP Specification: Versioning — the
YYYY-MM-DDscheme and confirmation that2026-07-28is current. - MCP Specification: Deprecated Features — Dynamic Client Registration deprecated in
2026-07-28, earliest removal on or after 2027-07-28. - MCP Authorization Extensions — the extension registry, with Enterprise-Managed Authorization listed as stable.
- RFC 8707: Resource Indicators for OAuth 2.0 — the
resourceparameter, theinvalid_targeterror, and why scope is the wrong place to encode a destination. - RFC 9728: OAuth 2.0 Protected Resource Metadata — the metadata document,
authorization_servers,scopes_supported, and theWWW-Authenticateresource_metadataparameter. - RFC 6750: OAuth 2.0 Bearer Token Usage — the
scopechallenge attribute and the mapping ofinvalid_tokento 401 andinsufficient_scopeto 403. - RFC 8693: OAuth 2.0 Token Exchange — delegation versus impersonation, and the
actandmay_actclaims. - OAuth Client ID Metadata Document, draft-ietf-oauth-client-id-metadata-document-00 — using an HTTPS URL as a
client_id, and why it beats registering everywhere. - RFC 9700: Best Current Practice for OAuth 2.0 Security — audience-restricting access tokens, sender-constraining them, and the rule against tokens in query parameters.
- user: revokeSignInSessions, Microsoft Graph — what refresh-token revocation covers and the documented few-minute delay.
- OWASP Top 10 for Agentic Applications for 2026 — the peer-reviewed risk list for agentic systems, published 9 December 2025.