Every MCP security scanner on the market does the same thing: it looks at each server in isolation. Does this server have prompt injection in its tool descriptions? Does that one have a known CVE? Good. Ship it.
Nobody asks what happens when those servers work together.
MCPhound does. It is the first scanner that models your MCP configuration as an attack graph and finds multi-hop chains across server combinations. The filesystem server and the fetch server are both fine on their own. Together, they are an SSH key exfiltration pipeline -- and no individual server scanner will ever flag it.
The blind spot is compositional
There are now over 8,600 MCP servers listed on PulseMCP, with 97 million monthly SDK downloads across the ecosystem. The average developer runs 4 MCP servers per client. MCP has been adopted by OpenAI, Google DeepMind, Microsoft, and AWS, and was donated to the Linux Foundation in December 2025. This is not a niche protocol anymore.
The security picture is rough. Astrix analyzed 5,000+ servers and found that 88% require credentials, but 53% use static API keys. Only 8.5% use OAuth. Bitsight found roughly 1,000 MCP servers exposed on the public internet with zero authentication. The tools exist to catch some of this. Snyk acquired Invariant Labs. mcpscan.ai does static analysis. Proximity scans individual servers for risks.
But none of them look at your config. None of them ask: given the specific combination of servers you have enabled, what attack paths exist between them?
This is the gap MCPhound fills.
Three attacks your scanner misses
Let me show you what compositional attacks look like. These are not theoretical. They use capabilities from real, official, widely-deployed MCP servers.
1. SSH Key Exfiltration: Filesystem + Fetch
Here is a config that millions of developers have some variant of:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/"]
},
"fetch": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-fetch"]
}
}
}
The filesystem server gives Claude read/write access to your files. The path argument is / -- your entire filesystem. The fetch server gives Claude unrestricted outbound HTTP access. Both are official Anthropic servers. Both are doing exactly what they are supposed to do.
The attack: a hidden instruction embedded in any webpage Claude visits (or any file it reads) tells it to read ~/.ssh/id_rsa using the filesystem server, then send the contents to attacker.com via a GET request using the fetch server. The data routes through the host application -- the LLM itself acts as the relay. No server-to-server communication needed. No exploit required. Just two servers with capabilities that compose into exfiltration.
This is what Simon Willison calls the "lethal trifecta": access to private data, exposure to untrusted content, and the ability to communicate externally. MCPhound automates detection of exactly this pattern at config time, before anything runs.
2. Code Tampering: Filesystem + Git
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/"]
},
"git": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-git"]
}
}
}
The filesystem server writes files anywhere. The git server runs git operations. The attack: Claude writes a .gitattributes file with a filter that executes arbitrary shell commands on checkout. Then it uses the git server to trigger the operation. Git's own filter mechanism runs the payload.
The git server already has 3 patched CVEs from 2025 (path traversal, command injection, restriction bypass). But even with all patches applied, the combination with an unrestricted filesystem server creates an attack path that neither server introduces alone.
3. Persistent Memory Poisoning: Fetch + Memory
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-fetch"]
},
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
}
}
}
The fetch server retrieves content from any URL. The memory server gives Claude persistent memory across conversations using a local knowledge graph. The attack: a malicious webpage injects a hidden instruction via prompt injection. Claude stores it in your memory server as a "helpful reminder." Every future Claude session starts poisoned -- even months later, even in different contexts. The memory server has no access control on the knowledge graph file. Any process with filesystem access can also read or modify stored memories.
This one is especially nasty because it persists. You close Claude, open it next week to work on something unrelated, and the poisoned memory is still there influencing every response.
Why existing scanners miss it
Every competitor in this space -- Invariant/Snyk, mcpscan.ai, Proximity, mcpwn -- scans servers individually. They check tool descriptions for prompt injection. They look for known CVEs. They evaluate permissions per-server.
This is necessary work. It is not sufficient.
The MCP architecture has a fundamental property that makes compositional attacks possible: the host relay. MCP servers do not talk to each other directly. Every tool invocation goes through the host application (Claude Desktop, Cursor, Windsurf, etc.), which means the LLM itself is the router. Server A returns data to the LLM. The LLM, potentially following an injected instruction, passes that data to Server B. No firewall, no network boundary, no access control between them. The host is the bridge.
Individual server scanners see:
- Filesystem server: has
file_read,file_write. Looks fine with path restriction. - Fetch server: has
network_out. Only makes GET requests. Looks fine.
MCPhound sees:
-
file_readon server A +network_outon server B = data exfiltration path through host relay - Path risk: 0.95 (min friction 0.05, 2 hops)
- Pattern: TRIFECTA-FILE-NET
- Severity: Critical
That is the difference between scanning servers and scanning configs.
This is not theoretical
Real incidents keep proving that compositional MCP attacks are exploitable:
- April 2025: WhatsApp MCP tool poisoning. An attacker used tool description manipulation to exfiltrate entire chat histories through a combination of read and send capabilities.
- May 2025: GitHub MCP prompt injection. Private repository data was leaked through crafted pull requests that triggered cross-tool data flows.
-
July 2025:
mcp-remotecommand injection (CVE-2025-6514). This package had 437,000 downloads. The vulnerability enabled arbitrary command execution through the transport layer. - October 2025: Smithery registry path traversal. Docker credentials and Fly.io tokens for 3,000+ servers were exposed through a directory traversal in the largest MCP server registry.
Each of these exploited the interaction between components, not a vulnerability in any single tool definition.
How MCPhound works
When you submit your config, here is what happens:
1. Graph construction (7 phases). MCPhound builds a NetworkX MultiDiGraph from your configuration. Server nodes get trust scores and friction values based on their trust tier (Official, Enterprise, Community). Tool nodes are created from real tool definitions or inferred from capability tags. Capability nodes (file_read, network_out, memory_write, etc.) connect to the tools that expose them. Data source nodes represent what each server can access. External endpoint nodes represent where data can go. A central host relay node connects all tools bidirectionally -- because that is how MCP actually works. Finally, untrusted input edges mark where prompt injection can enter the graph.
2. Attack path analysis (16 patterns). MCPhound runs BFS through the graph for each of 16 attack patterns, matching entry capabilities to exit capabilities across server boundaries. Patterns include TRIFECTA-FILE-NET (file exfiltration via network), MEMORY-POISON (AI memory poisoning), CLOUD-TAKEOVER (credential theft to cloud infrastructure), TOOL-SHADOW (file write to shell execution), and 12 others.
3. Friction-based scoring. Each path gets a risk score computed as path_risk = (1 - min_friction) * hop_decay. Friction measures how hard it is for data to traverse a node -- Official servers with no CVEs have high friction (harder to exploit), Community servers with known vulnerabilities have low friction (easier). Hop decay (0.85^n for n > 2 hops) penalizes longer chains. A 2-hop path through two low-friction Community servers scores near 1.0. A 4-hop path through hardened Enterprise servers scores much lower.
4. Chokepoint identification. MCPhound computes which nodes appear across the most attack paths. If your fetch server shows up in 80% of discovered paths, that is your highest-leverage fix. Remove or restrict that one server and you eliminate the majority of your attack surface.
5. Minimum hitting set. For remediation, MCPhound computes the smallest set of changes that eliminates all discovered attack paths. This is the greedy minimum hitting set over the path collection. Instead of "here are 47 findings, good luck," you get "restrict these 2 servers and all 47 paths are eliminated."
6. Output. Results come as structured JSON with full path details, narratives explaining each attack chain in plain English, and SARIF for direct integration with GitHub Code Scanning.
MCPhound's output
Here is what the scan result looks like for the filesystem + fetch config from above:
{
"status": "success",
"data": {
"summary": {
"servers_scanned": 2,
"attack_paths_found": 3,
"max_risk_score": 0.95,
"risk_level": "Critical"
},
"attack_paths": [
{
"pattern_id": "TRIFECTA-FILE-NET",
"severity": "Critical",
"path_risk_score": 0.95,
"narrative": "Server 'filesystem' reads files via 'read_file'. Data relays through the host application to 'fetch' on server 'fetch', which sends it externally.",
"path": ["tool:filesystem:read_file", "host_relay", "tool:fetch:fetch"],
"hop_count": 2,
"min_friction": 0.05
}
],
"chokepoints": [
{
"node_id": "tool:fetch:fetch",
"centrality": 0.67,
"remediation_impact": "Restricting 'fetch' would disrupt 67% of discovered attack paths."
}
],
"remediation": {
"minimum_changes": 1,
"actions": [
{
"server": "filesystem",
"action": "Restrict path from '/' to your project directory",
"impact": "Eliminates access to ~/.ssh, ~/.env, and system files"
}
]
}
}
}
Each attack path includes a plain-English narrative. Each remediation action is specific and actionable. The chokepoint analysis tells you where to focus.
How to use it
Web -- mcphound.ai
Paste your claude_desktop_config.json (or .cursor/mcp.json, or any MCP client config). Get results in about 30 seconds. Get a shareable URL and a README badge for your project. API keys and environment variables are stripped before storage.
Your config file is at:
- Mac:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
CI -- GitHub Action
Add MCPhound to your pipeline in 3 lines:
- name: MCPhound Scan
uses: tayler-id/mcphound-action@v0
with:
config-path: .cursor/mcp.json
Results appear directly in GitHub Code Scanning as SARIF findings. Every PR that changes your MCP config gets scanned automatically.
What MCPhound is not
MCPhound is a static config analyzer. It does not sit in the runtime path. It does not proxy MCP traffic. It does not require agents or sidecars. It reads your JSON config file, builds a graph, finds attack paths, and tells you what to fix.
It is complementary to runtime tools like Lasso MCP Gateway and credential managers like Astrix MCP Secret Wrapper. MCPhound catches the problems at config time. Runtime tools catch them at execution time. You probably want both.
The bottom line
The MCP ecosystem is moving fast. 70+ clients, thousands of servers, millions of downloads. The protocol was designed for extensibility, and that extensibility creates a combinatorial security problem that individual server scanners are structurally incapable of catching.
Your MCP servers are probably fine. Your configuration -- the specific combination of servers, their arguments, their capabilities, the paths between them -- is where the risk lives.
Check yours in 30 seconds at mcphound.ai.
Add MCPhound to your CI pipeline: tayler-id/mcphound-action.
Top comments (0)