mcp-scan
Passive security scanner: audits MCP servers against the OWASP MCP Top 10, graded A-F.
Open source Open in the app JSON README (API)
About
Passive security scanner: audits MCP servers against the OWASP MCP Top 10, graded A-F.
Details
- Kind
- MCP servers
- Topic
- Security & identity
- Publisher
- codingselim
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.2.1
- Open pull requests
- 4
- Last push
- 2026-08-01T07:46:45Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-08-29 03:01:48
- Updated
- 2026-08-29 03:01:48
- Origin id
io.github.CodingSelim/mcp-scan
README
<div align="center">
# 🛡️ mcp-scan
**Passive security scanner for Model Context Protocol servers.**
Point it at any running MCP server; it audits the live server against the **OWASP MCP Top 10** and grades it **A–F**. Read-only, so it is safe to run against production.
[](https://www.npmjs.com/package/owasp-mcp-scan)
[](./LICENSE)
[](https://nodejs.org)
[](https://github.com/CodingSelim/mcp-scan/actions/workflows/ci.yml)
[](#develop)
[](https://owasp.org/www-project-mcp-top-10/)
```bash
npx owasp-mcp-scan --stdio "npx -y @modelcontextprotocol/server-filesystem /tmp"
```
<img src="./docs/cli-demo.png" alt="mcp-scan console report: firecrawl-mcp graded F with critical findings" width="760">
I ran it against the 12 most-installed MCP servers. **[9 of them failed.](#real-findings-on-real-servers)** No install, no config, no exploiting anything.
</div>
---
## Why
MCP tool descriptions are fed straight into your model's context, and most public servers were never security-reviewed. A bad one can hide instructions in a description, expose a raw-shell tool, or leak a key; and your agent acts on it. Endor Labs found **82%** of servers prone to path traversal, **34%** to command injection.
**mcp-scan is the gut-check before you wire a server in.** It's passive: it reads advertised capabilities and analyzes them statically, never invoking tools, so it's safe against production servers.
## How it works
1. Connects to the server over stdio or Streamable HTTP and enumerates every advertised tool, prompt, and resource.
2. Runs 12 static checks across that surface: secrets, injection, SSRF, path traversal, tool poisoning, excessive scope, and more.
3. `toxic-flow` reasons across the **whole** toolset for the lethal trifecta (reads private data + ingests untrusted content + can exfiltrate), the shape single-tool scanners miss.
4. Scores 0-100 and grades A-F (any critical forces F). Emits console, JSON, or SARIF.
It never calls a tool, never fuzzes, and never sends an exploit. The worst it does is read what the server already tells everyone.
## Real findings on real servers
12 popular npm servers, actual output (snapshot 2026-07-11, [full benchmark](./docs/BENCHMARK.md)):
| Server | Tools | Grade | 🔴 | 🟠 | 🟡 | Notable |
|---|---:|:---:|---:|---:|---:|---|
| `firecrawl-mcp` | 26 | **F** | 2 | 11 | 1 | **lethal trifecta** (MCP10) + `code` exec (MCP05) |
| `@modelcontextprotocol/server-filesystem` | 14 | **F** | 0 | 1 | 11 | unconstrained path params (MCP01) |
| `@modelcontextprotocol/server-puppeteer` | 7 | **F** | 1 | 2 | 0 | `script` param executes JS (MCP05) |
| `tavily-mcp` | 5 | **F** | 0 | 3 | 1 | untrusted-input + exfiltration (MCP10) |
| `@modelcontextprotocol/server-memory` | 9 | **F** | 0 | 3 | 0 | `delete_*` tools, no confirmation (MCP02) |
| `@modelcontextprotocol/server-github` | 26 | **D** | 0 | 0 | 2 | state-changing tools (MCP02) |
| `@modelcontextprotocol/server-slack` | 8 | **C** | 0 | 1 | 0 | reads + posts = data + exfil (MCP10) |
| `@modelcontextprotocol/server-everything` | 13 | **A** | 0 | 0 | 0 | clean ✓ |
| `@kazuph/mcp-fetch` | 1 | **A** | 0 | 0 | 0 | clean ✓ |
**9 of 12 flagged, 3 clean**, every row audited finding-by-finding, false positives stripped rather than padded. Reproduce any row yourself:
```bash
npx owasp-mcp-scan --stdio "npx -y firecrawl-mcp" # F: lethal trifecta + code exec
npx owasp-mcp-scan --stdio "npx -y @modelcontextprotocol/server-everything" # A: clean
```
## Use it
**CLI**
```bash
npx owasp-mcp-scan --stdio "<command>" # local stdio server
npx owasp-mcp-scan --url https://host/mcp --header "Authorization: Bearer $TOKEN"
npx owasp-mcp-scan --config ~/.cursor/mcp.json --format sarif --output mcp.sarif
```
**As an MCP server**, let your agent scan servers on demand (*"scan this MCP server before I add it"*). Add to any client; this `mcpServers` shape works in Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, and Gemini CLI:
```json
{ "mcpServers": { "mcp-scan": { "command": "npx", "args": ["-y", "owasp-mcp-scan", "--serve"] } } }
```
OpenAI Codex (`~/.codex/config.toml`):
```toml
[mcp_servers.mcp-scan]
command = "npx"
args = ["-y", "owasp-mcp-scan", "--serve"]
```
Exposes two tools: **`scan_mcp_server`** (audit a stdio/HTTP target) and **`list_checks`**.
**Claude Code plugin**
```
/plugin marketplace add CodingSelim/mcp-scan
/plugin install mcp-scan@mcp-scan
```
Also on the official MCP registry as `io.github.CodingSelim/mcp-scan`. Publishing steps: [PUBLISHING.md](./PUBLISHING.md).
## What it checks
Full **OWASP MCP Top 10 (2025)** coverage, 12 checks:
| Check | OWASP | Catches |
|---|---|---|
| `secret-exposure` | MCP01 | AWS / OpenAI / Anthropic / GitHub / GitLab / Stripe / SendGrid / npm / HF / DB URIs / JWT / private keys in advertised text |
| `transport` | MCP01 | Plaintext `http://` to a non-loopback host |
| `path-traversal` | MCP01 | `file:///{path}` templates and unconstrained path params |
| `excessive-scope` | MCP02 | Destructive tools (`delete`, `drop`, `transfer`) with no confirmation |
| `tool-poisoning` | MCP03 | Instruction overrides, hidden exfiltration directives, zero-width / Unicode-tag smuggling |
| `tool-shadowing` | MCP03 / MCP09 | Duplicate tool-name collisions and "call me first" precedence injection |
| `supply-chain` | MCP04 / MCP09 | Unpinned/placeholder versions and homoglyph server names |
| `command-injection` | MCP05 | Unconstrained `command` / `shell` / `code` params, raw SQL, advertised execution |
| `ssrf` | MCP05 | Arbitrary `url` / `host` params with no allowlist |
| `tool-poisoning` (dynamic) | MCP06 | Injection in resource contents and server instructions |
| `authn` | MCP07 | HTTP servers that complete an unauthenticated handshake |
| `telemetry` | MCP08 | High-impact tools with no audit trail (advisory, unscored) |
| `toxic-flow` | MCP10 | **Lethal trifecta**: one server that reads private data, ingests untrusted content, and can exfiltrate |
`toxic-flow` is the standout, it reasons across the whole toolset, catching the GitHub-MCP / email-agent injection shape that per-tool checks miss.
## Output & CI
`console` (default) · `json` · `sarif` (GitHub Code Scanning). Exit code is non-zero at/above `--fail-on` (default `high`), so it gates CI:
```yaml
- run: npx owasp-mcp-scan --url ${{ secrets.MCP_URL }} --format sarif --output mcp.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: mcp.sarif }
```
## Limitations (read these)
- Static analysis can't see runtime sandboxing, a server that safely confines paths still flags them. Verify against actual enforcement.
- Auth and transport checks apply to HTTP targets only.
- Heuristics favor recall, so triage findings in context. When a rule is tightened to kill a false positive, a test pins the intended behavior.
- Scanning a stdio target spawns that command, so run it only on servers you trust.
## Develop
```bash
npm install && npm run build && npm test # 48 tests, incl. live end-to-end fixture scans
```
Checks are pure and isolated (`src/checks/`), reusing detectors in `src/detectors/`. See [CONTRIBUTING.md](./CONTRIBUTING.md).
## References
[OWASP MCP Top 10](https://owasp.org/www-project-mcp-top-10/) · [MCP Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/MCP_Security_Cheat_Sheet.html) · [Vulnerable MCP Project](https://vulnerablemcp.info/) · [MCPTox](https://arxiv.org/pdf/2508.14925)
MIT © [CodingSelim](https://github.com/CodingSelim)