Agents request every OAuth scope they might need

MCPCloudflareSecurityDeveloper ToolsOAuthAI Coding

August 22, 2026

Dark editorial UI of a consent window packed with checked scope chips and the words every OAuth scope on a graphite block

MCPMCPModel Context Protocol, an open standard that lets an AI tool connect to external data sources and services through a shared connector format instead of a custom integration per service.See also .mcp.json route, Extensions directory, Mcp-Session-Id header oauth scopes show up as a catalog request because the next tool call is unknown. Cloudflare's 20 August 2026 optional-scopes work lets a user take chips off that catalog. The agent still asked for all of them.

That is the whole story. Least privilege for a thing that will keep asking.

Cloudflare's OAuth post from that day names the MCP pattern in one paragraph. An MCP server requests a broad set, because in theory an agent could use all of them. Most users would not want an agent to have that much access. Cloudflare's task-based OAuth post is the receipt.

The checkbox is a UI. The request was already the union.

The next call is unknown so the request is the catalog#

A teal keyring of many dim scope chips around one glowing execute tile, with 381 SCOPES lettered largest
The agent cannot see the next door, so every key is already on the ring.

The MCP authorization spec tells a general-purpose client what to do when the first 401 has no scope challenge. Use every value in scopes_supported. The client, in the spec's words, typically lacks domain-specific knowledge to pick scopes one by one. So it asks for the catalog and hopes the consent screen is the brain.

Cloudflare's own API MCP then publishes a fat catalog. derived-oauth-scopes.json in the cloudflare/mcp repo carries 381 product scopes. REQUIRED_SCOPES is three strings, user:read, offline_access, and account:read. Full access is documented as every OAuth scope available to the MCP server, trusted clients only.

Two tools sit in front of that catalog. search() and execute(). Cloudflare's docs put the native-tool version of this API at about 2,500 endpoints. Code Mode keeps the tool list at two. The permission boundary moved. It now lives in the OAuth token, not in the tool list.

Hang 381 keys on a ring because execute() might open any door. Super normal for a general-purpose agent. Also how you hand a hotel the master key because the guest might want ice on floor twelve.

The 164 write-ish names in that JSON are the ones you actually care about. DNS writes. Access revokes. Workers script writes. An agent that only meant to read a zone still asked for them, because the catalog does not know your ticket.

MCP's security best practices call this a server-side mistake. Publishing every scope in scopes_supported sits next to omnibus values like full-access in the common-mistakes list. Clients still get told to request the published set. The fight is baked in.

Where a stateless MCP server puts its state is a different argument. This one is about what the token can still do after the handshakeinitialize handshakeThe one-time exchange at the start of an MCP connection where a client and server negotiated protocol version and capabilities before any tool calls happened.See also MCP, Mcp-Session-Id header, protocol version field.

A deselect is not a smaller request#

A fat request beam of scope chips hitting a consent glass, then a thinner token beam leaving to the right
The request that arrived is still fat. The token that leaves is thin.

Cloudflare now lets a client owner mark scopes optional. Users can deselect them. The issued token contains only what they granted. RFC 6749 section 3.3 already allowed that. The authorization server MAY fully or partially ignore the requested scope, and MUST echo the granted scope if it differs.

The new work is the checkbox. It is not a smaller authorize URL. Three moving parts, and people keep collapsing them.

  • The client still sends the catalog on the authorize URL. Wrangler does this by default.
  • Optional chips can come off at consent. They start on. Skip Edit Permissions and you granted the fat set.
  • The token that comes back is the granted subset. Check it. The request you sent is not evidence of what you received.

The 20 August changelog is blunt. Optional scopes are selected by default. Skip Edit Permissions, hit Authorize, and you still granted the catalog. Wrangler is the same shape on the CLI side. wrangler login uses all available scopes by default if no flags are provided. --scopes exists. The default is still everything.

Two days later, Wrangler and the Cloudflare API MCP actually opted in. The 22 August changelog says you can choose which optional scopes to grant instead of approving every scope each client requested. Required scopes stay selected. A later tool call that needs a declined scope means reauthorize.

Marking the rest optional is the client-owner half. If you skip optional_scopes, the edit controls do not even appear. Cloudflare's own post uses this payload.

terminal
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/oauth_clients" \
  --request POST \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "client_name": "ACME Corp",
    "redirect_uris": ["https://acme.org/oauth/callback"],
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "client_secret_basic",
    "scopes": [
      "user-details.read",
      "workers-scripts.write",
      "workers-kv-storage.write",
      "zone.read"
    ],
    "optional_scopes": [
      "workers-kv-storage.write",
      "zone.read"
    ]
  }'

The textbook move is incremental authorization. Ask for calendar when the user hits save. Google's granular-permissions guide has been saying that for years, and it is right for a human clicking through a settings panel.

An agent does not have a save button. The next tool is a sample from a distribution. Step-up in the middle of a job is a browser popup while the model is halfway through a planplan modeA Claude Code CLI mode that reads files and runs commands to explore a codebase but holds off making any changes until the user approves a proposed plan.See also Shift+Tab cycle, permission modes. The MCP spec itself warns that challenging one missing scope after another forces extra round-trips for a single operation.

Cursor's forum already has the ugly version. On 18 August 2026, Matthew Williams reported that Cursor's MCP OAuth always sends prompt=consent. Enterprise tenants that disable user consent loop on Approval required, even after admin consent. Same client ID and scopes work in Claude Code, which does not send that parameter. Cursor's reply is that prompt=consent gets added whenever offline_access is in the requested set, and Entra-backed servers often advertise that scope in metadata rather than in mcp.json.

The catalog on the wire is the product. You did not even type it.

One consent screen with a Read only template and a Full access template is the adult version of asking for the catalog. Ugly and finite, and done before the job starts.

The other approval habit on this site is auto mode eating a yes key. Different surface. Same muscle. People will click the wide option if the narrow one hurts.

A miss that crashes trains Full access#

A coral crash into a slammed Full access chip on the left, and a greyed write tool with teal read tools still running on the right
A crash trains Full access. A greyed tool is the actual product.

Cloudflare's post is already the verdict. Check the granted scope set after the code exchange. Do not assume the full request came back. An agent that operates inside whatever subset it received is one users will actually authorize.

Google says the same thing with more years on it. Disable the features you did not get. Do not prompt again until the user clearly intends to use that feature.

The error for a later miss is old. RFC 6750 names insufficient_scope, with a 403 and an optional scope attribute for what is needed. MCP step-up rides that header. Cloudflare's 22 August note rides it too. Reauthorize and grant that scope.

You'll hit this the first time a declined write tool is the one the model picks. A 500 on workers-scripts.write is a product that only works on Full access. A client that fails on a declined scope gives users a reason to skip Edit Permissions next time.

Hide the write tools. Grey them. Say the token cannot deploy. Leave the read tools running.

  • Read the issued scope after the code exchange. It is the contract.
  • Drop or disable tools whose scopes are missing. A greyed deploy is a product. A stack trace is a lecture.
  • On insufficient_scope, re-auth for that operation, not for the whole catalog again.

The cost of holding this is real. You ship a worse first-run agent. You write a scope map from tools to tokens. You keep a re-auth pathPATHThe shell environment variable listing directories searched in order when a command name is typed, so every lookup walks each entry until one matches.See also WSLENV, appendWindowsPath, command lookup that does not dump 381 chips back onto the ring. Change your mind if clients start requesting a task-sized set and treating a miss as a feature. Until then, the authorize URL is still a keyring.

Questions people actually asked

Why do MCP clients request every advertised scope?

The 2025-11-25 MCP authorization spec tells a general-purpose client that lacks a scope challenge to request every value in scopes_supported. The client does not know which tool comes next, so the catalog is the request.

asked on modelcontextprotocol.io
What happens if you decline an optional Cloudflare scope?

The access token contains only the scopes you granted. Required scopes stay on. If a later command or tool call needs a declined scope, reauthorize and grant that one.

asked on developers.cloudflare.com
Does Wrangler still request every scope on login?

Yes, unless you pass --scopes. Wrangler login uses all available scopes by default. Optional scopes on the consent screen let you uncheck after that request arrives, they do not shrink the default request.

asked on developers.cloudflare.com
Should the agent ask for one scope at a time instead?

That is step-up, and the MCP spec itself warns that challenging one missing scope after another forces extra round-trips for a single operation. A catalog request plus a Read only or Full access band is the less annoying shape, as long as a miss does not crash.

asked on modelcontextprotocol.io
What should the client do with a narrower token?

Read the issued scope. Hide or disable tools you did not get. On insufficient_scope, re-auth for that operation. Google already tells web apps to disable features for denied scopes. A 500 on a miss trains Full access.

asked on developers.google.com
Share

Newsletter

New posts land in your inbox when they publish. No spam, unsubscribe anytime.

Prefer RSS