You think you installed a set of functions. What the model sees is a block of prose someone else wrote, pasted verbatim into its context — and it finished reading that prose before you called a single tool.
The last three issues treated MCP as capability: doc retrieval moves text in, browser automation hands over a pair of hands. This one flips the view. Every server you connect splices a stranger's prose into the model's system prompt, and usually hands it one of your credentials on the way in.
There is no single star skill this time. The subject is one defense line that runs from before-install to running: vet with MCP Inspector and agent-scan, fence with mcp-context-protector or a container gateway, then close it off with client-side permission rules. Eleven named, installable or readable items below; the selection criteria come in the back half.
# (1) See what it actually hands the model (official debugger: Web UI / CLI / TUI)
npx @modelcontextprotocol/inspector
# (2) Scan every server and skill already on the machine
export SNYK_TOKEN=...
uvx snyk-agent-scan@latest
# (3) Shrink the blast radius on the spot: read-only, plus only the toolsets you need
github-mcp-server --read-only --toolsets repos,issues
The point of (1) is not “it connected.” It is reading, one by one, the description text that tools/list returns. That text lands in the model's context verbatim; the server author wrote it, you did not. Step (2) hides a trap worth calling out: plenty of tutorials still teach uvx mcp-scan@latest. That repo, invariantlabs-ai/mcp-scan, is now snyk/agent-scan, the package is snyk-agent-scan, and it wants a SNYK_TOKEN. Check the repo's current state before copying a command — a lesson this series paid for once already last issue.
The MCP specification (revision 2026-07-28), in server/tools.mdx, gives two sentences that carry most of this issue's theory: “clients MUST consider tool annotations to be untrusted unless they come from trusted servers” and “there SHOULD always be a human in the loop with the ability to deny tool invocations.” Note the reach of the first one — what is untrusted is not only the return value, but also the server's own label saying “this tool of mine is read-only.”
The overview section reduces it to three principles: users must explicitly consent to and understand all data access and operations; hosts must obtain explicit consent before exposing user data to servers; and “Tools represent arbitrary code execution and must be treated with appropriate caution.” Tools equal arbitrary code execution — that is the spec's own wording, not this site's rhetoric.
npx @modelcontextprotocol/inspector; Web UI, CLI and TUI, with --format json on the CLI so it fits in CI. When not to: it is a debugger, not a defense — it shows you things, it blocks nothing.invariantlabs-ai/mcp-scan). Kills: reading every description by hand. How: uvx snyk-agent-scan@latest (the 0.5.x line is uvx snyk-agent-scan@0.5.17), with SNYK_TOKEN set. It scans installed servers and skills and flags prompt injection, tool poisoning, tool shadowing, toxic flows, dangerous words in descriptions, plus hardcoded secrets and suspicious download URLs inside skills. When not to: when you are not willing to ship your local inventory off for cloud analysis — that is the price it pays for coverage.io.github.<user>/<server> requires authenticating as that GitHub user, and a custom domain has to clear a DNS or HTTP challenge. When not to lean on it: it is still preview (per the repo README, in preview since 2025-09-08, with breaking changes and data resets explicitly possible), and ownership proof is not a code audit.direct-poisoning.py, shadowing.py, whatsapp-takeover.py. When not to: do not run them on your working machine; reading the code is the whole point.uv sync, then wrap the downstream server with mcp-context-protector.sh --command "<downstream server command>". It does trust-on-first-use pinning of server instructions, tool descriptions and input schemas, then blocks semantic changes until you approve them; it also strips ANSI control characters used for visual deception, and --guardrail-provider LlamaFirewall adds quarantining of suspicious tool responses. When not to: if you only connect first-party servers and your client already has approval gates, this layer buys little.npx processes running bare in your home directory. How: docker mcp gateway run (with --profile / --port / --transport); one container per server, secrets through docker mcp secret, and per-profile enable/disable of individual tools. When not to: with one or two local servers, the container and image upkeep costs more than it returns.mcp__<server>__<tool> (characters outside A-Z a-z 0-9 _ - become _; the plugin form is mcp__plugin_<plugin>_<server>__<tool>), and permission rules, hook matchers and a subagent's tools list all address tools by that name. How: "deny": ["mcp__*"] turns off every MCP tool in one line; "deny": ["mcp__database__drop_table"] names a single one.mcp__github__get_* works, while an unanchored allow such as "*" or "mcp__*" is skipped with a warning. Worth copying as a design idea: an allow must name a concrete server, so a newly connected server cannot slip into your allowlist. Also: settings files skip any mcp__ rule containing parentheses, so parameter-level matching has to go through --disallowedTools.--strict-mcp-config and disabledMcpjsonServers. Kills: cloning a repo and silently connecting the servers in its .mcp.json. An interactive session does prompt for approval, but -p, the Agent SDK and cloud sessions load them without prompting. --strict-mcp-config restricts the session to configs you passed explicitly; disabledMcpjsonServers rejects a server by name in every session type. When: every CI and unattended run, unconditionally.requiresUserInteraction (self-restraint on the server side). Kills: an irreversible operation waved through by auto mode. How: the server author sets _meta["anthropic/requiresUserInteraction"] to the JSON boolean true on the tool's tools/list entry; that tool then prompts on every call, and acceptEdits, auto and bypassPermissions cannot suppress it, nor can a matching allow rule. Limits: in dontAsk mode it becomes an outright deny, and an allow from --permission-prompt-tool is converted to a deny.--read-only (or GITHUB_READ_ONLY) — the README states write tools are skipped even if explicitly requested; --toolsets (or GITHUB_TOOLSETS) defaults to context,repos,issues,pull_requests,users, and only all opens everything. When not to: drop --read-only on the day you genuinely want it opening PRs, then put it back.If you write servers rather than only installing them, the MUSTs in basic/authorization/security-considerations.mdx are the bar: a server MUST NOT pass through the token it received from the client to a downstream API; it MUST validate token audience and accept only tokens issued specifically to itself; clients MUST include the resource parameter, MUST implement PKCE, and MUST refuse to proceed when PKCE support cannot be verified; and a proxy server using a static client ID MUST obtain user consent for each dynamically registered client — the clause that exists to kill the confused deputy.
/sandbox covered both..mcp.json or .claude/ could grant itself permissions, or add a hook or MCP server that Claude Code runs outside the sandbox. In other words, those files need protecting precisely because MCP servers are not bound by it. Your isolation boundary is a container or a VM, not /sandbox.requiresUserInteraction prompts every single time. Security measures usually die of annoyance rather than of being bypassed — which is why order matters: do the zero-cost layer-three items first, and only add layer two when an external server arrives.webapp-testing skill replaces an entire browser server with Python scripts. A script's permissions are fixed by you, not decided by description text.--read-only and --toolsets, the remaining surface is usually just “the return value contains hostile prose.”claude mcp remove. Tool tax and attack surface both bill by the minute; this is the most underrated move of the three.Do these three tonight (zero cost): (1) npx @modelcontextprotocol/inspector and read the full tool descriptions of everything already installed; (2) put "deny": ["mcp__*"] in settings and re-allow tool by tool as mcp__<server>__<tool>, always anchoring the allow to a concrete server; (3) give github-mcp-server --read-only --toolsets repos,issues, and lift it only on the day you need writes.
Add on the day an external server arrives: uvx snyk-agent-scan@latest (scan before install) · mcp-context-protector.sh --command ... (pinning plus change alerts) · docker mcp gateway run (container isolation and secrets) · MCP Registry for namespace ownership.
Teams and CI: turn on --strict-mcp-config and disabledMcpjsonServers unconditionally (unattended runs never show the approval prompt) · ToolHive for central control and audit · if you write servers, follow the MUSTs in security-considerations.mdx.
Explicitly don't: treat stars as trust · copy uvx mcp-scan@latest from a blog (the repo moved to snyk/agent-scan) · believe a tool's self-declared annotations · assume /sandbox covers MCP.
One-line criterion: assume every character this server returns was written by an attacker — what can it cost you? If the answer is big enough, don't leave it online next to your write access.
tools/list returns enters the context at the start of the session. The mcp-context-protector README lists it as a primary target.mcp__<server>__<tool> is the client-side answer.npx @modelcontextprotocol/inspector, the Web UI / CLI / TUI split and --format json; per the GitHub repo page, 2026-09: ~10.9k stars, MITinvariantlabs-ai/mcp-scan: uvx snyk-agent-scan@latest, SNYK_TOKEN, and the list of MCP and skill risks it covers; per the GitHub repo page, 2026-09: ~3.0k stars, Apache-2.0--guardrail-provider LlamaFirewall; per the GitHub repo page, 2026-09: ~224 stars, Apache-2.0direct-poisoning.py / shadowing.py / whatsapp-takeover.py, the evidence behind this issue's attack chaindocker mcp gateway run, profiles with per-tool enable/disable, and docker mcp secret; per the GitHub repo page, 2026-09: ~1.6k stars, MIT--read-only / GITHUB_READ_ONLY, --toolsets / GITHUB_TOOLSETS and the default toolset list; per the GitHub repo page, 2026-09: ~32.9k stars, MIT.mcp.json approval behavior, --strict-mcp-config, requiresUserInteraction, and organization ask / blocked controls on connector toolsmcp__* deny glob, the rule that allow globs must be anchored to a server segment, skipped mcp__ rules containing parentheses, and --disallowedTools.mcp.json could add an MCP server that runs outside the sandbox”--allowed-origins / --isolated knobs, the client-side counterpart to layer three here--read-only — never from a label.