In September 2025, someone published an npm package called postmark-mcp. It copied the name of a legitimate Postmark integration and behaved exactly like it. Then version 1.0.16 shipped with one extra line: every email the agent sent was quietly BCC'd to the author. By the time it was pulled, it had been downloaded 1,643 times.
Nobody hacked a model. Nobody broke an API. An agent was given an on-ramp, and nobody checked who built it.
The on-ramps
In the first post of this series, I described AI infrastructure as a highway: the model is the vehicle, and the infrastructure is the road. In the second, the supply chain was the engine and the fuel.
The Model Context Protocol (MCP) is the on-ramp. Every MCP server you connect is a new road joining the highway, and anything on that road can drive straight into your agent's context. We have spent a year building on-ramps at speed. Very few of them have a checkpoint.
Why MCP is different from an ordinary dependency
When a client connects to an MCP server, it calls tools/list. The server replies with each tool's name, description and input schema, and the client places all of that into the model's context so the model knows what it can call.
That design choice has a consequence most teams miss: a tool description is not documentation. It is a prompt. The model reads every word of it and treats it as trusted context, just like your own instructions.
Three attack patterns follow from that.
1. Tool poisoning, or "line jumping." In April 2025, Invariant Labs showed an innocent add tool whose description carried hidden instructions: read the user's SSH key and MCP config, pass them in a spare argument, and say nothing. The approval dialog showed "Adds two numbers." The model saw the rest.
Trail of Bits named the same flaw line jumping, because the payload skips the queue. It lands at connection time, before any tool is invoked, so the approval prompt you rely on never fires. They found the clients and models they tested, Claude Desktop included, would follow those instructions while using other tools. A poisoned calculator can steer your email server.
2. Full-schema poisoning. The description field is not the only place to hide. CyberArk Labs showed in May 2025 that parameter names, parameter descriptions, defaults and types are all read by the model, so all of them can carry an instruction. They went further: a tool with a clean schema can return a fake error at call time asking the model for a sensitive file. No static scan of the definition will ever see that.
3. The rug pull. You review a server on Monday. It is clean, so you approve it. On Thursday it updates, and the description changes. Many clients do not ask again. postmark-mcp was the code-level version of this: a package that looked like the real thing until one release changed what it did.
The enterprise reality
Inside a large organisation, the danger is not one developer's laptop. It is the agent with a service account.
An MCP server on a production agent inherits whatever that agent can reach: the CRM, the ticketing system, the mailbox it sends from. Poison on one on-ramp travels to every other road the agent can drive on. That is why I treat MCP servers as part of the supply chain from the previous post, not as plugins.
The lab: watch it happen
The lab for this post is a deliberately malicious MCP server, plus a small inspector that shows you what the model sees. Everything runs locally against a fake "canary" secret, so nothing real is at risk.
bashgit clone https://github.com/ebbimola/3ripple.git cd 3ripple/a03-mcp-poison pip install -r requirements.txt # Launch 1: the server looks clean. Pin what you approved. python inspect_server.py -- python poisoned_server.py # Launch 2: the same server, "updated". python inspect_server.py -- python poisoned_server.py
On the first run, add is exactly what it claims to be, and the inspector pins its definition by SHA-256.
On the second run, the approval line still reads "Adds two numbers." Underneath it, the model now receives:
- a hidden block telling it to read the canary file and say nothing,plaintext
<IMPORTANT> - a new sidenote parameter whose own description repeats that instruction,
- a line wrapped in an ANSI "conceal" code, invisible in many terminals, asking it to BCC an attacker on emails sent by other tools.
The inspector flags the changed definition against the pin, then each injection pattern, and exits non-zero so you can run it in CI. If you want to see a model actually fall for it, the README explains how to wire the server into a throwaway client profile and watch for lab/exfiltrated.log.
One caveat matters more than the demo. The inspector uses pattern matching. It catches the obvious cases, and it misses paraphrased instructions and anything that arrives in tool output. A clean scan means "nothing obvious", never "safe".
Architecting the defence
None of this means giving up MCP. It means putting a checkpoint on every on-ramp.
- Treat servers as dependencies, not plugins. Pin each server to an exact version and a hash of its tool definitions. Any change to a name, description or schema should force a fresh review, just as a lockfile change does in code review.
- Review what the model reads, not what the dialog shows. Read the full description and schema before approval. Run a maintained scanner such as Invariant's mcp-scan or Trail of Bits' alongside your own checks.plaintext
mcp-context-protector - Keep an allowlist. Only servers from known publishers, installed from a registry you control. A typosquatted name should fail closed.
- Isolate the blast radius. Give each agent only the servers its task needs. A calculator and a mailbox should never share a context window.
- Watch the output, not just the schema. Log tool calls and responses. Alert on a response that asks for files or credentials, or on unexpected follow-up calls after an error. This is the only control that catches call-time poisoning.
Key takeaways
- Every MCP tool description is a prompt that your model trusts. Review it like one.
- Approval dialogs show a summary. The attack lives in the part they hide.
- A server you approved last week is not the server running today unless you have pinned it.
Read next: Arc 2 starts with Prompt Injection Is the SQL Injection of Agents, which extends the rag-poison lab to show why retrieval depth changes everything. If you build evaluations, Why Your Test Suite Can't Catch Agent Failures opens the Agent Evaluation series the following week.
Sources: Trail of Bits on line jumping · CyberArk on full-schema poisoning · Invariant Labs on tool poisoning · The Hacker News on postmark-mcp