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.
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
| Split servers | Gateway allowlist | Policy engine | In-server checks | Per-tool grants | |
|---|---|---|---|---|---|
| Works with servers you did not write | Yes | Yes | Yes | No | Yes |
| One deployment per server | No | Yes | Yes | Yes | Yes |
| Different access per agent and per person | One copy per audience | Per key | Yes | If the server knows the caller | Yes |
| Conditions on arguments | No | No | Yes | Yes | No |
| Where the rules live | Deployment config | Gateway config | Policy files | Server code | Next 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
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.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.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
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.
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
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.
- Keys encrypted at rest, never handed to an agent
- Cancel anytime, nothing is deleted
- How Statio handles credentials