What is a catch-all scope in MCP, and why is it the default?

A catch-all scope is one OAuth scope that covers an entire MCP server. The agent authorizes once. It gets back a token good for each tool the server exposes. It never asks again. It is the path of least resistance. The MCP authorization spec makes it easy to take. The spec builds MCP servers as OAuth 2.1 resource servers on top of RFC 8414, RFC 7591, and RFC 9728. But it never mandates what a scope should mean. Descope's read of the spec is blunt about the gap: "the specification doesn't define scopes, implementors must decide, a fixed scope like access_mcp may suffice in some cases" (Descope, MCP Authorization Specification).

So teams ship the simplest thing that passes a login test. One scope, one token, one yes/no gate at the front door. It works in a demo. It fails the moment the server exposes more than one tool with more than one blast radius. Which is each real deployment.

What goes wrong with a single catch-all scope

Two failures show up almost at once, and they compound.

One scope means one blast radius for every tool. Say a server bundles a read_customers tool and a read_billing tool under the same scope. An agent that only needed the first can reach the second. Nothing in the token says otherwise. WorkOS puts the fix as plainly as the problem: "scopes have to be per tool, not per server, access to the MCP server is not a useful permission" (WorkOS, MCP Authorization Patterns). An agent that can read one table can read all of them. The token never distinguished between tables in the first place.

Prompt injection inherits the whole scope. An attacker does not need to steal a credential. They only need to get malicious text in front of the model. In a chat message, an email, or a document the agent reads. Stytch's own MCP threat writeup shows how thin the bar is. A crafted message was enough to make an agent leak a user's contact list, no custom server required (Stytch, How to Secure Model-Agent Interactions Against MCP Vulnerabilities). Once the model's next action is compromised, it acts with whatever the token already allows. A catch-all scope hands the attacker the whole server. A narrow, per-tool scope hands them one tool. Only the one the hijacked step was already using.

This is exactly why OWASP's Agentic Top 10 puts goal hijack at the top of its list. An attacker who seizes an agent's decision-making rides each permission that decision-making already carries. The scope is the ceiling on how bad that ride gets.

MCP catch-all scopes best practices for per-tool permissions

A short list, in the order it earns its keep.

  • Don't use catch-all scopes. One scope for an entire MCP server means one compromised tool call is a compromised server. Treat "access to the MCP server" as a permission that does not mean anything. It doesn't.
  • Scope per tool, not per server. Give the read_customers tool a scope. Give write_invoices a different one. Never let a token cover both, unless the agent's job genuinely needs both. Descope frames this the same way: validate scopes "at the tool or function level," checking for something specific like a view scope rather than a blanket grant (Descope).
  • Make the ceiling non-negotiable. A scope is not advice the agent can talk its way past. It has to be enforced the same way no matter who or what is asking. Admin, service account, or passed-down agent. If a role can talk its way to a wider scope than the token carries, the token was never a ceiling.
  • Revoke mid-session. Authorization is not a one-time check at login. An agent's access can and should change while it is still running. The moment a session looks wrong, or a human pulls the plug. A token that stays valid for the life of a long agent run is a token that stays valid after you decide it shouldn't.
  • Log the decision, not just the call. A record that says "agent called read_customers" is thinner proof than one that says "agent called read_customers, scope check passed, here is the token and the tier that allowed it." The second record is what tells you, after an incident, whether the system worked as designed or slipped.

The audit trail an agent can't quietly rewrite

Scoping away MCP catch-all scopes stops most of the damage. It does not answer the next question a security review asks, which is: prove it. Prove the scope held, and that the revocation landed before the next call. Prove no one edited the record afterward. A tidy log an admin (or a hijacked agent with admin-adjacent reach) can edit without detection is not proof of anything. It is a story someone can rewrite.

That is the harder half of MCP authorization. It is the half most teams skip. Scoping is the visible part. Proof is the part nobody asks for until an incident forces the question.

How DataShield Auth implements scope-as-ceiling tokens

DataShield Auth treats the scope not as a preference the agent can argue with, but as a ceiling enforced by construction. Each governed tool call runs the same dispatch pipeline. Token scope ceiling, then declared authority tier, then a mid-session revocation re-check, then metered dispatch, then a sealed audit entry. A passed-down key can never confer more authority than its owner intended, even for a global admin. That is because the ceiling sits in the dispatch path itself. Not in a convention someone has to remember to follow.

Mid-session revocation is not a euphemism. Revoke or suspend an agent, and the change is caught on its very next tool call. The session context gets downgraded mid-flight, rather than at the next login. Each one of those calls, ceiling check, tier check, revocation re-check, gets sealed into a hash-chained audit log with signed checkpoints. Verification distinguishes tampering, insertion, deletion, and truncation as separate findings. So an try to quietly remove a record shows up as a deletion, not silence. That is the difference between a log you hope holds up, and one you can check for yourself at /verify.

The Cedar policy engine handles the admin, platform, config, and token calls on top of that pipeline, with policy_explain. A reviewer can see exactly why a call was allowed or denied. Not just that it was. None of this claims to be a public MCP gateway or an inline content filter sitting in front of each request. It is authorization and proof at the dispatch point. The scope ceiling holds no matter the role. The mid-session check catches a revoked agent before its next call lands. The record of each call is chained, so it can't be quietly edited after the fact. More on how the pieces fit at /architecture.

What a per-tool review actually looks like

Say you inherited an MCP server carrying MCP catch-all scopes. The fix is not a rewrite. It is a pass through the tool list.

First, list each tool the server exposes. Write down, honestly, what each one can reach. Which tables, which write paths, which downstream systems. Second, group tools by blast radius, not by convenience. A read-only lookup and a payment write do not belong in the same scope. That's true even when they live in the same codebase. Third, cut the token down to the narrowest set of scopes the agent's actual job requires. Make the ceiling apply at dispatch, not in a comment or a wiki page. Fourth, wire revocation into the same path, so a suspended agent loses reach on its next call. Not its next login. Fifth, seal each authorization call into a record nobody, including the people running the server, can quietly edit.

Do the first two steps on paper before touching any code. Most of the actual risk reduction happens in that grouping exercise. It is where a team discovers how many unrelated features got bundled under one scope, out of convenience rather than design.

Related reading

Product

DataShield Auth

Scope-ceiling tokens, per-call authorization, mid-session revocation, and a hash-chained audit log.

MCP authorization

Databricks MCP Server Authorization

What Databricks' MCP server authorizes today, and where a scope ceiling closes the rest of the gap.

MCP authorization

Snowflake MCP Server Authorization

Snowflake's native MCP authorization model, and what per-tool scoping adds on top of it.

One scope per server is easy to ship and easy to regret. Scope per tool, cap it at dispatch, and prove it with a chain nobody can quietly edit.

See DataShield Auth

Frequently asked questions

What is a catch-all scope in MCP authorization?

A catch-all scope is a single OAuth scope that covers an entire MCP server rather than individual tools. One authorization grants access to each tool the server exposes, so a token meant for one low-risk tool also works for each higher-risk tool on the same server.

Why do most MCP servers default to one scope instead of per-tool scopes?

The MCP spec builds servers as OAuth 2.1 resource servers but leaves scope design to implementors. Descope's reading of the spec notes that a fixed scope like access_mcp can technically pass, so teams ship the simplest working setup, one scope, one token, and skip the harder work of scoping each tool separately.

How does prompt injection turn a catch-all scope into a bigger breach?

Prompt injection hijacks the agent's next call, not its credentials. Once hijacked, the agent still acts with whatever its token already allows. Stytch has shown a single crafted chat message was enough to leak data with no custom malicious server involved. A catch-all scope means that hijacked step can reach each tool on the server instead of just the one it was using.

How do I revoke an MCP agent's access mid-session instead of waiting for it to log in again?

The revocation check has to run on the agent's next tool call, not only at token issuance. DataShield Auth's dispatch pipeline re-checks revocation on each governed call. Suspending an agent downgrades its session context mid-flight, on the very next call it makes, rather than after its next login.