MCP permissions: deciding which tools an agent can call

An MCP server hands every connected client all of its tools, and this guide covers the five ways teams narrow that down, where each one wins, and what any of them needs to get right.

Hubert Robert, The Portico of a Country Mansion · The Met, CC0

Why MCP access is all or nothing

  • A client connects to a server, not to a tool

    An MCP client config has one entry per server. Connect it and the client calls tools/list and gets every tool the server registers. The GitHub server offers list_issues next to merge_pull_request, and a database server offers a read next to a delete.
  • So access is all or nothing

    If the only switch is the server, an agent you set up to sort issues can also merge to main. The protocol leaves authorization to whoever runs the server, so where the finer decision is made is up to you.

Five ways to control tool access

None of these is wrong; they trade expressiveness against how much you have to run and maintain.

  • Split the server

    Run a read-only copy for agents and a full copy for people, one per environment. It works with any client and needs no new software. The cost is operational: every split is another deployment to patch, another key to rotate and another place for access to drift. Diagrid documents this as its workaround, because its unit of authorization is the server.
  • An allowlist at a gateway

    A proxy in front of your servers exposes only named tools. LiteLLM supports allowed_tools and a blocklist per key, team or organization. Simple to reason about, and a good fit when access follows API keys rather than people.
  • A policy engine

    Rules that evaluate each call: who is calling, which tool, sometimes the arguments. Runlayer documents server policies scoped to exact tool names, deny-only global policies and conditions on tool arguments. The most expressive option, and the most to write and review.
  • Checks inside the server

    If you own the server code, check a scope or role on each tool handler. Precise, and nothing extra to run. It only covers servers you wrote, and each server enforces its own copy of the rules.
  • Per-tool grants per principal

    Grant a person or an agent a server, then deny or grant individual tools on top, checked at a gateway on every call. This is what Statio does. Less expressive than a policy language, and much less to write: the rules live next to the tools they govern.

The approaches side by side

Ways to control MCP tool permissions
Split serversGateway allowlistPolicy engineIn-server checksPer-tool grants
Works with servers you did not writeYesYesYesNoYes
One deployment per serverNoYesYesYesYes
Different access per agent and per personOne copy per audiencePer keyYesIf the server knows the callerYes
Conditions on argumentsNoNoYesYesNo
Where the rules liveDeployment configGateway configPolicy filesServer codeNext to each tool, in a UI

What any approach has to get right

Whichever you pick, check it against these six.

  • Every caller has a name

    An agent borrowing a person’s token makes every permission and every log line ambiguous. Agents need their own identity, with their own grants and their own revocation.
  • A deny beats a grant

    A grant on the server covers its tools. A deny on one tool has to win over that grant, or the server grant quietly reopens what you closed.
  • Unknown tools are not a loophole

    Servers add tools between versions. A tool nobody has decided on should not be callable just because it is new.
  • An unanswerable check refuses

    If the permission service cannot be reached and there is no recent decision to rely on, the safe answer for a tool call is no.
  • Refusals are logged too

    A record of allowed calls tells you what happened. A record of refused calls tells you what something tried to do, which is the part a review asks about.
  • The agent never holds the key

    If the upstream credential sits in the agent’s config, permissions are a suggestion: the agent can call the API directly. Keep the key in the gateway and attach it after the check.

How Statio does it

  1. Grant the server

    Give a member or an agent a server. That covers every tool on it. Nobody, owners and admins included, can call a server’s tools before they are granted.
  2. Deny the tools that should stay closed

    In the Manage Access drawer, deny merge_pull_request or deleteRows for that principal. A tool-level deny overrides the server grant. You can also grant single tools without the whole server.
  3. Every call is checked at the gateway

    A refused call fails with a 403, “You don’t have permission to call tool ’merge_pull_request’”, and shows in the Activity log as failed, with the tool, server and caller. Tools the gateway has not seen before are refused until the server’s inventory is known.

triage-bot calls tools on github

triage-botagentgatewayStatiolast decisions, cachedpermission servicedowngithub mcp serverlist_issuesgrantedissue_readgrantedadd_issue_commentgrantedmerge_pull_requestdenied403triage-botagentgatewayStatiolast decisions, cachedpermissionsservicedowngithub mcp serverlist_issuesgrantedissue_readgrantedadd_issue_commentgrantedmerge_pull_requestdenied403

Still down, and triage-bot calls add_issue_comment for the first time. There is no recent decision to rely on, so the gateway refuses it with a 403.

Rules
Permission service down
A server grant, a tool deny, and what happens when the permission service is down. Tool names are the official GitHub MCP server’s and the 403 text is the app’s; the agent is illustrative.

Agents are principals in their own right, covered in agent identity, and every call and refusal lands in the audit trail.

For a worked example, where an agent reads a poisoned GitHub issue and tries to merge a pull request, see Grant the tool, not the server.

Questions

No. The specification covers how a client authorizes with a server, and leaves which tools a given caller may use to the server or to whatever sits in front of it. That is why the approaches above exist.

It helps, because an agent rarely calls a tool it was never shown. It is not enforcement: a client can still send a tools/call for a tool it guessed or remembered. The call itself has to be checked.

Not as a policy. Statio decides per tool, and scans tool-call arguments and output for threats, blocking with “Request blocked by security policy”. If you need rules on argument values, a policy engine or checks inside the server are the better fit.

The gateway keeps the last decision it made for a short grace period, so an outage never revokes access mid-task. With no recent decision to fall back on, a tool call is refused.

Yes. Per-tool allow and deny is on every plan, including Free. Roles, teams and team grants come with Team and up.

Lock down your first tool in minutes.

Three MCP servers, one of them hosted, and 10,000 calls a month, free and without a card. If you outgrow it the next plan is $19.

Two steps: create your account, then install the desktop app on each machine that runs an AI client.