MCP security: threats, controls and an approval checklist
How to approve MCP servers for coding agents: the main threats mapped to controls, plus a checklist you can run before a server goes live.
For Platform and security teams
MCP security is the work of deciding which Model Context Protocol servers a coding agent may connect to, what each one can reach, and how you notice when one changes. The protocol makes authorization optional and leaves most enforcement to the client, so a platform team has to supply the policy. This guide maps the main threats to controls and ends with an approval checklist.
Why does MCP security matter for coding agents?
An MCP server gives an agent new tools: search a ticket system, query a database, drive a browser. The tool's name, description and input schema go straight into the model's context. The agent then calls the tool with arguments it chose, using whatever access the server holds.
That creates two trust problems. The agent trusts text it got from the server, and the server (or whoever controls it) receives data the agent chose to send. A local server adds a third: it is a program running on the developer's machine, with the same privileges as the client.
The MCP specification says there should always be a human able to deny tool invocations. In a coding agent that runs many tool calls per task, that human often clicks through. Your controls need to hold even when the click is automatic.
Which MCP threats map to which controls?
Use this table as the review frame. The threat names come from the MCP security best practices document and the OWASP MCP Security Cheat Sheet.
| Threat | What it looks like | Control |
|---|---|---|
| Tool poisoning | Hidden instructions in a description or schema | Review the full definition, not the summary |
| Rug pull | Definition changes after approval | Hash the whole definition, re-review on change |
| Tool shadowing | One server's text changes how the agent uses another server's tools | Treat each server as a separate trust domain; watch cross-server data flow |
| Local server compromise | Malicious startup command or package | Show the exact command, sandbox the process, allowlist by command |
| Token passthrough | Server forwards tokens not issued to it | Servers validate audience; never pass a client token downstream |
| Confused deputy | Proxy acts with its own broad privileges | Per-client consent, narrow scopes |
| Over-scoped tokens | Broad scopes granted up front | Least-privilege scopes, short-lived tokens |
| Supply chain | Typosquatted or compromised package | Verify names and sources, pin versions, scan dependencies |
| Injection through tool output | A tool returns text that looks like instructions | Treat all output as untrusted data; limit what the agent can do next |
What is a tool poisoning attack?
A tool poisoning attack puts instructions in the part of a tool the model reads and the user does not. Invariant Labs described it in April 2025. Their example was an add tool whose docstring told the model to read ~/.cursor/mcp.json and an SSH private key and pass the contents as a sidenote argument, while explaining the arithmetic so the user saw nothing odd.
Two details from that write-up matter for approvals. The confirmation dialog showed a summarized tool name and hid most of the arguments, so a human approving the call could not see what was leaving. And the instructions did not have to be in the tool being called: a description on one server could change how the agent used a different, trusted server. Invariant called that shadowing.
OWASP's guidance goes further than descriptions. It says to treat the entire schema, including parameter names, types and return schemas, as a place instructions can hide. Review all of it.
What is an MCP rug pull?
A rug pull is a definition change after approval. A server you reviewed on Monday can return a different description on Friday. The specification gives servers a legitimate way to do this: a server that declares the listChanged capability can send notifications/tools/list_changed when its tools change. Package-based servers can also simply ship an update.
The defense is to pin what you reviewed. OWASP recommends hashing the complete tool definition (inputSchema, outputSchema and annotations as well as the description) and requiring review when the hash changes. The annotations matter because the spec tells clients to treat them as untrusted unless they come from trusted servers, and a flipped readOnlyHint can change approval behavior without touching the visible text.
Be clear about what a hash does not cover. OWASP states that it detects metadata changes, not changes to the server code behind an unchanged definition. For that you need version pinning, a review of upgrades, and monitoring of what the server actually does.
How does MCP authorization work?
Authorization in MCP is optional, and it applies to HTTP-based transports. When a server supports it, the specification builds on OAuth 2.1 and related standards: the server acts as a resource server, the client sends the user through an authorization server, and the client includes a resource parameter (RFC 8707) that names the MCP server the token is for.
The rules that matter most to you as the approver:
- Servers must validate that an access token was issued for them as the audience, and must not accept or pass on any other token
- Clients must not send a server any token other than one issued by that server's authorization server
- Access tokens must not travel in the URI query string
- Servers using stdio should not use this flow and should read credentials from the environment instead
Token passthrough, where a server accepts a token meant for something else and forwards it downstream, is forbidden in the specification. It defeats rate limits and other controls that depend on the audience, muddies audit logs, and lets a stolen token be used through the server as a proxy.
For scopes, the specification's guidance is a progressive, least-privilege model: a small initial set of low-risk scopes, with step-up when a privileged operation is first attempted. In review, ask what scopes a server requests on first connect and refuse servers that ask for everything. The same section warns that a stolen broad token widens the blast radius and makes revocation disruptive.
If the server is an OAuth proxy in front of a third-party API, ask how it handles consent. The confused deputy attack in the specification depends on a proxy that reuses a static client ID and skips per-client consent, letting an attacker's redirect receive an authorization code without the user seeing a prompt. The required mitigation is a consent registry per client, checked before the third-party flow starts.
How risky is a local MCP server?
A local server is a binary the client starts. The specification lists the attacks plainly: a malicious startup command in a configuration, a malicious payload inside the server, or an insecure server left on localhost that is reached through DNS rebinding. The consequence is arbitrary code execution with the client's privileges.
Tooling around MCP has had real bugs of this kind. MCP Inspector before version 0.14.1 had no authentication between its client and proxy, which allowed remote code execution (CVE-2025-49596, fixed in 0.14.1). mcp-remote versions 0.0.5 through 0.1.15 were exposed to OS command injection from a crafted authorization_endpoint returned by an untrusted server (CVE-2025-6514, fixed in 0.1.16).
Controls that follow from this:
- Show and approve the exact command, with arguments, before a local server first runs
- Run local servers in a sandbox with a minimal file system, network and credential view. The spec lists containers and application sandboxes as options, and OWASP notes that a bare
chrootis not a process sandbox - Prefer
stdioover a listening port; if a server must listen, bind to localhost and require authorization - Match servers by command or URL in policy, not by the name the user gave them
Can an approved server still be dangerous?
Yes, and this is where review stops being enough. Whatever an approved server returns is text in the model's context. A search tool that returns a web page, a ticket tool that returns a comment, or a database tool that returns a user-supplied field can all carry instructions. OWASP's guidance is to treat every tool response as untrusted data, and to enforce authorization and validate later tool calls in code that does not depend on the model's reading of that text.
That connects MCP to the broader problem covered in prompt injection in coding agents. You cannot filter your way out of it. You can limit what an agent holds when it reads hostile text: narrow scopes, no standing credentials, no unrestricted network, and a human approval on the actions that matter.
How do you approve an MCP server?
Run every server through the same short review, and record the result.
- Identify the server. Who publishes it, where does the code live, and is the package name exactly the one you intended? Typosquatting on package names is a documented attack.
- Read the definitions. Print every tool's full description, parameter names, schemas and annotations. Look for instructions, references to files or other servers, and parameters with no clear purpose.
- Classify the data. What can its tools read and write? Does it touch source, secrets, customer data or production?
- Check authorization. For a remote server, look for OAuth with the
resourceparameter, audience validation, narrow scopes, short-lived tokens. For a local one: which environment credentials will it receive, and can they be scoped down? - Constrain the runtime. For a local server, use a sandbox with only the directories and hosts it needs. For a remote one, note the domains it must reach.
- Pin the definition. Store a hash of the full tool definition and the version you reviewed.
- Decide the approval mode. Which tools may run without a prompt, and which always need one?
- Turn on logging. Capture tool name, arguments, caller and time. OWASP recommends alerting on new tools and unusual call patterns.
- Set a re-review trigger. A changed hash, a new version, or a change of owner sends the server back to step 2.
How do you enforce an MCP allowlist in Claude Code?
Review means nothing if developers can add servers freely. By default, anyone running Claude Code can connect any MCP server. Claude Code lets an administrator restrict that through managed settings. This policy allows only servers that match the listed URLs, and it ignores allowlists in user, project and local settings:
{
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://api.githubcopilot.com/*" },
{ "serverUrl": "https://*.internal.example.com/*" }
]
}
Match by serverUrl or serverCommand. A serverName entry is a label the user chooses and is not a security control. For a fixed set, deploy a managed-mcp.json file, and for none at all, deploy it with an empty server map. The full set of options is in the managed MCP documentation, and our Claude Code security guide covers how it fits with the rest of managed settings.
Where review stops and runtime control starts
A checklist tells you whether to let a server in. It cannot tell you what the agent does with it once it is running. That gap is the reason runtime policy exists: decisions about each tool call, made as it happens, with a record kept. Rechazo, which is in waitlist stage, enforces policy on what an agent does on the machine, including which MCP servers and skills it loads, and keeps an audit trail. It works next to the controls above, not in place of them, and it limits the damage from prompt injection rather than preventing it.
What to do this week
- List every MCP server currently configured across your developers' machines, including project
.mcp.jsonfiles and plugins. - Print the full definitions for each and read them. Remove any you cannot attribute to a publisher.
- Update any copy of MCP Inspector or
mcp-remoteto a fixed version (0.14.1 or later, and 0.1.16 or later respectively). - Choose an enforcement pattern: a fixed set, an allowlist, or a denylist, and deploy it through managed settings or your agent's equivalent.
- Start hashing approved tool definitions and alert when one changes.
- Pull the credentials your MCP servers receive out of shared config files and into scoped, short-lived tokens. The AI coding agent security pillar guide shows where this fits in a wider rollout.
Frequently asked questions
- What is MCP security?
- MCP security is the set of controls that stop Model Context Protocol servers from being used to attack the agent or the machine it runs on. It covers vetting servers before approval, restricting what each server can reach, validating authorization tokens, detecting changed tool definitions, and logging tool calls. The protocol leaves most of this to the client and the people deploying it.
- What is an MCP tool poisoning attack?
- Tool poisoning hides instructions inside an MCP tool's description or schema. The model reads the full text, while the user usually sees a short summary. Invariant Labs showed in April 2025 that a harmless-looking addition tool could tell an agent to read SSH keys and pass them back as a parameter. Reviewing complete definitions before approval is the first defense.
- What is an MCP rug pull?
- A rug pull is when an MCP server changes a tool's definition after you approved it, turning a trusted tool into a malicious one. Servers are allowed to announce tool list changes, and a package-based server can also ship an update. Pin reviewed definitions with a hash that covers the whole definition, and require re-review when it changes.
- Are MCP servers safe to install?
- Not by default. A local MCP server runs code on your machine with the privileges of the client, and a remote one receives whatever the agent sends it. Anthropic reviews connectors for its directory but states that it does not security-audit any MCP server. Treat each server like a third-party dependency with credentials, and approve it through a review.