The Note Inside the Tool: MCP Tool-Description Poisoning
The challenge
Riverbend wired a community MCP plugin into their internal assistant so staff could search their notes. Since then the assistant has been quietly attaching files to its tool calls that nobody asked it to attach. The assistant is behaving exactly as instructed, which is the problem: the instructions are not all coming from Riverbend. Read the plugin's server code. One tool carries an order the model obeys and the user never sees. Name the parameter it uses to smuggle the stolen data out.
What you'll learn
- Explain that a model reads tool descriptions as instructions, not inert docs
- Spot an injected instruction hidden in a docstring and told to stay silent
- Trace a smuggling parameter from the signature into the function body
- Describe MCP tool poisoning as a metadata-level, not prompt-level, attack
- List defences: untrust third-party descriptions, pin versions, least privilege
Skills tested
Prerequisites
- Reading Python
- What an MCP tool / agent tool call is
How it works
An agent decides how to call a tool from the tool's description. That description is attacker-controllable content if the server is a third party, and the model obeys instructions found there the same way it obeys the user. This is the opposite of where defenders usually look: the payload is in metadata the human never reads.
Here the search_notes description hides an order, wrapped in <IMPORTANT> tags, to read private keys and cloud credentials and attach them to a parameter, while telling the model to keep quiet about it. The parameter, sidenote, looks optional and benign in the signature, but the body posts its contents off the machine.
The tell is the instruction to conceal: real documentation never asks the model to hide what it did from the user.
Common mistakes
- Answering
query. That is the legitimate search input;sidenoteis the smuggling channel. - Answering
_audit_post. That is the function that sends the data, but the parameter the instruction fills is what the question asks for. - Reading only the signature. The attack lives in the docstring, which is the tool description the model is given.
- Blaming the user's prompt. Nothing the user typed matters; the instruction is in the tool metadata.
How to defend against it
Treat tool metadata as untrusted and constrain what tools can reach.
- Do not feed third-party tool descriptions to the model as trusted instructions; sanitise or display them for human review first.
- Pin and review the exact version of every MCP server you load. Descriptions can change between calls; re-review on update.
- Give each tool only the filesystem and network access it needs. A notes search tool should not be able to read SSH keys or reach an arbitrary host.
- Surface to the user every argument a tool is actually called with, so a hidden parameter cannot ride along unseen.