In this article
September 1, 2026
September 1, 2026

MCP: Scope step-up is not authentication step-up

MCP standardized how an agent asks for more permission. It has no vocabulary yet for asking whether the human behind the token is still there.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

An agent calls delete_production_database. Its access token carries the admin:write scope, because the user granted it three hours ago when they connected the server. The token is valid, the audience is right, the scope is sufficient.

Under the Model Context Protocol as it stands today, that request proceeds. And there is no standardized way for your server to say the one thing you actually want to say, which is: not until a human confirms they are still sitting there.

MCP has a section of its authorization spec titled "Step-Up Authorization Flow." It is worth being precise about what that section covers, because the name is going to mislead a lot of people. It is about scopes. It has nothing to do with re-authenticating a human.

TL;DR

  • MCP's Step-Up Authorization Flow handles insufficient permission: a 403 with WWW-Authenticate: Bearer error="insufficient_scope", after which the client re-authorizes with a wider scope set.
  • Authentication step-up is a different question: not "may this token do that," but "how recently, and how strongly, did a human prove they were here?" That is RFC 9470, and its vocabulary is insufficient_user_authentication, acr_values, max_age, and auth_time.
  • RFC 9470 is not in MCP's standards compliance list, and none of those four terms appear anywhere in the authorization specification.
  • MCP does give you URL mode elicitation, which sends the user out to a browser and requires the server to verify who opens the link. That is the delivery mechanism for a step-up. The freshness vocabulary to go inside it is still missing.
  • Until that changes, build an out-of-band approval bound to the user and the exact operation.

Two questions that look alike

Every authorization decision an MCP server makes is really one of two questions, and they have different answers, different failure modes, and different specs.

May this client do this? A permission question. It is answered by scopes, roles, and policy. It is stable: if a user granted files:write an hour ago, they still have it now. When the answer is no, the fix is to go get more permission.

Is the human still here, and did they prove it recently enough? A presence question. It is answered by the authentication event behind the token. It decays: a user who authenticated eight hours ago has the same permissions and a much weaker claim to being at the keyboard. When the answer is no, the fix is not more permission. More permission would be exactly the wrong response. The fix is a fresh authentication ceremony.

Conflating these is easy because both surface as "the request was refused, go do something and come back." They are not interchangeable. An agent that responds to a presence failure by requesting broader scopes has escalated its own privileges in response to a signal that a human was not watching.

Two columns comparing refusals. Left, the token lacks a scope: the agent calls the tool, the server returns 403 insufficient_scope, the client re-authorizes with a wider scope, and the retry succeeds. This path is standardized by MCP as the Step-Up Authorization Flow, built on RFC 6750. Right, the authentication is stale: the agent calls the tool, but there is no standard error to return and no way to ask for a fresher human, so the tool runs or you invent something. This path is not standardized, and reaching for the scope path here grants more privilege, not less.
Same tool call, two different reasons to refuse. Only one of them has a standard.

What MCP standardized

The scope half, and it standardized it well.

When a client presents a token without sufficient scope at runtime, the server responds per RFC 6750:

  
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"
  

The client then runs the Step-Up Authorization Flow: parse the challenge, compute the union of the scopes it already had and the scopes being demanded, re-authorize with that set, and retry the original request.

The details are thoughtful. Clients MUST treat the challenged scopes as authoritative for the current operation. Scope accumulation is explicitly the client's responsibility, so previously granted permissions are not lost when a server issues per-operation challenges. Servers SHOULD emit every scope needed for an operation in a single challenge, because challenging incrementally forces multiple authorization round trips for one action. Clients SHOULD implement retry limits and track upgrade attempts. Servers MUST account for scope hierarchies.

This is a good specification for the problem it addresses. The problem it addresses is permission.

What MCP did not standardize

Read the standards compliance list in the authorization specification and you will find OAuth 2.1, RFC 6750, RFC 8414, RFC 7591, RFC 8707, RFC 9728, RFC 9207, Client ID Metadata Documents, and OpenID Connect Discovery and Registration.

RFC 9470 is not on that list. And the four terms that make authentication step-up work do not appear anywhere in the specification:

Concept Defined in Present in MCP authorization spec
insufficient_scope RFC 6750 Yes
insufficient_user_authentication RFC 9470 No
acr_values OIDC Core, RFC 9470 No
max_age OIDC Core, RFC 9470 No
auth_time OIDC Core, RFC 9068 No

So there is no interoperable way for an MCP server to say "this token's authentication is nine hours old, get me a fresher one," and no defined way for a client to carry that requirement to the authorization server if a server said it anyway.

To be fair to the spec authors, this is a scoping decision, not an oversight. MCP built its authorization layer on OAuth 2.1 and Bearer token usage, and deliberately implements "a selected subset of their features to ensure security and interoperability while maintaining simplicity." Authentication strength and recency are OIDC concepts, and MCP's authorization layer is careful not to become an authentication protocol. That is defensible. It is also a gap, and the gap is widening as agents take on more destructive work.

Why the names collide

Look at the two protocols side by side and the collision is almost designed to happen.

MCP step-up RFC 9470 step-up
Full name Step-Up Authorization Flow Step Up Authentication Challenge Protocol
Status code 403 Forbidden 401 Unauthorized
Error code insufficient_scope insufficient_user_authentication
Challenge carries scope acr_values, max_age
Client sends next wider scope acr_values, max_age
Question answered May this client do this? Is the human still here?

One word separates the names, and it is the word doing all the work. In casual use, developers say "step-up" for both and "auth" for either, which means a sentence like "our MCP server supports step-up auth" is now ambiguous in a way that matters.

This is already showing up in the wild. Documentation issues on major agent clients ask why step-up re-authorization is not covered. Changelogs describe scope elevation as "step-up authentication." SDK issues report that a client with a valid refresh token never surfaces the challenge to the user at all, which is a scope bug that reads, in the issue title, like an authentication one.

If you write about this, or write docs for it, use the full names. Step-Up Authorization and step-up authentication are worth the extra word every single time.

Why this matters more for agents than for apps

In a normal web app, the gap between these two questions is real but narrow. The user is right there. If your session is eight hours old, the person is probably still at the desk.

Agent sessions break every assumption in that sentence.

  • They are long. An agent connection can persist for days across refresh cycles. The auth_time behind it stays fixed at the original sign-in, which is correct and which means the freshness gap grows without bound.
  • They are unattended. The entire value of an agent is that it works while you do something else. "The user is probably watching" is not just weak here, it is the opposite of the product.
  • They are resumable. A workflow that pauses on a queue and resumes tomorrow morning will execute tool calls against an authentication event from yesterday, and nothing in the protocol notices.
  • Scope is granted once, at consent. The user approves a scope set at connection time, in a consent screen, for a category of actions. They are not approving the specific delete that happens six hours later. Scope is a durable grant; presence is a perishable fact. Agents make the difference between those two much larger than it has ever been.

The piece MCP does give you

There is a genuinely useful primitive in the spec, and it is in the client chapter rather than the authorization one: URL mode elicitation.

URL mode lets a server pause a tool call and direct the user to an external URL for an interaction that must not pass through the MCP client. The client shows the user the full URL, gets explicit consent, and opens it in a way it cannot inspect. The server picks up the interaction out of band, and the client retries the original request afterwards.

That is exactly the shape of an approval flow, and the security requirements attached to it are the ones you want.

The spec is emphatic that the server has to verify who showed up:

"URL mode elicitation returns a URL that an attacker can use to send to a victim. The MCP Server MUST verify the identity of the user who opens the URL before accepting information."

The attack the spec spells out, and why binding an approval to sub is not optional.

It then walks through the attack in full: Alice triggers an elicitation, tricks Bob into opening the link, Bob completes the flow believing it is his own, and the resulting tokens bind to Alice. Account takeover. The recommended mitigation is to compare the authoritative sub claim from the MCP server's authorization server against the subject on the browser session before proceeding.

So the spec hands you a browser, a consent step, and a hard requirement to check identity on arrival. What it does not hand you is any way to express how recently that identity was proven. You are told to verify who the person is. You are not given the vocabulary to say when they last authenticated.

One caveat worth reading carefully before you build: the spec states that URL mode elicitation is not for authorizing the MCP client's access to the MCP server, and that servers MUST NOT rely on it to authorize users for themselves. That constraint is aimed at using elicitation as a substitute for MCP authorization. Using it to collect a confirmation from a user who is already authorized is a different thing, but the boundary is not spelled out, so tread deliberately and keep MCP authorization as the thing that establishes identity.

What to build today

The pattern that works now, and that will keep working when the standards catch up, is an out-of-band approval bound to three things.

  • Bound to the user, taken from the sub claim on the verified token and never from a tool argument. This is the Alice and Bob mitigation, and the spec requires it.
  • Bound to the exact operation, including a hash of the arguments. Without this, an agent can request approval for something harmless and redeem it for something else. The user approved a decision they were never shown.
  • Bound to a fresh authentication, which is the part the protocol cannot help you with yet. Do it in your own web app, where you control the session and can require a real ceremony. If your authorization server supports OIDC max_age, that is where you use it.

Then make the approval single use and short lived, and record the authentication time alongside it, because "who approved this and how did we know it was them" is the question you will be asked later.

We wrote this up end to end in step-up authentication for AI agents, which pauses a destructive MCP tool call, sends the user to a browser to re-verify with AuthKit, and lets the agent resume with an approval identifier bound to the arguments it was granted for.

What would actually close the gap

Two things, and neither is exotic.

  • MCP adopting RFC 9470 alongside RFC 6750. The specification already has the right shape: a WWW-Authenticate challenge, a client that re-authorizes, a retry with limits. Adding insufficient_user_authentication with acr_values and max_age would slot into the existing flow rather than replacing it. The authorization extensions repository is designed for exactly this kind of additive proposal.
  • Authorization servers supporting max_age on the endpoints MCP clients actually use. A challenge is only useful if the client can act on it. Today the enforcement half of step-up generally lives on OIDC authorization endpoints, not the OAuth endpoints MCP clients hit.

Until both exist, an MCP server can pause and ask a human. It cannot yet ask the network how recently that human proved who they were.

The short version

MCP's Step-Up Authorization Flow is about scopes. It answers "may this client do this," it uses 403 and insufficient_scope, and it works well.

Authentication step-up answers a different question: "is the human still here, and how do we know?" It lives in RFC 9470, it uses 401 and insufficient_user_authentication, and MCP has not adopted it.

Agents are the worst possible place for that gap, because long, unattended, resumable sessions are precisely the conditions under which a durable scope grant stops being evidence that anybody is watching. Until the specs converge, bind your approvals to the user, to the operation, and to a fresh authentication you enforce yourself.