mcpscan
README.md
# mcpscan — MCP server & agent-skill supply-chain scanner (MVP)
[](https://github.com/fpavia111-creator/mcpscan/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/mcpscan)
[](LICENSE)
[](https://registry.modelcontextprotocol.io)
> Supply-chain security scanner for MCP servers and agent skills (Claude, Cursor, VS Code, Windsurf).
## Quickstart
**Install** (Node.js ≥ 18):
```bash
npm install -g mcpscan # or: npx mcpscan scan
```
**Claude Code**
```bash
claude mcp add mcpscan -- npx -y mcpscan@0.1.0 mcp
```
**Cursor** — add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project). The same snippet works for Claude Desktop, VS Code and Windsurf:
```json
{ "mcpServers": { "mcpscan": { "command": "npx", "args": ["-y", "mcpscan@0.1.0", "mcp"] } } }
```
Restart the client and the tools appear. Full details below.
Teams install third-party **MCP servers** and **agent skills** (Claude Code skills, plugin skills, Cursor skills) the same way they once installed random npm packages: copy a JSON snippet, run `npx -y something`, and trust it. Those components can be **dead**, **over-privileged**, or can **hide prompt injection** in a `SKILL.md` or a tool description.
`mcpscan` is a zero-dependency Node.js CLI that finds every MCP config and skill folder on a machine (Windows, macOS, Linux), runs offline static checks, and writes **text / JSON / SARIF / HTML** reports with CI-friendly exit codes. It can also run as a **read-only MCP server** so an agent can audit its own environment.
> Status: MVP. Static, pattern-based analysis. Read [Limitations](#limitations) before relying on it.
## What it finds
| Rule | Default sev. | Check |
|---|---|---|
| MCP001 | high | Prompt injection / hidden instructions ("ignore previous instructions", "do not tell the user", `<IMPORTANT>` tags, instructions inside HTML comments, "bypass safety") in SKILL.md, docs, and tool descriptions |
| MCP002 | high / critical | Zero-width, bidi-override, and Unicode **tag** characters (tag text is decoded and shown) |
| MCP003 | critical | Instructions to send data to an external endpoint or harvest credentials |
| MCP004 | high / critical | Dangerous commands in scripts, SKILL.md code blocks, and server command lines: `curl … \| sh`, `iwr … \| iex`, `rm -rf ~`, `Remove-Item -Recurse -Force`, `base64 -d \| sh`, reverse shells, persistence |
| MCP005 | high | Credential paths: `~/.ssh/id_*`, `~/.aws/credentials`, `.kube/config`, browser `Login Data`, `.netrc`, `%USERPROFILE%\.ssh\…` |
| MCP006 | medium | Large base64 blobs (possible obfuscated payloads) |
| MCP007 | high / critical | Hardcoded secrets in `env`, `headers`, args, URLs, and skill files (GitHub, Anthropic, OpenAI-style, AWS, Slack, Stripe, GitLab, npm, xAI, JWT, DB URLs, private keys). **Values are always masked** (first 4 chars + length) |
| MCP008 | low–high | Unpinned packages: `npx -y pkg`, `pkg@latest`, ranges like `^1.2.0`, `uvx pkg`, `docker image:latest` (handles `cmd /c npx.cmd …` on Windows) |
| MCP009 | high (info for localhost) | Plain `http://` / `ws://` remote transport, secrets in URL query strings |
| MCP010 | medium / high | Excessive filesystem roots (`/`, `C:\`, `~`, `%USERPROFILE%`, the whole home dir) and dangerous docker mounts (`/`, `docker.sock`) |
| MCP011 / MCP012 | high / medium | Policy deny-list hit / not on allow list |
| MCP013–016 | varies | `--online` only: npm deprecated, stale (no publish in 365 days), GitHub repo archived/missing, package missing from registry |
| MCP017 | low | Config file could not be parsed |
| MCP018 | info | SKILL.md missing `name`/`description` frontmatter |
`mcpscan rules` prints the catalog.
## Where it looks
| Client | Windows | macOS | Linux |
|---|---|---|---|
| Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json` | `~/Library/Application Support/Claude/…` | `~/.config/Claude/…` |
| Claude Code | `%USERPROFILE%\.claude.json` (user + per-project servers), `%USERPROFILE%\.claude\settings.json` | `~/.claude.json`, `~/.claude/settings.json` | same |
| Cursor | `%USERPROFILE%\.cursor\mcp.json` | `~/.cursor/mcp.json` | same |
| VS Code (+ Insiders, VSCodium) | `%APPDATA%\Code\User\mcp.json` and `settings.json` (`mcp.servers`) | `~/Library/Application Support/Code/User/…` | `~/.config/Code/User/…` |
| Windsurf | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` | `~/.codeium/windsurf/mcp_config.json` | same |
| Project | `.mcp.json`, `.cursor\mcp.json`, `.vscode\mcp.json`, `.windsurf\mcp.json` | same | same |
| Skills | `%USERPROFILE%\.claude\skills`, `%USERPROFILE%\.claude\plugins\**\skills`, `%USERPROFILE%\.cursor\skills`, project `.claude\skills` | `~/.claude/...` | same |
Run `mcpscan paths` to see the exact paths checked on your OS. JSONC (comments, trailing commas) is supported. Symlinks are never followed.
## Install & use
Needs Node.js 18 or newer. No runtime dependencies, so you don't have to run `npm install` to use it.
### Windows (PowerShell)
```powershell
# from the folder you copied (e.g. C:\path\to\mcpscan)
cd $env:USERPROFILE\mcp-skill-scanner
node .\bin\mcpscan.js paths # where it will look
node .\bin\mcpscan.js scan # user configs + current folder
node .\bin\mcpscan.js scan -p C:\code\my-repo -f html -o "$env:TEMP\mcpscan.html"
Start-Process "$env:TEMP\mcpscan.html"
npm test # run the test suite (offline)
# optional: put `mcpscan` on PATH
npm link
mcpscan scan --fail-on critical
$LASTEXITCODE # 0 = clean, 1 = findings, 2 = error
```
If PowerShell blocks the `mcpscan.ps1` shim that `npm link` creates, run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`, or keep calling `node .\bin\mcpscan.js`.
### macOS / Linux
```bash
node bin/mcpscan.js scan
node bin/mcpscan.js scan -p ~/code/app -f sarif -o mcpscan.sarif
```
### Options
```
mcpscan [scan] [-p dir]... [-c config.json]... [-s skillsDir]... [-t tools.json]...
[-f text|json|sarif|html] [-o file] [--fail-on critical|high|medium|low|info|none]
[--min-severity sev] [--policy file] [--online] [--root home] [--platform win32|darwin|linux]
[--no-default-roots] [--no-color]
mcpscan list | paths | rules | init-policy | mcp
```
- **Vet a skill before installing:** `mcpscan scan --no-default-roots -s .\downloaded-skill`
- **Vet tool descriptions:** save a server's `tools/list` result to JSON, then `mcpscan scan --no-default-roots -t tools.json`
- **Exit codes:** `0` = nothing at or above `--fail-on` (default `high`, or `failOn` from the policy), `1` = findings, `2` = usage/runtime error.
### CI (GitHub Actions + code scanning)
```yaml
- run: node bin/mcpscan.js scan --no-default-roots -p . -f sarif -o mcpscan.sarif --fail-on high
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: mcpscan.sarif }
```
## Policy file (`mcpscan.policy.json`)
Loaded from the current directory, or pass `--policy path`. `mcpscan init-policy` writes a starter file.
```json
{
"version": 1,
"failOn": "high",
"requireAllowList": false,
"allow": { "servers": [], "packages": ["@modelcontextprotocol/*"], "hosts": ["mcp.linear.app"], "skills": [] },
"deny": { "servers": [], "packages": ["*-mcp-free-*"], "hosts": ["*.ngrok-free.app"], "skills": [] },
"ignore": [ { "rule": "MCP018" }, { "rule": "MCP008", "target": "internal-*" } ],
"severityOverrides": { "MCP014": "low" }
}
```
`*` is a wildcard. When `requireAllowList` is `true`, any server or skill that isn't allow-listed raises MCP012.
## MCP server mode (read-only)
`mcpscan mcp` runs a stdio MCP server (JSON-RPC 2.0, protocol `2025-06-18`) with four tools, each annotated `readOnlyHint: true`: `scan_all`, `list_discovered`, `scan_skill`, `scan_text`. None of them writes files or touches the network.
```json
{ "mcpServers": { "mcpscan": { "command": "node", "args": ["C:\\path\\to\\mcpscan\\bin\\mcpscan.js", "mcp"] } } }
```
Claude Code: `claude mcp add mcpscan -- node /path/to/mcpscan/bin/mcpscan.js mcp`
## Claude Code skill
`skills/mcpscan/` holds a ready-made skill (`SKILL.md` + `reference.md`). Copy the folder into `%USERPROFILE%\.claude\skills\` (Windows) or `~/.claude/skills/` (macOS/Linux), or into a repo's `.claude/skills/`:
```powershell
Copy-Item -Recurse .\skills\mcpscan "$env:USERPROFILE\.claude\skills\mcpscan"
```
## Competitors and prior art
- **mcp-scan / Snyk Agent Scan** by Invariant Labs, acquired by Snyk on June 24, 2025. It discovers MCP servers and skills and scans tool descriptions for tool poisoning, rug pulls, and cross-origin escalation. It connects to servers and sends tool names and descriptions to a remote analysis API. Snyk also offers a background mode that reports to Snyk Evo for company-wide monitoring.
- Repo: https://github.com/invariantlabs-ai/mcp-scan
- Launch post: https://invariantlabs.ai/blog/introducing-mcp-scan
- Acquisition: https://snyk.io/news/snyk-acquires-invariant-labs-to-accelerate-agentic-ai-security-innovation/
- **Cisco AI Defense mcp-scanner**, which scans MCP servers for threats: https://github.com/cisco-ai-defense/mcp-scanner
**How mcpscan differs:** it runs fully offline by default and sends no tool descriptions to a third party. It has zero dependencies, so it's easy to audit. It covers supply-chain hygiene (pinning, secrets, filesystem scope, transport, liveness) as well as injection. It produces native SARIF for CI, applies a simple policy file, and covers Windows paths explicitly. Its trade-off: no LLM-based semantic analysis, and it doesn't connect to live servers.
## Limitations
- Detection is regex/heuristic. Expect false positives. Skills that legitimately say "do not tell the user X" get flagged MCP001. Expect false negatives too: paraphrased or translated injections, payloads split across files, and obfuscation beyond base64 and hidden Unicode get through.
- Tool descriptions are only checked when they appear in a config, or when you pass a `tools/list` dump with `-t`. mcpscan doesn't launch or connect to servers, so it can't detect rug pulls (description changes over time).
- `--online` checks npm and GitHub only. PyPI and Docker Hub liveness aren't implemented, and unauthenticated GitHub API calls are rate-limited (60 requests/hour).
- Config paths cover the clients' current documented defaults. Portable installs, custom `CLAUDE_CONFIG_DIR`, and enterprise-managed settings aren't discovered automatically; pass them with `-c`/`-s`.
- Homoglyph and confusable-character detection isn't implemented.
- The MCP server mode implements tools only (no resources or prompts) over stdio.
## Development
```bash
npm run build # syntax-check all files, load modules, validate policy JSON
npm test # node:test, fully offline (fetch is stubbed to throw)
npm run demo # scan the bundled malicious fixtures
node scripts/make-fixtures.js # regenerate fixtures (all secrets are fake)
```
## License
MIT © 2026 Kurt Francisco. See [LICENSE](LICENSE).
## Contributing
Issues and pull requests are welcome at https://github.com/fpavia111-creator/mcpscan. See [CONTRIBUTING.md](CONTRIBUTING.md).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues