Independent AI intelligence Two editions daily · ET
FervorAI

Analysis · August 24, 2026 · concept

Cloudflare OAuth scope customizationMCP Roadmap 2026RFC 6749mcpmcp-securityagent-identityagent-security

Cloudflare's Optional OAuth Scopes Make Partial Grants Normal, and Most Agents Will Break On Them

Users can now hand your agent a narrower slice of permissions than it asked for. If your code assumes it got everything, it fails on exactly the users you most want to keep.

The OAuth consent screen has been a yes/no button for fourteen years, and the spec behind it never said it had to be. RFC 6749 has always let an authorization server issue a token with a smaller scope than the client requested, and has always required the server to say so in the response when it does. Almost nobody built for that. The button said Approve or Deny, so client code got written as if those were the only two outcomes.

On August 20, Cloudflare added the third one. Optional scopes let a client owner mark specific permissions as deselectable, and the user can uncheck them right there on the consent screen. The token that comes back contains only what they agreed to.

Read the announcement as a feature and it looks small. Read it as a contract change and it is the most consequential thing that shipped this weekend for anyone running an agent against someone else's account.

This is a client-side breaking change wearing a user-facing hat

Here is the position I will defend for the rest of this piece: if you run an MCP server or an agent integration on Cloudflare OAuth, you almost certainly have code today that treats the requested scope set and the granted scope set as the same object. That code has been correct by accident. It stops being correct the first time a security-minded user unchecks a box, and it will fail in a way your error tracking probably will not attribute to consent.

Cloudflare says it directly in the post: "you need to check the granted scope set after exchanging the authorization code, rather than assuming the full requested set of scopes was approved." That sentence is the whole story. Everything else is packaging.

The scale makes it worth acting on. Cloudflare says developers have created thousands of third-party OAuth apps since June, with more than a million authorizations behind them. Every one of those apps written before August 20 was written under the old assumption.

The mechanism, and the part people will misread

Configuration is one array. When you create or update an OAuth client through the API, you add optional_scopes alongside your existing scopes, listing the subset users may decline:

"scopes": [
  "user-details.read",
  "workers-scripts.write",
  "workers-kv-storage.write",
  "zone.read"
],
"optional_scopes": [
  "workers-kv-storage.write",
  "zone.read"
]

Now the piece that trips people up. Required and optional are evaluated against the scopes requested in one specific authorization flow, not against everything configured on the client. With the client above, a flow that asks for all four shows the user two locked scopes and two checkboxes. A later flow asking for only workers-scripts.write and zone.read evaluates exactly those two, and user-details.read is neither shown nor enforced, because it was not requested that time.

That design choice is correct and it is also a foot-gun. Your "required" scopes are only required when you ask for them. Marking user-details.read as required does not guarantee you have it. It guarantees that if you ask for it, the user cannot decline it. Those are different promises, and code that conflates them will hold a token that is missing something it thinks it was promised.

Defaults are conservative in the right direction. Clients that never set optional_scopes see no change at all, and even on clients that do, the consent screen still grants the full requested set unless the user actively unchecks something. Nothing breaks today. It breaks the day your first careful user shows up, which is a much worse debugging experience than a clean rollout would have been.

Why MCP servers are the named example

Cloudflare picked MCP servers as the worked case, and the reasoning is honest about how these things get built. An MCP server requests a broad permission set because in theory an agent could call any of its tools. Most users do not want an agent holding that much. Before optional scopes, the only fix was building your own scope picker upstream of the consent flow, which almost nobody does because it means shipping a second consent UI.

So the broad request survives, and the user's only lever is to walk away.

Two days after Cloudflare's post, the MCP core maintainers published a new roadmap that opens its security section with a near-identical diagnosis. MCP authorization today, David Soria Parra and Den Delimarsky write, "is built around a person approving access in a browser." That works for interactive clients and fails for the callers that now dominate: agents running as cloud workloads with their own identity, acting for a user who is not present, delegating narrower authority to sub-agents. Agent identity is now one of five priority areas, with DPoP, Workload Identity Federation, the ID-JAG grant, and standard token exchange named as the work.

Nobody coordinated those two publications. They arrived two days apart because the same wall is in front of everyone building here. Cloudflare's answer is to shrink what the human grants. MCP's answer is to give the agent its own identity so the human is not the only thing being asked. Both need the same thing from you: stop treating a token as a fixed bundle of powers.

Put this into practice

Start with the cheapest change that has real value. In your token exchange handler, read the scope field on the response and store it next to the token. That is it. One field, one column. You now have ground truth instead of an assumption, and every later improvement builds on it.

Second, write down which of your tools needs which scope. A short map in code, not in a doc. Most teams have never made this explicit, and making it explicit usually surfaces two or three tools requesting permissions they stopped needing months ago.

Third, degrade per tool, not per connection. When a scope is missing, hide or disable the tools that need it and let everything else work. An agent that operates inside whatever subset it was given is one people will authorize; an agent that throws a 500 on startup because one checkbox was unchecked gets uninstalled. Cloudflare makes the same argument, and it is the difference between a partial grant being a feature and being an outage.

Fourth, audit your scopes array against what you actually call, move the rest into optional_scopes, and treat the size of that optional list as a signal you are sending users about how much you respect their decision.

Fifth, test the failing case before a user finds it. Run your own authorization flow, uncheck an optional scope, and drive your agent through its main path with the narrower token. Thirty minutes of work, and it is the only way you will find the call sites that assumed.

Honest limitations

This is one provider's consent screen. Optional scopes are a Cloudflare OAuth feature, not an OAuth extension anyone else has to implement, and if your agent integrates with ten SaaS products this changes exactly one of them. The underlying spec behavior is universal, so the defensive code you write is portable, but the user-facing control is not.

It is also opt-in twice. A developer has to mark scopes optional, and then a user has to bother unchecking one. Consent-screen research has been depressingly consistent for a decade about how often people read these things. My guess is that the share of authorizations exercising a partial grant stays in the low single digits for a long while. That does not make the work optional, because a bug that fires on two percent of users is still a bug, but it does mean nobody should expect a behavioral revolution at the consent screen.

The "task-based" framing in the title of Cloudflare's post is doing some work. Consent is still granted once per authorization flow and the resulting token still lives as long as it lives. Nothing here is per-task, per-call, or revocable at the moment of use, which is the thing agent security actually needs and which the MCP roadmap is aiming at with proof of possession and token exchange. Narrowing a long-lived token is real progress and it is not the same as gating an action.

And a narrower grant does nothing about the scopes the user did approve. If your agent is prompt-injected into misusing workers-scripts.write, the fact that it never received zone.read is cold comfort. Scope reduction limits blast radius. It does not address intent.

One more thing worth naming plainly. Two of the three authors credited on the Cloudflare post, Miller Vargas and José Enrique Rodríguez, are interns. A meaningful piece of the delegated-access model that a million authorizations now run through was shipped by undergraduates over a summer. That is a compliment to them and an observation about how thin the staffing is on the plumbing everyone assumes is solid.

What to do with this

Open your token exchange code today and find the line where you store the token. If the granted scope is not being read and stored beside it, you have a ten-minute fix and a decision to make about how your agent behaves when it has less power than it wanted. Make that decision on purpose, while it is a design question, rather than at 2 a.m. when it is an incident.

The consent screen is turning into a negotiation. Your agent should know how to lose one.

Sources: Cloudflare, "From all-or-nothing to task-based OAuth consent" (August 20, 2026); The New MCP Roadmap (August 22, 2026); RFC 6749, The OAuth 2.0 Authorization Framework, section 3.3; Cloudflare OAuth documentation.