An MCP security checklist for developers wiring agents to real tools
Connecting an agent to a tool through MCP takes about ten minutes. You add a server to a config file, restart, and now the model can read files, run git, query a database or send email. What you also did in those ten mi
Connecting an agent to a tool through MCP takes about ten minutes. You add a server to a config file, restart, and now the model can read files, run git, query a database or send email.
What you also did in those ten minutes is give a process that takes instructions from text it reads the same permissions as you. Every file the agent opens, every web page, every tool result is input that can steer it. And the MCP server on the other side usually runs with your user, your keys and your network.
Most of the MCP problems I've looked at are not exotic. They're the old bugs (path traversal, shell injection, hardcoded keys) in a place where the input now comes from a model that can be talked into anything. Plus a few new ones that only exist because of how agents read tool output.
So this is the list I'd go through before letting an agent use an MCP server on anything real.
The numbers, briefly
These are from public research in 2025, so treat them as a snapshot, not a census:
- 82% of MCP servers studied use file operations prone to path traversal (Endor Labs).
- 53% rely on static, long-lived credentials like hardcoded API keys (Astrix Security, 5,200+ servers analyzed).
- 43% of servers tested had command injection (Equixly).
And the incidents that show the pattern:
- Tool poisoning, April 2025. Invariant Labs showed a malicious MCP server, sitting next to a trusted WhatsApp server in the same agent, quietly exfiltrating chat history through instructions hidden in its tool description.
- MCP Inspector RCE, June 2025. CVE-2025-49596, CVSS 9.4. Unauthenticated code execution on developer machines through Anthropic's official debugging tool.
-
mcp-remote, July 2025. CVE-2025-6514, CVSS 9.6. A malicious
authorization_endpointduring the OAuth flow turned into OS command execution. - Postmark MCP, September 2025. A trojanized package on npm BCC'd every outgoing email to the attacker.
None of these needed a clever model attack. They needed one trust assumption nobody wrote down.
1. The server itself (4 checks)
These are the ones that end in code execution or file access on your machine.
-
Path traversal. Every path argument is resolved and checked against the allowed root, after following symlinks.
../is the obvious case, an absolute path or a symlink inside the allowed folder are the ones that slip through. -
Command injection. No shell strings built from tool arguments. Pass arguments as an array, and watch for arguments that start with
-, becausegit log --grep="$input"stops being safe the moment the input becomes a flag. -
Execute primitives in "safe" tools. Git is the classic one. If the agent can write
.git/configor pass-coptions, it can register a hook, an alias or a filter that runs on the next commit or checkout. A reader pointed out on one of my earlier posts that git worktrees share hooks and config with the main repo, so a separate worktree doesn't help here either. -
Write boundaries. Writes are limited to the folders the task needs. Config files (
.git/hooks,.husky,package.jsonscripts, CI files, editor settings) are persistence, so treat writes there as executions.
2. Tool poisoning and injection (4 checks)
The part that ordinary input validation doesn't catch.
- Injected instructions in tool output. A git log, a README, an issue comment or a web page can contain "ignore previous instructions". Assume it will. The defense is not a better prompt, it's that the agent can't do much damage when it follows one.
- Hidden content. Invisible Unicode, HTML comments and text the user never sees but the model does. Strip or flag it before it reaches the context.
- Exfiltration through normal-looking actions. A poisoned agent doesn't need a "send secrets" tool. A URL with a query string, an image link in markdown, a commit message, a search query. Check which of your tools can carry data out.
- Tool descriptions are input too. A server can put instructions in its own tool descriptions, or name a tool so it shadows one from a trusted server. Read the descriptions of every server you add, and re-read them when the package updates.
3. Credentials and access (3 checks)
- No hardcoded credentials. Use proper OAuth or short-lived tokens where the API supports it.
- Scoped keys. A key per server, with the smallest scope that works, rotated. Not your personal admin token in an env var that every server and every subprocess can read.
- Least privilege for the process. Don't run servers as root. Ideally not even as your main user: a container or a separate user with access only to what the task needs.
4. Several servers in one agent (3 checks)
- Combinations are the attack surface. A browser server plus a git server means web content can steer git operations. Filesystem plus network means read plus send. Look at what each pair can do together, not each one alone.
- Shared context. All servers write into the same model context, so one bad server can influence how the agent uses all the others.
- Write down the combinations. A short table of which servers are connected where, and what each combination could do if one of them went bad.
5. Supply chain (3 checks)
- Pin versions and verify what you install. An MCP server is a dependency with your permissions.
- Watch for typosquats and impersonation. The Postmark case was a package that looked like the real thing.
- Prefer a short list of servers you've reviewed over whatever a search turns up, and review updates like you'd review a PR.
6. Prompt and context (3 checks)
- Context pollution. Large or adversarial tool output can push your instructions out of the window or bury them.
- Keep instructions and data apart. Mark tool output as data in the way you build the context, and don't let it land in the system prompt.
- Size limits on what a tool can return.
7. Network and monitoring (4 checks)
-
SSRF. Any tool that fetches a URL can be pointed at
169.254.169.254, localhost admin panels or your internal network. Block private ranges by default. - Webhooks and callbacks are authenticated and can't be pointed anywhere by the model.
- Rate and spend limits that live outside the agent, so a loop or a poisoned plan can't run up a bill.
- Logs with arguments. Which tool was called, with what, by which agent session. When something goes wrong, "the agent did something" is not a useful incident report.
Before you ship it
- Go through the OWASP Top 10 for Agentic Applications once with your setup in mind.
- If the agent uses more than one server, review them together.
- Test prompt injection against your actual model and tools, not a generic demo. Put an instruction in a file the agent will read and see what it does.
- Decide what you'll do when a server or a key is compromised: how you revoke it, and how you find out what it touched.
The short version
If I could only pick three: no shell strings built from model input, no long-lived broad keys in the server's environment, and treat every tool result as something an attacker wrote. Most of the incidents above fail at least one of those.
What does your MCP setup look like? I'm curious how many people run servers in a container versus straight on their laptop.
Adapted from a guide our team published on zealynx.io.
I wrote this with help from AI for drafting and editing. The opinions and the audit experience are mine.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.