MCP servers give AI agents real hands: files, databases, browsers and cloud APIs. That also makes every server a door into your machine, and November 2026 has already shown what happens when those doors are left open.
This guide turns the official MCP security spec and recent incidents into a practical checklist you can apply today, whether you use MCP servers or build them.
- Biggest risks: tool poisoning, prompt injection through tool results, token passthrough, SSRF and malicious local servers.
- For users: install only trusted servers, pin versions, sandbox local servers and never paste secrets into configs.
- For builders: never accept tokens not issued to your server, ask for minimal scopes, and validate every URL.
- For admins: enforce an allowlist with
serverUrlandserverCommandentries, not names. - Recent lessons: 2,539 secrets leaked via Claude Code settings files, and a CVSS 9.8 GPUStack flaw joined CISA’s KEV list.
Why MCP Security Matters Now
The Model Context Protocol lets any AI client call tools exposed by any server. The model reads tool names, descriptions and results as text, and it can act on that text.
So an MCP server is not just a plugin. It is code that runs with your privileges, plus text that can steer your agent. Two recent incidents show both sides of the risk.
Incident 1: secrets in Claude Code settings files
On November 5, 2026, Apiiro reported that Claude Code’s “Yes, and don’t ask again” option saves the full approved command into .claude/settings.local.json. When that command held a password, the password went into the file.
- Scale: 2,539 secrets across 1,994 public GitHub repositories.
- Cause: the file was ignored only in each user’s global git excludes, so it was often committed.
- Lesson for MCP: agent config folders are secret stores. MCP configs with inline tokens have the same problem.
Our full write-up covers detection and cleanup: Claude Code “Don’t Ask Again” secrets leak.
Incident 2: GPUStack added to CISA’s KEV catalog
On November 9, 2026, CISA added a GPUStack improper authentication flaw to its Known Exploited Vulnerabilities catalog. GPUStack is an open-source platform for running AI models on GPU clusters.
- The bug (CVE-2026-102895): before version 2.2.0, requests that appeared to come from localhost were treated as the built-in admin.
- How it was abused: a same-host reverse proxy or a spoofed
X-GPUStack-Real-IPheader made remote requests look local. - Impact: unauthenticated admin access and code execution. It is rated CVSS 9.8, with exploitation seen in the wild.
- Lesson for MCP: “it only listens on localhost” is not authentication. Local MCP servers need real auth too.
The Main MCP Threats at a Glance
| Threat | What it looks like | Main defence |
|---|---|---|
| Tool poisoning | Hidden instructions in a tool description tell the model to read SSH keys or leak data | Trusted servers only, review descriptions, pin versions |
| Rug pull | A server changes a tool description after you approved it | Version pinning and hash checks |
| Cross-server shadowing | One server’s description changes how the agent uses another server’s tool | Fewer servers per session, data-flow limits |
| Prompt injection via results | A web page, issue or email returned by a tool contains commands for the model | Human approval for risky actions, least privilege |
| Token passthrough | A server forwards a client’s token to a downstream API without checking its audience | Reject tokens not issued to the server |
| Confused deputy | An MCP proxy skips user consent because of a cached consent cookie | Per-client consent and exact redirect URI checks |
| SSRF | A malicious server points OAuth discovery at 169.254.169.254 or internal hosts |
HTTPS only, block private IP ranges, egress proxy |
| Local server compromise | A one-click install runs a hidden curl that uploads ~/.ssh |
Show the full command, sandbox, stdio transport |
| Secret leakage | API keys pasted into .mcp.json or settings files get committed |
Environment variables, secret managers, scanners |
Tool Poisoning and Prompt Injection
Invariant Labs named the tool poisoning attack in April 2025. Malicious instructions sit inside a tool’s description, which the model reads in full but the user usually sees only in short form.
In their demo, a harmless-looking add tool told the model to read local config files and SSH keys, pass them as a hidden argument, and not mention it to the user.
Related attacks to know
- Rug pulls: the server swaps a clean description for a poisoned one after you approve it.
- Shadowing: a rogue server rewrites how the agent uses a trusted tool, such as redirecting every email to an attacker, without its own tool ever being called.
- Indirect prompt injection: tool results carry the attack, for example a GitHub issue or web page with hidden instructions.
How to defend against them
- Use known publishers: prefer official vendor servers and well-reviewed open-source ones. Our best MCP servers list is a starting point.
- Read tool descriptions: look for words like “ignore”, “do not tell the user”, file paths or URLs that do not fit the tool.
- Pin versions: use an exact package version or container digest instead of
latest, so descriptions cannot change silently. - Keep sessions small: load only the servers a task needs, to limit shadowing.
- Keep humans in the loop: require approval for writes, deletes, payments and outbound messages.
Warning: Anthropic’s Claude Code docs warn that servers which fetch external content can expose you to prompt injection. A web-browsing or email server plus a shell tool in the same session is a risky mix.
Tokens, Scopes and Token Passthrough
The current MCP spec (version 2026-07-28) is blunt on this point. Servers MUST NOT accept any tokens that were not explicitly issued for the MCP server.
Why token passthrough is dangerous
- Bypasses controls: rate limits and validation tied to the token audience no longer apply.
- Breaks audit trails: downstream logs show the wrong identity, so incidents are hard to trace.
- Spreads damage: one stolen token can unlock every service that accepts it.
Scope minimization
Broad scopes like files:* or admin:* turn one leaked token into a disaster. The spec recommends a progressive, least-privilege model:
- Start small: a minimal initial scope for low-risk read and discovery tools.
- Step up when needed: ask for more scope with a targeted
WWW-Authenticatechallenge the first time a privileged tool runs. - Avoid wildcards: do not publish omnibus scopes such as
*,allorfull-access. - Log elevation: record which scopes were requested and granted.
For users, the same idea applies to personal access tokens. Give a GitHub MCP server a fine-grained token for one repo with read-only access, not a classic token with full repo rights.
Other must-dos for server builders
- Per-client consent: proxy servers must show their own consent page naming the client, scopes and redirect URI.
- Exact redirect URIs: match by exact string, never by pattern or wildcard.
- Single-use state: generate a random OAuth
stateper request and expire it quickly. - Safe URLs: clients must reject
javascript:,data:andfile:authorization URLs and never open URLs through a shell. - Bind state to users: never treat a state handle, such as a cart or workflow ID, as proof of identity.
Building your own server? Our MCP server in Python tutorial walks through the SDK basics.
Sandboxing Local MCP Servers
A local MCP server is a program running on your machine with your privileges. The spec’s own example of a malicious startup command pipes ~/.ssh/id_rsa to a remote URL.
The spec says clients with one-click install MUST show the exact command, untruncated, and require explicit approval. It also recommends running servers sandboxed with minimal default privileges.
Run a stdio server in Docker
Containers are the easiest sandbox for most people. This runs a server with no network, a read-only file system and a read-only project mount:
claude mcp add docs-reader -- docker run -i --rm \
--network none --read-only --cap-drop ALL \
-v "$PWD:/workspace:ro" \
ghcr.io/example/docs-mcp@sha256:REPLACE_WITH_DIGEST
Adjust the flags to what the server truly needs. A server that calls an API needs network access, but it still should not see your home folder.
Sandboxing rules of thumb
- Prefer stdio: the spec recommends
stdiofor local servers, so only the client can talk to them. - Lock down local HTTP servers: require an auth token, or use unix sockets. Never trust “it is only on localhost”.
- Mount only what is needed: one project folder, read-only where possible.
- Block metadata endpoints: deny
169.254.169.254and private ranges from server-side clients. - Patch AI infrastructure: the GPUStack case shows model servers are now active targets. Upgrade GPUStack to 2.2.0 or later.
Allowlists and Managed Policies
Teams should not leave MCP choices to every developer. Claude Code supports managed MCP policies that IT can enforce.
| Goal | Claude Code mechanism |
|---|---|
| Disable MCP entirely | managed-mcp.json with an empty server map |
| Same fixed servers for everyone | managed-mcp.json with your servers |
| Approved catalog only | allowedMcpServers plus allowManagedMcpServersOnly: true |
| Soft allowlist | allowedMcpServers alone |
| Block known-bad servers | deniedMcpServers |
Here is an approved-catalog example for managed settings:
{
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://mcp.example-company.com/*" },
{ "serverCommand": ["npx", "-y", "@modelcontextprotocol/server-filesystem@PINNED_VERSION", "/srv/docs"] }
],
"deniedMcpServers": [
{ "serverUrl": "https://*.untrusted.example/*" }
]
}
Note: Anthropic’s docs say a serverName entry is not a security control, because users choose server names and can call anything “github”. Enforce with serverUrl and serverCommand entries instead. Replace PINNED_VERSION with the exact version you approved.
Claude Code also refuses to let a cloned repo approve its own .mcp.json servers until you accept the workspace trust dialog. Never click through it on unknown repos. For scopes and setup commands, see our Claude Code MCP setup guide.
Keeping Secrets Out of Configs
The Apiiro findings are a warning for every agent config file. Never paste a key directly into .mcp.json, settings files or allow rules.
Use environment variable expansion
Claude Code expands ${VAR} in .mcp.json, so the file can hold the variable name while the value lives in your shell or secret manager:
{
"mcpServers": {
"company-api": {
"type": "http",
"url": "https://mcp.example-company.com/mcp",
"headers": { "Authorization": "Bearer ${COMPANY_MCP_TOKEN}" }
}
}
}
Claude Code also reads its own and cloud provider credential variables as empty in a remote server’s url and headers. That stops a project file from shipping your Anthropic key to a server it names.
More secret hygiene
- Short-lived tokens: use a
headersHelperscript to fetch a fresh token at connect time. - Ignore agent files: add
.claude/settings.local.jsonto every project’s.gitignore. - Scan history: run gitleaks or TruffleHog on full git history, not just the latest commit.
- Rotate on exposure: once a secret has been public, only rotation makes it safe.
Auditing and Monitoring
You cannot respond to what you cannot see. Build an audit trail for MCP from day one.
- Inventory servers: run
claude mcp liston each machine, or deploy a managed list so you already know. - Log tool calls: a Claude Code PreToolUse or PostToolUse hook can log every MCP tool name and argument. See our hooks guide.
- Watch for scope creep: alert when a server asks for new scopes or a tool description changes.
- Export telemetry: Claude Code supports OpenTelemetry export for per-user activity.
- Review regularly: remove servers nobody used in the last month.
Step-by-Step Hardening Checklist
Work through these steps once for each machine or team. Most take only a few minutes.
- List what is installed. Run
claude mcp list(or your client’s equivalent) and write down every server and who publishes it. - Remove what you do not use. Fewer servers means fewer poisoning and shadowing paths.
- Pin versions. Replace
latesttags and unpinnednpxpackages with exact versions or image digests. - Read every tool description. Look for hidden instructions, odd file paths and external URLs.
- Shrink tokens. Swap broad personal tokens for fine-grained, read-only, short-lived ones.
- Move secrets to variables. Replace inline keys with
${VAR}references, then rotate the old keys. - Sandbox local servers. Run them in containers with minimal mounts and no network unless needed.
- Require approval for risky tools. Keep human confirmation for writes, deletes, sends and payments.
- Enforce an allowlist. For teams, deploy
allowedMcpServerswithserverUrlandserverCommandentries. - Turn on logging. Add a tool-call logging hook or OpenTelemetry export, and review it monthly.
Tip: Patch the AI stack around MCP too. Model servers, gateways and vector databases are now targets, as the GPUStack KEV entry showed. Subscribe to CISA KEV alerts for the tools you run.
Troubleshooting: Common Problems and Fixes
A project MCP server stays at “Pending approval”
Claude Code waits for you to trust the workspace before applying approvals committed to the repo. Run claude in the folder and accept the trust dialog, but only for repos you trust.
My server shows “Blocked by enterprise managed policy”
An organization policy refuses it. Ask your admin which allowlist or denylist entry applies, or request that the server be added to the approved catalog.
The server gets 401 after I moved the token to a variable
The variable may be unset, or it may be one Claude Code reads as empty in remote headers. Use a dedicated variable name for the MCP token and check claude mcp list for warnings.
The Docker-sandboxed server cannot reach its API
You used --network none. Allow network access for that server only, and block private ranges and metadata endpoints at the host firewall instead.
A tool description changed after an update
Treat it as a possible rug pull. Diff the old and new descriptions, check the publisher’s changelog, and roll back to the pinned version until you are sure.
The allowlist does not stop a renamed server
You probably used serverName entries. Names are user-chosen, so switch to serverUrl or serverCommand entries.
Frequently Asked Questions
What is MCP tool poisoning?
It is a prompt injection hidden in a tool’s description. The model reads and follows the hidden instructions, while the user usually sees only a short summary of the tool.
Are local MCP servers safe because they run on localhost?
No. They run with your privileges, and localhost-only trust can be bypassed, as the GPUStack CVE-2026-102895 case showed. Use stdio, auth tokens and sandboxing.
What is token passthrough in MCP?
It is when a server forwards a client’s token to another API without checking it was issued for the server. The MCP authorization spec forbids it.
How do I stop prompt injection from tool results?
You cannot fully stop it, so limit the damage. Use least-privilege tokens, keep human approval for risky actions, and avoid mixing web-reading tools with shell or email tools in one session.
Should I put API keys in .mcp.json?
No. Use ${VAR} references and keep values in your environment or a secret manager. Committed keys leak, as the 2,539 secrets in Apiiro’s research showed.
How can a company restrict which MCP servers developers use?
In Claude Code, deploy managed-mcp.json for a fixed set, or allowedMcpServers with allowManagedMcpServersOnly for an approved catalog. Match on URL or command, not name.
Which version of the MCP spec covers these rules?
The latest spec, version 2026-07-28, includes the security best practices page covering confused deputy, token passthrough, SSRF, local server compromise and scope minimization.
Sources
- Model Context Protocol: Security Best Practices
- Invariant Labs: MCP Security Notification, Tool Poisoning Attacks
- Claude Code Docs: Connect to tools via MCP
- Claude Code Docs: Managed MCP configuration
- iTWire: Claude Code approval files exposing company secrets
- The Hacker Wire: CVE-2026-102895 GPUStack
- CISA KEV alert: GPUStack Improper Authentication Vulnerability



