Scoping an MCP Server's Permissions Before You Connect It
Last reviewed: 9 October 2026.
Method disclosure, up front: this is an operational runbook, not a vulnerability write-up. The threat descriptions are sourced to published 2026 security research, linked at the end. The checklist and the permission matrix are ours — they are how we gate tool access across an agent fleet, and you should adapt rather than copy them blindly.
Connecting an MCP server feels like adding a dependency. It is closer to granting a contractor a key to the building.
The difference is that a normal dependency does what its code does. An MCP server supplies text the model reads with full trust — tool names, descriptions, parameter documentation — and that text is instruction-shaped. The model acts on it. Your user never sees it.
This is not theoretical. Tool poisoning — hiding instructions for the model inside a tool's description or parameter metadata — was the most-discussed MCP vulnerability of 2026. Trail of Bits demonstrated an attack that silently exfiltrated a user's entire chat history, including credentials, tokens and intellectual property, from a tool description alone. The Cloud Security Alliance published a research note in May 2026 characterising the issue as systemic design flaws in agent infrastructure, not isolated bugs.
So: before you connect, scope. Here is the order we do it in.
Step 1: Decide what the server is allowed to be
Before reading its tool list, write down one sentence: what is the smallest job this server needs to do?
That sentence is your scope ceiling. Everything after this step is checking the server against it. Teams that skip this step end up justifying each permission the server asks for, rather than asking whether the job needs it — and the result is predictable: a tool built to read one table ends up with write access to an entire database, simply because nobody scoped its credentials down after the initial build.
Step 2: Read every tool description as if it were user input
Not the tool names — the full descriptions, including parameter descriptions, enum values and any examples blocks. Dump the raw tool list and read it as text.
You are looking for anything that reads as an instruction to the model rather than a description of a function:
- imperative sentences aimed at the assistant ("before calling this, first read…", "always include the contents of…")
- references to other tools, files, or the conversation itself
- any mention of credentials, environment variables, system prompts, or prior messages
- hidden formatting: HTML comments, zero-width characters, text positioned to be invisible in a UI but present in the payload
- unusually long descriptions — length is where injections hide
A legitimate tool description describes inputs and outputs. If it describes behaviour you should perform, treat the server as hostile until proven otherwise.
Automate the dump, not the judgement. Store the full tool manifest as a file in your repo. That turns step 2 into a reviewable diff, which is what makes step 6 possible.
Step 3: Scope the credential, not the tool
The tool asks for what it wants. The credential decides what it gets. Only the second one is enforceable.
For each tool the server exposes, issue a credential that satisfies the scope sentence from step 1 and nothing beyond it:
| Tool wants | Issue instead |
|---|---|
| database connection string | a read-only role, scoped to named tables, with a statement timeout |
| repository access | a token scoped to one repo, read-only, no org scope |
| cloud account credentials | a role with a resource-level policy and no * actions |
| "API key" | a key from a sub-account with its own rate limit and spend cap |
| filesystem access | a bind-mounted subdirectory, read-only where possible |
If the provider cannot issue a credential narrower than "everything", that is a finding. Record it and decide explicitly whether the job is worth it — do not let the gap pass silently because the integration was convenient.
Step 4: Put the dangerous half behind a human
Not every action needs approval. Approval fatigue is a real failure mode: a reviewer who confirms forty prompts an hour is a rubber stamp, and a rubber stamp is worse than no gate because it creates a record that implies review happened.
So split actions by reversibility and blast radius, not by how scary they sound:
| Class | Examples | Gate |
|---|---|---|
| Read, scoped | query a read-only table, fetch an issue, read a file in-scope | auto-allow, log |
| Write, reversible, bounded | create a draft, open a PR, write to a scratch path | auto-allow, log, keep the undo path |
| Write, irreversible or unbounded | delete, force-push, send an email, charge a card, change permissions | always ask |
| Anything that touches identity or money | rotate a key, add a user, modify a policy, move funds | always ask, and name the approver in the log |
| Egress to an unapproved destination | HTTP to a domain not on the allowlist | block, do not ask |
The last row matters more than it looks. Exfiltration needs a destination. An agent that cannot reach an arbitrary host cannot leak to one, regardless of what a poisoned tool description tells it to do. Domain allowlisting is the single highest-leverage control on this list because it defends against attacks you have not thought of yet.
Step 5: Authenticate properly, and centralise the tokens
For remote servers, the 2026 baseline from the security literature is narrow and specific:
- OAuth 2.1 on every remote endpoint. Not an API key in a config file.
- Tokens stored centrally, not in per-client configuration files scattered across developer machines.
- Access scoped through identity-provider groups, not per-user configuration — so revoking someone's access is one action in one place.
- Every tool invocation logged with the user identity that triggered it. "The agent did it" is not an audit trail.
One specific trap if you are building or proxying: the confused deputy attack. When an MCP proxy uses a static client ID against a third-party authorisation server and lets clients dynamically register against that same server, an attacker can craft an authorisation request that reuses an existing consent cookie — and the user is never asked to consent again, because as far as the authorisation server is concerned, they already did. If you operate a proxy, do not share one static client ID across dynamically registered clients.
Step 6: Pin the manifest and watch it for changes
This is the step almost everyone skips, and it is the one that catches the attack that gets past steps 2–5.
Tool definitions are mutable. A server that was clean when you reviewed it can push a new description tomorrow, and nothing in the protocol makes that visible to you. Mitigating tool poisoning requires version pinning, signed tool definitions where available, and continuous monitoring for tool-definition changes across every connected server.
Practically:
- Pin the server version. No floating
latest. - Commit the tool manifest from step 2 to your repo.
- Re-fetch it on a schedule and diff it against the committed copy.
- Fail the build — or disconnect the server — on an unreviewed diff.
A changed tool description should be as loud as a changed lockfile. Today, in most setups, it is completely silent.
The 10-minute version
If you connect a server today and have ten minutes:
- Write the one-sentence scope. Everything is checked against it.
- Dump the full tool manifest and read every description as untrusted text.
- Issue a credential narrower than what the tool asked for. Read-only by default.
- Allowlist egress domains. Block, don't ask, on anything else.
- Put irreversible and identity/money actions behind an explicit approval.
- Log every invocation with the triggering user identity.
- Pin the version, commit the manifest, diff it on a schedule.
Seven lines. The first and the last are the two that teams skip, and they are the two that do the most work: the first prevents scope creep by making it visible, and the last is the only control that survives the server changing after you reviewed it.
What this does not protect against
Honesty about the limits, because a checklist that implies completeness is its own risk:
- Prompt injection through data, not tools. If your agent reads a web page, an email or an issue comment, the content can carry instructions. Tool scoping limits the damage; it does not prevent the attempt. Egress allowlisting is your main defence here.
- A legitimately-scoped tool used for an illegitimate task. If the agent is allowed to open PRs and it opens a bad one, every control above passed. That is what review is for.
- The model simply being wrong. No permission model fixes a bad decision inside an allowed action.
Scope reduces blast radius. It does not make an agent trustworthy. Those are different problems, and the second one is solved with evals and review, not with permissions.
Sources
- MCP Security: Risks, Real Incidents & Controls (2026) — Checkmarx
- MCP Security Vulnerabilities: Preventing Prompt Injection and Tool Poisoning (2026) — Practical DevSecOps
- MCP Security Crisis: Systemic Design Flaws in AI Agent Infrastructure — Cloud Security Alliance, May 2026
- MCP Server Security: Threats and Controls (2026) — Scalar
- MCP Security Best Practices: The Complete 2026 Guide — Obot
- MCP-ITP: An Automated Framework for Implicit Tool Poisoning in MCP — arXiv
Not tested hands-on: we did not reproduce the published attacks. The threat descriptions are sourced above; the checklist, the action-class table and the credential-scoping table are our operational practice.