← All posts

MCP security: tool poisoning and how to defend against it

MCP tool poisoning hides instructions for the agent in a tool's description, and the same trick works through tool results. How it works, what defenders can do, and how Next Moca screens tool results.

The Model Context Protocol (MCP) lets an agent plug into tool servers: a GitHub server, a database server, a file server, a server someone published last week. Every connected server sends the agent two kinds of text that, in most agent hosts, the model reads directly. The first is each tool’s description, which tells the model what the tool does and how to call it. The second is each tool’s result, the data that comes back from a call.

Tool poisoning hides instructions to the agent in the first of those, the description. Invariant Labs named the attack in April 2025 in MCP Security Notification: Tool Poisoning Attacks, describing malicious instructions “invisible to users but visible to AI models.” The same move works through the second, the result, where it is indirect prompt injection arriving over MCP. In both cases the attacker never talks to your agent. They write text your agent will read.

Tool descriptions go straight into the model’s context

When a client connects to an MCP server, it asks for the server’s tool list and gets back each tool’s name, description and input schema. Most agent hosts pass those descriptions to the model so it knows which tools exist and when to use them. The user often sees a tool name and perhaps a one-line summary.

A description can run to many paragraphs, and the model reads every word, so text a user would refuse on sight can sit in the part of the description they never see.

A poisoned add tool can send your SSH key to its server

Invariant’s demonstration used a tool called add. Its visible job was to add two numbers. Its description also carried a block marked <IMPORTANT> that told the model to read ~/.cursor/mcp.json and ~/.ssh/id_rsa before using the tool, pass their contents in a parameter named sidenote, and explain the arithmetic to the user without mentioning any of it.

So the user asks to add two numbers. The model calls add with the two numbers and the two files. The server receives an SSH private key and an MCP configuration file, which can hold credentials for other servers. The user sees the right sum.

The same post documents two variations. In a rug pull, a server ships a harmless description, waits for the user to approve it, and changes it afterwards. In shadowing, one server’s description gives instructions about a different, trusted tool, such as adding an extra recipient to every email the agent sends. The attack then runs through a tool the user already trusts, and the attacker’s own tool never has to be called.

Tool results carry the same risk

In May 2025, Invariant showed the result-borne version through the GitHub MCP server (GitHub MCP Exploited). An attacker opened an issue on a public repository and wrote instructions in the issue text. When a user asked their agent to look at that repository’s issues, the agent read the planted text, pulled data from the user’s private repositories, and published it in a pull request on the public one.

No tool in that chain was malicious. The GitHub server returned an issue exactly as it was written. The poison was in the data, and any MCP server that returns text other people can write can return a planted instruction: issues, emails, tickets, web pages, chat messages, shared documents.

Six habits make poisoned tools harder to exploit

The MCP specification puts much of this on the client. It says clients must treat tool annotations as untrusted unless they come from trusted servers, should prompt for confirmation on sensitive operations, and should validate tool results before passing them to the model. In practice that comes down to six habits.

  • Install servers the way you install dependencies. Keep an allowlist, pin versions, and read the full tool descriptions before approving a server, not only the names.
  • Pin descriptions and watch for changes. Store a hash of each approved description and ask the user again when it changes. That is the direct answer to a rug pull, and Invariant recommends pinning tools with checksums for the same reason.
  • Scope credentials to the task. Give each server a token that reaches only what the task needs. An agent that cannot read your private repositories cannot leak them.
  • Keep servers apart. A description from one server should not be able to steer calls to another. Group servers by how much you trust them, and think twice before one agent mixes a server you wrote with one you found.
  • Confirm before side effects. Show the user a call’s arguments before it sends email, moves money, writes to a repository or reads files outside the project. The sidenote parameter in the example would have shown up here.
  • Screen tool results before the model reads them. Results are where text from strangers enters the agent, and they change on every call, so they need checking on every call.

Next Moca screens tool results before each model call

Needlepath, Next Moca’s runtime security for AI agents, sits between the agent and the model. Before each model call, it takes what the agent’s tools returned, along with the documents and passages the agent gathered, selects what the model reads, and screens those records for instruction-like text. With quarantine on, each flagged span is cut out and replaced in place with a quarantine marker. Each call returns a receipt listing what was sent and what was quarantined, so you can see what the model read at every step.

Pin and review descriptions when you add a server, and re-check them when they change. Results arrive fresh on every call, and that is the traffic Needlepath screens. The GitHub attack above lived there, in the text of an issue the agent fetched.

Screening is a per-request setting. Start in audit mode on your own traffic: your context goes through unchanged, and the receipt reports what would have been removed. The docs cover the switch to quarantine once the flagged requests look right.

We measured the approach on AgentDojo, ETH Zurich’s public benchmark for AI agent security, where every planted instruction arrives inside data a tool returns. The same agent and the same model ran the same attacked cases with and without Needlepath in front of the model. The figures and the method are on the prompt injection benchmark page.

If your agents connect to MCP servers you did not write, talk to one of our founders about running the same measurement on your own tools.

Talk to one of our founders

About fit, and how this applies to your business.

Prefer a calendar? Book a time

We handle your information as described in our Privacy Policy.