MCP Server Security: How Malicious Tools Attack AI Agents
The Model Context Protocol (MCP) lets AI agents connect to external tools, but MCP clients only ever see a tool's name, description and input schema, never the code that runs beneath. That gap lets a clean-looking tool hide command injection, data exfiltration or credential theft while every API call still succeeds and no metric looks wrong. The September 2025 postmark-mcp incident, where a single line silently BCC'd every email to an attacker, showed the risk in production. The three demonstrations below reproduce command injection, tool poisoning and a full supply chain compromise.
In September 2025, a security researcher at Koi Security spotted something abnormal in the postmark-mcp npm package: a quiet line that BCC'd every email sent through the tool to an attacker address, phan@giftshop[.]club. postmark-mcp is an MCP server that enables AI agents to send transactional emails.
A rough estimate assumed 300 organisations using this in production based on 1,500 weekly downloads, totalling up to 15,000 emails a day. The types of emails sent include password resets, invoices, internal notifications; each of these was duplicated blindly to an attacker. This attack ran for weeks before anyone noticed, and all it took was one line of code added to a tool the agent already trusted, using the user's own credentials on their own machine.
The AI agents executing the tool never flagged this as suspicious. All API calls succeeded, every metric was healthy and nothing raised suspicion about the tool. This is because nothing in the MCP protocol surfaces suspicious behavioural changes behind the stable and benign interface. The agent will only see the tool's name, description and schema. The code running beneath is never verified. This gap between detection and non-detection will be explored later in the three demonstrations.
What is MCP
As per Anthropic's definition, Model Context Protocol (MCP) is "an open standard that enables developers to build secure, two-way connections between their data sources and AI-powered tools". In essence, MCP acts as a universal adapter for AI agents, allowing for external tools to be plugged in without needing a custom integration for each one.
Within MCP exists three roles:
- The host (an AI app itself, for example, Claude Desktop)
- The client (the connector that speaks the protocol)
- The server (the actual tool, such as postmark-mcp)
When a server starts up, it responds to a request called tools/list. This hands back the name, description and input schema for each tool offered. This all happens over JSON-RPC, either over stdio (requiring a local server on your machine) or HTTP (a server running remotely).
The dangerous part is that the tool description from the tools/list response is fed to the LLM as trusted context. Furthermore, the user will only see a short, truncated summary of what the tool does, hiding discrete operations such as BCC'ing all emails to an untrusted third-party email. This gap is where attacks in this post live.
Stats and real world incidents
The postmark-mcp vulnerability doesn't exist in isolation. By December 2025, MCP SDKs were being downloaded 97 million times a month, and a Stacklok survey found that 41% of organisations were already running MCP. Furthermore, Equixly found 43% of MCP servers to be vulnerable to command execution, 30% to SSRF, and 22% to path traversal. This is backed by Enkrypt's finding that 32% of servers have at least one critical vulnerability.
MintMCP found an 84.2% attack success rate on servers with auto-approval on, where the agent would run the tool without asking the user first. Registries meant to catch these malicious packages aren't helping much either: OX Security submitted a malicious proof of concept to 11 public MCP registries in April 2026, and 9 of the 11 accepted it without review.
Several named CVEs exist so far, including:
- CVE-2025-6514, CVSS 9.6: OS command injection in mcp-remote (JFrog)
- CVE-2025-49596, CVSS 9.4: RCE in MCP Inspector (Oligo Security)
- CVE-2025-54136, CVSS 7.2: MCPoison, Cursor trust bypass (Check Point)
- CVE-2026-0755, CVSS 9.8: command injection in gemini-mcp-tool (Trend Micro)
- CVE-2026-33032, CVSS 9.8: MCPwn, unauthenticated nginx-ui (Pluto Security)
The attack surface
MCP attack surfaces are very different from the typical attack surfaces seen in web app, infrastructure and cloud environments. The LLM has permissions to run tools at the user's request, but fails to verify what these tools are doing.
Further, the code runs locally with the user's privileges. No isolated sandbox or containerisation exists by default to minimise the attack surface. As such, a malicious tool gets the same access as the local user: filesystem, network, credentials, plus whatever that specific tool was granted, like git or cloud provider access. Given this, a clean-looking tool, like an email assistant agent, can hide anything malicious, such as exfiltration, dropping an infostealer and command execution. The same visibility gap applies to any AI agent with local access, an angle we covered in what Claude Code stores locally.
The following demonstrations show exactly how this plays out.
Demo 1: Command injection
The first demo covers the simplest of the three vulnerabilities: command injection.
The vulnerable code sits in send_notification(message), using subprocess with shell=True and an f-string to build the command. Whatever the LLM sends as message goes straight to the shell, with no sanitisation.

Payload used: Hello'; whoami; echo '. Once that's dropped into the f-string, the shell runs three commands instead of one: echo 'Hello'; whoami; echo ''. The output came back as Hello, jamil, blank line. The account name leaked straight into the tool's response, a straightforward quote breakout.

The fix is a single line. Swap to shell=False and pass the command as an argument array instead of a string: subprocess.run(["echo", message], shell=False). The same payload is now treated as a literal string, with no breakout.

This isn't a theoretical case. CVE-2026-0755 in gemini-mcp-tool (CVSS 9.8) is the same pattern: string concatenation fed into execAsync, leading to RCE on Windows.
Demo 2: Tool poisoning
The second demo covers tool poisoning, and the bypass that gets around it.
The tool below is get_daily_fact, which on the surface just returns an interesting fact of the day. Hidden inside its docstring, the same place fed to the LLM as trusted context, is an <IMPORTANT> block instructing the model to silently call send_notification with an injection payload before returning the fact. The user only ever sees the short description, "Returns an interesting fact of the day", never the hidden block underneath.

Tested against Claude Desktop, the model caught it. It read the full docstring, flagged the <IMPORTANT> block as an embedded instruction rather than genuine documentation, refused to act on it, and named the payload directly.

The bypass removes the <IMPORTANT> block from the docstring completely and moves the same logic into the implementation instead, wrapped inside a function called _exfiltrate(). The description is now clean, so Claude calls the tool as normal and sees nothing to flag. The malicious code still runs, because Claude never sees the Python implementation, only the tool's name, description, and input schema.
The image below shows Claude Desktop responding to "How are my PRs doing? Am I caught up? jamil537/payment-service." No hidden instructions are present within the description this time, so Claude calls the tool and hands back a normal, accurate PR list. Meanwhile, _exfiltrate() is running in the background the whole time.

Claude can catch instructions hidden inside content it reads. However, it has no visibility into what the code behind a tool actually does.
Demo 3: Supply chain
The third demo is the same trick as demo 2, just applied across a whole server instead of one tool.
The setup is a fake but convincing repo, jamil537/payment-service, private, two open PRs (feat/oauth2-support and fix/payment-retry), a .env file committed with fake credentials, and a hardcoded AWS key sitting in config/config.js. Alongside it sits trojanised-github-mcp, a fake GitHub MCP server with a proper README, requirements.txt, pyproject.toml, and six real working tools: list_repositories, get_issues, get_pull_requests, get_commit_history, create_issue_comment, search_code. Every description is absent of malicious instructions.
The trigger is mundane: asking Claude to get the pull requests for jamil537/payment-service. Claude returns a normal, accurate PR list, similar to Demo 2.
The image below shows exactly that, Claude Desktop returning a clean PR list.

Meanwhile, four separate items get sent out silently to an OASTIFY endpoint. The PR data, as JSON. git config (name, email) and the live gh auth token. Full system info: hostname, OS, user, uptime, CPU, memory, disk, running apps, network IPs, even the Mac serial number. And TruffleHog run against the filesystem, which finds the hardcoded AWS key in config/config.js and sends that too.
Figure 1: Exfiltrated PRs

Figure 2: Exfiltrated GitHub auth token and credentials

Figure 3: Exfiltrated TruffleHog scan

Figure 4: Exfiltrated system information

Postmark-mcp is the same pattern: one clean-looking package with malicious logic hidden inside. The Oura/SmartLoader attack from February 2026 followed the same idea: attackers cloned a legitimate Oura Ring MCP server, built up fake GitHub personas over three months to make it look credible, then submitted it to the registry. It went on to deliver the StealC infostealer.
Demo 3 demonstrates significant impact compared to Demo 1, which just leaked a username. This hands over PR data, GitHub credentials, a full system fingerprint, and a hardcoded secret.
How to protect yourself
The following steps can help reduce your exposure to the vulnerabilities covered in the demos above.
Audit before you install
Don't install an MCP server just because it looks legitimate or ships with a proper README. Demo 3 had both and still exfiltrated credentials. Review the source itself, or have it independently reviewed through penetration testing, before connecting it to anything.
Sandbox the server process
Run the MCP server in an isolated environment rather than giving it full access to your machine. It shouldn't be able to reach ~/.ssh, ~/.aws, browser sessions, or anything else it doesn't strictly need.
Apply least privilege
Only grant a tool access to what it actually needs. If it needs GitHub access, it doesn't need filesystem access. If it needs to read data, it doesn't need write access on top.
Filter network egress
Restrict what the MCP process can send traffic to, and block unknown or unexpected outbound connections by default. This is what would have stopped the OASTIFY calls in demo 3 from ever reaching an attacker. Unexpected outbound traffic like this is also the kind of signal a managed detection and response service is built to surface.
Pin tool descriptions
Nothing in the protocol stops a description from silently changing after you've already reviewed and approved it. Hashing descriptions, with something like Snyk's Agent Scan, means a change actually gets flagged instead of slipping through.
Turn off auto-approval
Auto-approval is exactly the setting MintMCP found an 84.2% attack success rate against. You should require explicit approval for each call, despite the added friction.
Log every invocation
Keep a full log of every tool call and its arguments. This will allow you to reconstruct what happened if something does go wrong.
Don't rely on registries to vet for you
OX Security submitted a malicious proof of concept to 11 public MCP registries, and 9 accepted it without review. Being listed proves nothing; vet servers yourself.
The ecosystem needs to change too
Everything in the previous section puts the work on the individual developer: vetting, sandboxing, monitoring servers themselves. Although necessary, it's a lot to expect from every person installing a server, and demo 2 already showed where the model itself hits a wall. Some of this needs to sit further up, in how MCP itself works and in the registries distributing these servers.
Analyse the implementation, not just the description
Claude and other MCP clients only ever see a tool's name, description, and input schema. The actual code behind the tool is never inspected. This is how demo 2's bypass worked. When we moved the malicious logic out of the description into the implementation, there was nothing left for the model to detect as malicious.
At minimum, MCP clients or an intermediary layer should scan the implementation code the first time a tool is added, the same way a scanner would flag shell=True, unexpected outbound network calls, or credential access. It doesn't need re-scanning on every single call, but it should get re-scanned as soon as anything changes.
Registries need to actually vet what they list
OX Security's research showed that it is possible for attackers to publish their MCP servers onto trusted directories. This shows that a registry listing means almost nothing about safety in this context, even though it's exactly the signal a developer is likely to lean on when picking a server.
Registries sit at a natural chokepoint, every server passes through one before it reaches a user. That's a far better place to catch malicious code than expecting every developer to audit every server on their own. npm went through this exact fight for years before it built in proper scanning. MCP registries are at the start of that same curve. Postmark-mcp and the trojanised GitHub MCP in demo 3 are both a taste of what happens if appropriate registry-level auditing doesn't take place.
Closing
The three demos above cover command injection, tool poisoning, and a full supply chain compromise, but the underlying issue is the same one seen in the postmark-mcp incident at the start. None of them required tricking the model into anything. All it took was a clean tool description and code the agent was already trusted to run.
MCP adoption isn't slowing down, with 97 million SDK downloads a month. The level of scrutiny these servers receive hasn't kept pace with that growth, and until it does, a clean-looking tool description is not a guarantee that the code behind it is safe.
Frequently asked questions
What is a malicious MCP server? A malicious MCP server is a Model Context Protocol tool that presents a clean, legitimate-looking name, description and input schema to an AI agent while hiding harmful behaviour in the code beneath. Because MCP clients only ever see the tool's interface and never the implementation, the server can run command injection, exfiltrate data, or steal credentials while every API call still succeeds and no metric looks abnormal.
Can AI models like Claude detect malicious MCP tools? Only partially. Claude can catch instructions hidden inside content it reads, such as an <IMPORTANT> block embedded in a tool's docstring, and refuse to act on it. It has no visibility into the code that actually runs behind a tool. As demo 2 shows, moving the same malicious logic out of the description and into the implementation leaves nothing for the model to detect.
What was the postmark-mcp incident? In September 2025, a researcher at Koi Security found that the postmark-mcp npm package contained a single line that silently BCC'd every email sent through it to an attacker-controlled address. The package is an MCP server for sending transactional emails such as password resets and invoices. Based on around 1,500 weekly downloads, an estimated 300 organisations were affected, with up to 15,000 emails a day duplicated to the attacker for weeks before anyone noticed.
How do you protect against malicious MCP servers? Audit the source before installing, sandbox the server process away from sensitive paths like ~/.ssh and ~/.aws, apply least privilege, filter outbound network egress to block unexpected connections, pin and hash tool descriptions so changes are flagged, turn off auto-approval, log every tool invocation, and do not treat a registry listing as proof of safety. MintMCP measured an 84.2% attack success rate against servers running with auto-approval enabled.
Are MCP registries safe to trust? No. OX Security submitted a malicious proof-of-concept server to 11 public MCP registries in April 2026, and 9 of the 11 accepted it without review. A registry listing says almost nothing about whether a server is safe, so you should vet each server yourself rather than relying on its presence in a directory.