Skip to main content
Resource / Comparison

MCP tool allowlists vs per-call authorization

An allowlist decides which tools exist for an agent. Per-call authorization decides whether this identity may make this call, now, with these arguments. Where each one fails, and how to run both.

Scroll for definition
Nolan Sullivan headshotBy Nolan Sullivan, Founding Growth Engineer
Published
Definition

MCP tool allowlist vs per-call authorization

An MCP tool allowlist is a fixed list of the tools a Model Context Protocol (MCP) server or gateway exposes, set before any session starts. Per-call authorization is a decision made on every tool call about whether this identity may make this call now, with these arguments, under the policy in force. The allowlist bounds what an agent can see, and per-call authorization decides what it can do.


MCP authorizationDefinitionSpeakeasy

Most MCP deployments start with an allowlist, and they’re right to. Trimming a GitHub server down to the read tools takes minutes, shrinks the context the model has to reason over, and removes the tools nobody should be calling. The trouble starts when the allowlist is asked to answer questions it was never built for: whether a contractor and a staff engineer should get different tools, whether run_query is safe when the query is a DELETE, or whether a person offboarded an hour ago still has a live session. An allowlist answers one question once, for everyone who shares it, and per-call authorization answers every other access question at the moment the call is made. The practical design keeps both and makes the call-time check authoritative.

What does each control decide?

Per-call authorization decides a tool call from five inputs, and an allowlist decides from one of them.

  1. Identity. The person or agent identity behind the session, with its current directory groups and roles.
  2. Tool. Which tool, on which server, and what that tool is known to do, from annotations someone trusted has recorded.
  3. Arguments. What the call asks for: the repository, the recipient, the amount, the SQL statement.
  4. Time and state. Whether the grant still holds right now: the person is still active in the directory, the session hasn’t been revoked, and no block has been added since the session began.
  5. Policy. Which rule allowed or denied the call, recorded so the decision can be explained to an auditor or an incident responder.

A tool allowlist decides the second input, ahead of time, for every identity that uses the list. That’s a real decision with real value. It is also the only one of the five that doesn’t change between the moment the list is written and the moment a tool is called.

Per-call authorization decides from five inputsFive inputs feed a tool-call decision. 1, Identity: the person or agent, with current groups and roles. 2, Tool: which tool on which server. 3, Arguments: what the call asks for. 4, Time and state: whether the grant still holds now. 5, Policy: the rule that decided, recorded. Per-call authorization covers all five, evaluated when the tool is called. A tool allowlist covers only input 2, Tool, decided ahead of time when the list is written, for every identity that uses the list.PER-CALL AUTHORIZATION VS TOOL ALLOWLISTPer-call authorization decides from five inputsTOOL ALLOWLISTCovers onlyinput 2. Decidedahead of time,when the list iswritten, forevery identitythat uses thelist.1 · IDENTITYPerson or agent,with currentgroups androles.2 · TOOLWhich tool onwhich server.3 · ARGUMENTSWhat the callasks for.4 · TIME ANDSTATEWhether thegrant stillholds now.5 · POLICYThe rule thatdecided,recorded.PER-CALL AUTHORIZATIONCovers all five inputs. Evaluated when the tool is called.Per-call authorization decides from five inputsFive inputs feed a tool-call decision. 1, Identity: the person or agent, with current groups and roles. 2, Tool: which tool on which server. 3, Arguments: what the call asks for. 4, Time and state: whether the grant still holds now. 5, Policy: the rule that decided, recorded. Per-call authorization covers all five, evaluated when the tool is called. A tool allowlist covers only input 2, Tool, decided ahead of time when the list is written, for every identity that uses the list.PER-CALL AUTHORIZATION VS TOOL ALLOWLISTPer-call authorizationdecides from five inputsPER-CALL AUTHORIZATIONCovers all five inputs. Evaluated whenthe tool is called.1 · IDENTITYPerson or agent, with current groupsand roles.2 · TOOLWhich tool on which server.3 · ARGUMENTSWhat the call asks for.4 · TIME AND STATEWhether the grant still holds now.5 · POLICYThe rule that decided, recorded.TOOL ALLOWLISTCovers only input 2. Decided ahead oftime, when the list is written, forevery identity that uses the list.

What is an MCP tool allowlist?

An MCP tool allowlist is the set of tools a server or gateway returns when a client sends tools/list, and accepts when a client sends tools/call. Everything else on the upstream server is hidden and refused. Gateways implement it as configuration attached to a server, a portal, or a profile:

  • Docker’s MCP Gateway manages a tool allowlist per profile, in dot notation, with commands like docker mcp profile tools production --enable github.list_repos. Per Docker’s profile documentation, every tool is enabled unless explicitly disabled, so a restrictive profile starts with --disable-all and adds tools back.
  • Cloudflare’s MCP server portals expose all of a server’s tools by default and let an administrator turn individual tools off per portal, or invert the default with default_disabled: true so only listed tools appear. Cloudflare’s docs state that turned-off tools are removed from the portal’s list and can’t be called.
  • Tag-based filtering narrows the list a client asks for. A client connects with a parameter such as ?tags=billing and receives only the tools tagged billing. This is curation chosen by the client, which makes it useful for context and useless as a security boundary, since a client that omits the parameter gets the full list.

For plenty of deployments, an allowlist is enough. It is deterministic, cheap to evaluate, and easy to review, because the whole policy fits on one screen. It also keeps tool definitions out of the model’s context, and Cloudflare’s portal docs give that as the reason to curate: the less external context exposed to the model, the better its responses tend to be. For one team on one server, where every tool is read-only and everyone should see the same set, the allowlist is the whole policy, and a per-call policy engine adds a hop and a rule set that change no outcomes.

What is per-call authorization for MCP?

Per-call authorization is a policy decision on each tools/call request, made after the client names the tool and supplies its arguments and before the request reaches the upstream server. A denied call returns an error to the client, and the server never sees it.

The MCP specification leaves room for this without prescribing it. Its authorization section defines how a server answers a request whose token lacks a scope: 403 Forbidden with error="insufficient_scope" and the scopes the operation needs, so the client can step up. It also says the required scopes “may be determined dynamically based on the specific request arguments and context.” The specification expects authorization to depend on what a call asks for, and it leaves the mapping from tools and arguments to scopes to whoever runs the server. How MCP authorization works covers the OAuth flow itself.

Gateways implement per-call authorization at different depths:

  • Kong’s AI MCP Proxy plugin evaluates allow and deny lists of Consumers and Consumer Groups, with a default ACL for every tool and an optional ACL per tool. Deny entries are checked first, a denied call returns 403 Forbidden before it reaches the upstream server, and every attempt is written to an audit log.
  • IBM’s ContextForge runs a two-layer model: token scoping controls what a caller can see, then an RBAC check controls what it can do, with permissions such as tools.read and tools.execute held by team-scoped roles. Its plugin framework adds tool_pre_invoke hooks, including OPA and Cedar policy plugins, which receive the tool name and arguments and can stop a call.
  • Cloudflare checks identity when a user signs in to a portal, with Access policy selectors such as Emails, Groups, Country, and Device Posture deciding who reaches the portal and which servers they see. Routing portal traffic through Cloudflare Gateway adds data loss prevention scanning on each tool call request and response, which blocks matching calls.

ContextForge’s split between seeing and doing is the clearest statement of the framework on this page: visibility and permission are separate questions. A gateway that answers only the first has authenticated the caller and then applied the same list it would apply to anyone.

Where do MCP tool allowlists fail?

Allowlists fail in five predictable places. Each one comes from the list being written once, before the identity, the arguments, or the current state are known.

The list is the same for everyone who shares it. A list attached to a server or portal gives every user of that server or portal the same tools. To give support engineers the read tools and platform engineers the write tools, you need two lists and a way to route each person to the right one. Cloudflare curates tools per portal, so different tool sets for different groups means separate portals with separate Access policies. Each new role adds another list to keep in sync, and the lists drift apart in ways nobody reviews.

A tool name doesn’t describe a call. Allowing run_query allows a SELECT and a DROP TABLE. Allowing send_email allows a message to a colleague and a message to an external address with a customer export attached. Tools that wrap a general capability, such as a shell, a query engine, or an HTTP client, are the ones agents find most useful and the ones whose risk lives entirely in the arguments. An allowlist can keep the tool or remove it, and it can’t tell one use from another.

Hiding a tool and blocking it are different controls. Removing a tool from the tools/list response keeps a well-behaved agent from planning around it. It does nothing about a client that sends tools/call with the tool’s name anyway, and nothing about a client that reaches the upstream server without going through the gateway. Cloudflare’s documentation is direct about the second case: users blocked by a portal’s Access policies can still connect to the server through its direct URL unless Access is configured as the server’s OAuth provider. The same is true of any gateway, because a control only holds on the path the call takes.

Tools change after the list is written. MCP servers can add tools at any time and announce it with notifications/tools/list_changed. An allow-by-default list, which is the default in both Docker profiles and Cloudflare portals, exposes the new tool with no review. A deny-by-default list hides it until someone updates the list, which is safer and slower. A tool can also keep its name while its description changes, which is the rug pull described in MCP tool poisoning, and a name match will not notice.

The list doesn’t know what time it is. An allowlist has no notion of a person being deprovisioned, a session being revoked, or a block being added after the session started. If access is decided when the session connects and never again, a session opened before an offboarding keeps working until it expires.

Where does per-call authorization fall short?

Per-call authorization closes the five gaps above and introduces five costs of its own: the policy, the annotations it trusts, argument rules, latency, and coverage.

Someone has to write the policy. Directory groups don’t arrive mapped to create_refund. Somebody decides which roles may call which tools on which servers, and the work grows with every tool. Tool classes help: a rule that denies every tool annotated as destructive covers tools that don’t exist yet, as long as the annotations can be trusted.

Rules built on annotations depend on trusted annotations. The MCP specification says clients must consider tool annotations untrusted unless they come from trusted servers. A third-party server that marks a destructive tool readOnlyHint: true walks it past a read-only rule. For servers you don’t control, the annotations the gateway evaluates should be recorded or reviewed by an administrator, not read from the server on every sync.

Argument rules are expensive to get right. Deterministic argument policy, such as a Rego or Cedar rule that permits refunds under a set amount, needs knowledge of each tool’s schema and breaks when the schema changes. Content scanning for secrets and personal data covers more tools with less work and produces false positives. Model-based judges handle intent, such as whether a curl is an exfiltration attempt, and they are probabilistic, so a judge’s deny is a classification with a confidence attached. Writing argument rules for a handful of high-risk tools, and relying on identity and tool rules for the rest, is a reasonable place to stop.

Every call pays for the check. A role-and-tool lookup is cheap. Argument inspection costs more, and a model-based judge adds a model call. A gateway also has to choose what happens when a check can’t finish in time: failing closed stops work during an outage, and failing open lets unchecked calls through. Both are defensible, and the gateway’s behavior should be known and documented before the first outage tests it.

The check only covers calls on its path. A local stdio MCP server configured on a laptop never passes through a gateway, so no gateway policy applies to it. Closing that gap is a discovery problem, covered in shadow MCP and shadow AI.

A denied call is also a worse experience for the agent than a missing tool. An agent that sees a tool plans around it, calls it, receives a 403, and has to recover mid-task. That cost is the strongest argument for running both controls instead of choosing one.

How do tool allowlists and per-call authorization work together?

The two controls answer different questions, and the questions arrive in a fixed order. Each one has a natural place to be answered.

QuestionAnswered byWhy there
Should this tool exist for anyone in the organization?Allowlist on the server or gatewayUnused tools cost context and add surface for no benefit
Should this identity ever see this tool?Per-identity filtering of tools/listThe agent doesn’t plan around a tool it will be denied
May this identity make this call, with these arguments, now?Per-call authorization on tools/callThe only point where identity, arguments, and current state are known together
What happened, and under which rule?The decision logAudit, incident response, and tuning the policy

Three rules keep the layers consistent:

  1. Derive the filtered list and the call check from one policy. If the list shows a user the tools their roles permit, and the call check evaluates the same roles, the list is a preview of the decision. When the two read from different sources, a tool can appear and then be denied, or be hidden and still callable.
  2. Make the call check authoritative. Treat list filtering as a usability layer. Kong’s documentation describes discovery filtered against the default ACL and invocation checked against a tool’s own ACL where one exists, so the invocation check is the one that holds. Kong also notes that a per-tool ACL replaces the default ACL rather than merging with it, so it has to list every allowed subject again.
  3. Put argument and state checks only at call time. Nothing about a call’s arguments is known at tools/list, and the grant’s state can change between listing and calling. These checks belong on tools/call, scoped to the tools that need them.

Under this model, the server-level allowlist becomes the coarse outer boundary an administrator sets once, and the per-identity view of it is computed from roles rather than maintained by hand. Adding a role means adding grants to the policy that already drives both checks.

How do MCP gateways compare on allowlists and per-call authorization?

Each of the four gateways below covers the allowlist layer and at least part of the per-call layer. They differ in where identity enters and whether arguments are part of the decision. Each row reflects the vendor’s current documentation.

GatewayTool exposureIdentity in the decisionCall-time checkArgument inspection
Docker MCP GatewayTool allowlist per profile, all tools on by defaultProfile selected per gateway run; Docker AI Governance is invite-onlyConfigurable interceptors (--interceptor)--block-secrets, on by default, blocks secrets sent to or from tools
Cloudflare MCP server portalsTool toggles per portal, optional deny-by-defaultAccess policies at portal sign-in and per serverTurned-off tools refused; DLP block through Gateway routingDLP profiles on requests and responses
Kong AI MCP ProxyDiscovery filtered by default ACLConsumers and Consumer Groups from Kong auth pluginsPer-tool ACL, deny first, 403 on denialGuardrails on MCP traffic listed as not supported
IBM ContextForgeToken scoping by team and visibilityTeam-scoped RBAC rolesRBAC permission check, tool_pre_invoke pluginsOPA and Cedar plugins receive arguments

Each gateway is the obvious choice for a different reader. Docker gives a developer a curated, containerized tool set on their own machine with almost no configuration. Kong brings MCP under the same Consumer model a team already uses for its APIs. ContextForge has the most expressive per-call policy of the four, through policy-as-code plugins, and its OPA plugin needs an OPA server running beside the gateway. Cloudflare puts identity, device posture, and DLP from an existing Zero Trust deployment in front of MCP, and documents that independent MFA, purpose justification, and temporary authentication are not enforced for servers authorized through a portal.

Does per-call authorization require a self-hosted gateway?

No. Per-call authorization has to run on the path every tool call takes, and that can be a gateway you operate, a gateway a vendor operates, or the MCP server itself. Docker’s MCP Gateway and IBM’s ContextForge are open source and run where you deploy them. That gives you control of network placement, and it hands you the work of patching, scaling, and running a policy engine next to the gateway. A managed gateway moves that work to the vendor and puts tool call traffic through a service the vendor operates.

The requirement behind a self-hosting request is often narrower than self-hosting. If the concern is that a gateway carrying tool arguments and results shouldn’t have a public listener, a managed gateway reachable only over a private network route can meet it. If the concern is that no vendor may operate the component at all, only self-hosting meets it. Separating the two is worth doing before a procurement decision, and what is a private MCP gateway walks through the difference between private, self-hosted, and tunneled deployments, and what each costs.

Need to self-host?

Self-hosting is available for Enterprise customers. Book time with our team to learn more.

A note on Speakeasy

The Speakeasy AI Control Plane runs both layers from one set of role grants, on a gateway Speakeasy manages by default. In the role editor, the mcp:connect scope narrows to All servers, Specific projects, Specific servers, or Specific tools, and tools can be chosen by name or by annotation, so a role can be limited to read-only tools on a billing server without listing them. Remote and tunneled servers can be permissioned by tool name once their tool metadata is recorded, and the annotations the gateway evaluates on those servers are the ones an administrator recorded, not the ones the server reports. Every tool call through the MCP gateway passes a second check against the caller’s grants after the session connects, as the enterprise identity guide describes. With Directory Sync, role membership follows identity provider groups, and deprovisioning a user in the identity provider removes their access.

Each scope has a blocking counterpart, such as mcp:blocked_connect, which withdraws the scope for the matching servers or tools even when another role grants it, and a deny always beats an allow. The same rules appear on each server’s Team Access tab, and where authorization challenge logging is enabled for the organization, denied calls are recorded as challenges that an admin can turn into a precise grant. Shadow AI, generally available under Guardrails for every organization, applies the allowlist idea to clients: each coding harness or assistant is Approved or Blocked at the gateway, and a Blocked one is refused when it authenticates, on every MCP server in the organization.

Three limits are worth knowing before you rely on it. The role check evaluates identity, tool name, and annotation, not arguments. Argument inspection runs through MCP-scoped guardrail policies, which are enabled per organization on request. Each check reads the first 50 KiB of a request or result, and a call goes through when a scan can’t finish in time or the content is larger than that. Tag-based tool filtering is client-chosen curation for context, not an access control. And because the gateway is managed by default, teams that need it off the public internet should know that private access over a Tailscale tailnet requires an Enterprise organization, with each server set to public-only, dual, or private-only. For a team whose security review turns on network exposure, that requirement decides whether the gateway fits, and the private MCP gateway resource covers the trade-offs in full.

The tool-level grants shipped in the April 2026 tool-level RBAC release. If you’re deciding where your allowlist should end and your per-call policy should begin, we’d be interested to hear how you’re drawing that line. Talk to us.

Frequently asked questions

What is the difference between an MCP tool allowlist and per-call authorization?

An MCP tool allowlist is a fixed list of the tools a Model Context Protocol (MCP) server or gateway exposes, set before any session starts. Per-call authorization is a decision made on every tool call about whether this identity may make this call now, with these arguments, under the policy in force. The allowlist bounds what an agent can see, and per-call authorization decides what it can do.

Is an MCP tool allowlist enough on its own?

An MCP tool allowlist is enough when every person who connects should have the same tools, the tools are read-only or low-risk, and nothing about a call's arguments changes whether it should run. A single team using one documentation server fits that description. Once two roles need different tools, a tool's risk depends on its arguments, or access has to end the moment someone is offboarded, the allowlist needs per-call authorization behind it.

What does per-call authorization check on an MCP tool call?

Per-call authorization on an MCP tool call checks the identity behind the session, the tool and the server it lives on, the call's arguments where the policy needs them, the current state of the grant, and the rule that applies. It runs when the client sends tools/call, which is the only point where all of those are known together. The decision and the rule behind it are recorded so the call can be explained later.

Is hiding a tool from tools/list the same as blocking it?

No. Removing a tool from the tools/list response keeps a well-behaved agent from planning around it, but a client can still send tools/call with that tool's name. A tool is blocked only when the gateway or server refuses the tools/call request. Per-call authorization has to be the authoritative check, with list filtering derived from the same policy.

Does the MCP specification define per-tool authorization?

The MCP specification defines how a client obtains an OAuth 2.1 token and how a server rejects a request with 403 Forbidden and an insufficient_scope error so the client can step up. It leaves the mapping from tools to scopes to the server, and it notes that required scopes may be determined dynamically from the request arguments and context. Per-tool and per-argument rules are an implementation choice at the server or gateway.

Can MCP tool annotations be used for authorization?

MCP tool annotations such as destructiveHint and readOnlyHint are useful for grouping tools into classes like read-only or destructive, which lets a rule cover tools added later. The MCP specification says clients must consider annotations untrusted unless they come from trusted servers. A rule based on annotations is only as reliable as whoever recorded them, so for third-party servers an administrator should record or review the annotations the gateway uses.

What is MCP RBAC?

MCP RBAC is role-based access control applied to MCP servers and tools. Roles, often mapped from identity provider groups, carry grants that name servers, tools, or tool classes, and the gateway evaluates those grants on each call. It is a common form of per-call authorization, and it evaluates the identity and the tool, so argument inspection needs a separate policy or guardrail layer.

Should an MCP gateway filter tools/list per user?

Yes, when the same policy drives both the filtered list and the call-time check. An agent that sees only the tools its user may call wastes fewer turns on calls that will be denied, and it loads fewer tool definitions into context. The call-time check stays authoritative, because a list filter alone does not stop a client from calling a tool by name.

Why do name-based tool allowlists drift?

A name-based tool allowlist names the tools that existed when it was written. When a server adds tools, a deny-by-default list hides them until someone updates it, and an allow-by-default list exposes them without review. A tool can also keep its name while its description or behavior changes, which a name match will not notice. Allowlists built on tool classes, such as annotations, cover new tools automatically but depend on trusted annotations.

Does per-call authorization add latency to MCP tool calls?

Per-call authorization adds a policy evaluation to every tool call, and an extra network hop when the check runs in a gateway. A role-and-tool check is a lookup. Argument inspection costs more, and content classifiers or model-based judges cost the most, which is why policies that inspect arguments are usually scoped to the servers and tools that need them.

Can per-call authorization inspect tool call arguments?

Per-call authorization can inspect tool call arguments when the policy engine receives them. Policy-as-code hooks, such as an OPA or Cedar plugin on a pre-invoke hook, can allow or deny based on argument values. Data loss prevention and guardrail layers scan arguments and results for sensitive content and block matches. Deterministic argument rules need knowledge of each tool's schema, so a practical starting point is identity and tool rules everywhere, with argument rules for a few high-risk tools.

Does per-call authorization require a self-hosted MCP gateway?

No. Per-call authorization has to run on the path every tool call takes, and that path can be a self-hosted gateway, a managed gateway, or the MCP server itself. Self-hosting controls network placement and adds the work of running the gateway and its policy engine. A managed gateway with a private network route can meet a no-public-listener requirement without self-hosting, which the private MCP gateway resource covers in detail.

How does Speakeasy handle MCP tool allowlists and per-call authorization?

In the Speakeasy AI Control Plane, the role editor narrows the mcp:connect scope to all servers, specific projects, specific servers, or specific tools, and every tool call through the MCP gateway is checked against the caller's role grants by tool name and annotation. Blocking scopes such as mcp:blocked_connect withdraw access even when another role grants it. Argument inspection runs through MCP-scoped guardrail policies, which are enabled per organization, and the gateway is managed by default, with private access over a Tailscale tailnet and self-hosting available to Enterprise organizations.

AI everywhere.

Control here.