Audit MCP servers for tool poisoning, rug pulls and supply-chain risk (OWASP MCP Top 10).
Runs on your machine from its package; the steps are below.
Not assessed yet.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y mcp-security-guard
MCP client config
{
"mcpServers": {
"mcp-security-guard": {
"command": "npx",
"args": [
"-y",
"mcp-security-guard"
]
}
}
}
A Claude Code plugin that audits the MCP servers you have installed. Your code is covered by other tools. This one checks the servers that inject text into Claude's context.
| Check | What it catches |
|---|---|
| Tool poisoning | Instruction overrides, "don't tell the user", <IMPORTANT> tags, directives to read secrets (~/.ssh, .env), conversation harvesting, exfiltration via URLs, parameters and Markdown images, HTML comments, encoded payloads |
| Full-schema poisoning | The same checks on parameter names, descriptions, defaults, enums, required, plus non-schema text in type |
| Hidden text | Zero-width, bidi-control and Unicode-tag characters, ANSI terminal escapes, homoglyph tool names |
| Tool shadowing | A server whose descriptions reference another server's tools, and tool-name collisions between servers |
| Rug pulls | Tool definitions or launch commands that changed after you pinned them (SHA-256 per tool), and skills, commands, subagents, CLAUDE.md and hooks of a plugin or repository that changed after you pinned them (a plugin that changes files under the same version is flagged high). Re-checked automatically at every session start |
| Capabilities & score | Per-tool classification (execute, delete, write, egress), unauthenticated remote write access, 0–100 score and A–F grade per server, recommended permission rules |
| Supply chain | OSV vulnerabilities and malicious versions, typosquats, missing or brand-new packages, install scripts, publisher changes (opt-in network check) |
| Runtime | Hooks on every MCP call: ask before credentials are sent, warn on injected instructions or credentials in outputs, content-free audit log |
| Policy | .mcp-security.json approved/blocked servers and hosts, enforced in audits, CI and at session start |
| Toxic flows | Servers that hold all three legs of the "lethal trifecta" (untrusted input, private data, a way to send data out), on their own or together, with the tools to put behind approval. A posture note (low or info), not a defect |
| Agent context | The text Claude reads besides MCP tools: skills and their scripts, slash commands, subagents, rules, CLAUDE.md and plugin hooks. Instruction overrides, "don't tell the user", invisible text, HTML comments addressed to the model, directives to read credential files, commands that upload credentials, download-and-run, encoded execution, infostealer scripts, pre-approved unrestricted Bash |
| Configuration | Plaintext secrets in env/headers/args/URLs, plain-HTTP remotes, unpinned npx/uvx packages, privileged or unpinned Docker images, pipe-to-shell launches, duplicate names across scopes |
It discovers servers from every place Claude Code and Claude Desktop load them: user, local and project scope, servers shipped inside installed plugins and plugins synced from your claude.ai account (named <plugin>:<server>), claude_desktop_config.json and Claude Desktop extensions, the organisation-managed managed-mcp.json, other clients on the machine (Cursor, VS Code, Windsurf, user and project configs), and the claude.ai connectors you have used (names only: their configuration lives in your account).
It scans everything a server puts into Claude's context, not only tools: server instructions, prompts, resources and resource templates go through the same poisoning checks and are pinned for rug-pull detection.
Everything runs locally. Nothing is sent anywhere.
Every finding is tagged with its OWASP MCP Top 10 id, in reports and in SARIF.
| ID | Risk | Covered by |
|---|---|---|
| MCP01 | Token Mismanagement & Secret Exposure | ✅ Plaintext secrets in env, headers, args and URLs. At runtime, asks before a credential is sent to an MCP server and warns when one comes back. |
| MCP02 | Privilege Escalation via Scope Creep | ✅ Capability inventory (execute, delete, write, egress), ready-to-paste permissions.ask rules, privileged or broadly mounted containers |
| MCP03 | Tool Poisoning | ✅ 26/27 published techniques detected, including full-schema poisoning, shadowing and name collisions, plus rug-pull pinning |
| MCP04 | Supply Chain Attacks | ✅ Unpinned packages, images and git sources; OSV vulnerabilities and malicious versions; typosquats; new packages; install scripts; publisher changes |
| MCP05 | Command Injection & Execution | ✅ Flags tools that can execute commands; adversarial_test finds injectable parameters in servers you own |
| MCP06 | Prompt Injection via Contextual Payloads | ✅ In tool metadata and, at runtime, in tool outputs (English patterns) |
| MCP07 | Insufficient AuthN/AuthZ | ✅ Plain-HTTP remotes; remote servers exposing write or exec tools without authentication; OAuth detection |
| MCP08 | Lack of Audit and Telemetry | ✅ Local, content-free audit log of every MCP call, with query_audit_log |
| MCP09 | Shadow MCP Servers | ✅ Discovery across user, project, local, plugin and Claude Desktop configs; approved-server policy enforced in audits, CI and at session start |
| MCP10 | Context Injection & Over-Sharing | ✅ Conversation and system-prompt harvesting, Markdown-image exfiltration, credentials in outputs, network-egress inventory |
What a local tool cannot do (planned for a hosted Team plan): org-wide discovery and audit aggregation, and OAuth scope review.
Measured: detects 26/27 attacks from a corpus of publicly documented techniques, with 0 false positives on 13 hard benign samples and on 17 real servers (83 tools). See bench/RESULTS.md.
/plugin marketplace add petrovicistefan/mcp-security-guard
/plugin install mcp-security-guard@mcp-security-guard
Then run /mcp-audit, or ask Claude "are my MCP servers safe?".
The same server is published on npm and runs with no install step:
{ "mcpServers": { "mcp-security-guard": { "command": "npx", "args": ["-y", "mcp-security-guard"] } } }
The command line tool is the same package: npx mcp-security-guard audit --project-only. It is also listed in the official MCP Registry as io.github.petrovicistefan/mcp-security-guard.
Team keys are issued by hand for now: write to me. With a team API key (MCP_SECURITY_API_KEY, or the key setting of the plugin) the plugin can also work with your organisation. Everything below is off without a key, and the free plugin stays fully functional.
.mcp-security.json cannot loosen it.allowedPlugins and blockedPlugins in a policy are enforced in audits, CI and at session start; a blocked plugin is critical, an unlisted one high.team report sends which servers and plugins you run: names, scopes, transport, package names and versions or remote hostnames, pinned or not. Never paths, arguments, environment, headers, query strings or secrets. Reports are refused until an admin turns fleet visibility on, team report --dry-run shows exactly what would be sent, and sending at session start needs MCP_SECURITY_TEAM_REPORT=on.team request server project:linear asks the admin; team approve adds it to the policy's allow list for everyone. Alerts (new request, new violation, policy change) go to a Slack-compatible webhook.team keys create ana, shown once; only its hash is stored) and revokes it (team keys revoke); a purchase sets the seat limit. The same admin actions are in the web dashboard (/v1/team/dashboard on the service: fleet, approvals, policy, keys, settings; the key is typed in, kept in the browser tab only, and every value from the service is written as text).team settings --email you@example.com) next to the Slack-compatible webhook; at most 20 a hour per organisation.team compliance --output report.md (admin) writes a Markdown report from the policy, the latest fleet reports and the approval log: controls mapped to the OWASP MCP Top 10, each met or gap with its evidence, plus violations, stale reports and pending approvals. Together with bom (agent bill of materials) it is what an auditor asks for.team approve, reject, policy-push, settings, inventory), not MCP tools, so injected text cannot trigger them. The MCP tools team_status, request_approval and team_report ask for confirmation and cannot approve anything.mcp-security-guard team status
mcp-security-guard team report --dry-run
mcp-security-guard team request plugin some-plugin --note "for the docs site"
mcp-security-guard team approvals --status pending # admin
mcp-security-guard team approve apr_… --note ok # admin
mcp-security-guard team policy-push policy.json # admin
mcp-security-guard team keys create ana # admin: a key for a developer, shown once
mcp-security-guard team settings --fleet on --webhook https://hooks.slack.com/… --email alerts@acme.example # admin
| Tool | Launches servers? |
|---|---|
list_mcp_servers | No |
audit_mcp_config | No |
audit_server_tools | Yes, after explicit confirm_launch: true. Sends only initialize and list requests (tools, prompts, resources); never calls a tool, renders a prompt or reads a resource |
pin_tools | Yes (same as above). Writes ~/.claude/mcp-security/pins.json |
export_bom | No. Writes nothing and launches nothing: an inventory of servers, plugins, skills, commands, subagents and CLAUDE.md as a CycloneDX 1.6 agent bill of materials or a Markdown summary with OWASP MCP Top 10 evidence |
audit_agent_context | No. Reads skills, commands, subagents, rules, CLAUDE.md and plugin hooks (user, project and installed plugins); project_only limits it to the repository |
team_status, request_approval, team_report | Network, after confirmation. Team plan: sync the organisation's policy, ask the admin to approve a server or plugin, preview and send a fleet report (see above) |
pin_context | No. Writes ~/.claude/mcp-security/context-pins.json (SHA-256 per skill, command, subagent, rule, CLAUDE.md, hook config and script, per origin). Skips origins with critical or high findings unless force |
analyze_tool_definitions | No. Offline analysis of a tools/list payload, for MCP server authors |
check_supply_chain | No. Sends package names and versions to npm, PyPI and OSV after confirm_network: true |
apply_fixes | Only for the permissions fix. Dry run by default; with write: true it edits the project's .mcp.json / .claude/settings.json after a backup to ~/.claude/mcp-security/backups/ |
security_dashboard | Only with scan: "full" and confirm_launch: true. Interactive dashboard (MCP App) |
generate_policy | No. Returns a .mcp-security.json approving the current servers |
query_audit_log | No. Summarises the runtime audit log |
adversarial_test | Yes, and calls tools with injection payloads. Only for servers you own; needs i_own_this_server and confirm_launch; skips destructive tools |
A SessionStart hook re-verifies only the servers you have pinned (pinning is your consent to launch them) and stays silent unless something changed. Control it with MCP_SECURITY_SESSION_CHECK:
full (default): compare launch configs and re-list toolsconfig: compare launch configs only, launch nothingoff: disable the checkFail pull requests that add risky MCP servers to .mcp.json, and show the findings in GitHub code scanning:
name: MCP security
on: [pull_request]
permissions:
contents: read
security-events: write
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: petrovicistefan/mcp-security-guard@main
id: mcp
with:
fail-on: high # critical | high | medium | low | info | none
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.mcp.outputs.sarif-file }}
To also fail pull requests that add a poisoned skill, command or CLAUDE.md to the repository, set context: true on the action (second SARIF file in context-sarif-file). The same checks run locally without Claude:
node plugin/dist/cli.mjs audit --project-only --format sarif --output mcp.sarif
node plugin/dist/cli.mjs analyze-tools tools.json --name my-server # for MCP server authors: a saved tools/list result
Scan the skills, commands, subagents and CLAUDE.md files next to your servers (add --project-only for a repository you are about to trust):
node plugin/dist/cli.mjs audit-context --project-only --fail-on high
Pin what you have reviewed with node plugin/dist/cli.mjs pin-context (or ask Claude to run pin_context); from then on audit-context and the session-start check report any pinned plugin, skill, command or CLAUDE.md that changed.
Quoted attack phrases inside examples or fenced code are reported as medium (documentation), install snippets such as curl … | sh as low; the same patterns in scripts, hooks and inline ! commands are high.
Check a server before installing it (launches it, sends only initialize and tools/list):
node plugin/dist/cli.mjs scan some-server.mcp.json --confirm-launch
Test your own server for command injection and path traversal (it calls the tools; run a test instance, ideally in a container):
node plugin/dist/cli.mjs adversarial my-server.mcp.json --server my-server --i-own-this-server --confirm-launch
Fix what the audit found (dry run first, then --write):
node plugin/dist/cli.mjs fix --pin-versions # npx pkg → pkg@x.y.z, uvx pkg → pkg==x.y.z (looks up npm/PyPI)
node plugin/dist/cli.mjs fix --env-refs --write # literal secrets in .mcp.json → ${VAR} references
node plugin/dist/cli.mjs fix --permissions --confirm-launch --write # permissions.ask rules for risky tools
Start a team policy from the servers configured today:
node plugin/dist/cli.mjs policy-init && git add .mcp-security.json
Any command takes --format markdown|json|sarif|html. The HTML report is a single self-contained file you can open in a browser or attach to a ticket.
Exit codes: 0 clean, 1 findings at or above --fail-on, 2 usage error.
Remote servers that require OAuth (most hosted MCP servers) cannot be scanned at the tool level: the scanner cannot reuse Claude Code's tokens. Their configuration is still audited.
security_dashboard is an MCP App: hosts that support MCP Apps (Claude Desktop, claude.ai, VS Code Copilot…) render it inline. It shows every server with its score and grade, findings filterable by severity and server, the OWASP MCP Top 10 breakdown, recommended permission rules, and Full scan and Pin buttons. Selecting a server tells Claude what you are looking at, so follow-up questions have context. Claude Code in a terminal gets the text summary instead.
To use it in Claude Desktop, add the server to claude_desktop_config.json and ask Claude to "open the MCP security dashboard":
{ "mcpServers": { "mcp-security-guard": { "command": "node", "args": ["/path/to/mcp-security-guard/plugin/dist/index.mjs"] } } }
The UI is a single self-contained HTML file. Server-supplied text reaches the page only as text (never as HTML), and the host's sandbox applies. Develop it with a local host that drives the real server: npm run dashboard:dev -- /path/to/project.
| Hook | What it does | Setting |
|---|---|---|
PreToolUse on mcp__* | Asks for confirmation when a call's arguments contain a credential | MCP_SECURITY_SECRET_GUARD=ask (default), deny or off |
PostToolUse on mcp__* | Warns Claude and you when an output contains injected instructions, hidden characters, exfiltration markup or a credential | always on |
| Audit log | ~/.claude/mcp-security/audit.jsonl: server, tool, time, input hash and sizes. Never arguments or outputs. Rotates at 10 MB. | MCP_SECURITY_AUDIT_LOG=off |
The hooks add about 40 ms per MCP call.
policy-init (which writes a file you asked for).check_supply_chain / --supply-chain send package names and versions to npm, PyPI and OSV. The adversarial_test tool is the only one that calls tools.npm install
npm run build # bundles to plugin/dist/ (committed, so the plugin runs without npm install)
npm test
npm run bench # false-positive gate on real servers (network; Docker images optional, see bench/RESULTS.md)
test/fixtures/poisoned-server.mjs is a deliberately malicious server used by the end-to-end test.
Everything above is free and stays free: it runs locally and needs no account. Paid plans add what needs a server: a daily threat feed of known malicious MCP servers, packages and skills, alerts when a server you use ships changed tool descriptions, history, and team policies, approvals and a dashboard. They are opt-in through MCP_SECURITY_API_KEY; see PRIVACY.md for exactly what is sent.
Paid plans are not on sale yet, and there is no checkout. If you or your team would like one, write to hello@petrovicistefan.ro or join the early access list: I issue keys by hand for now, and early users get launch pricing, including a limited lifetime license.
I'm Stefan Petrovici: passionate about IT, a husband and a father. I built mcp-security-guard on my own. I'm looking for a job.
I build web applications end to end, frontend, backend, APIs and deployment, and I'm happy to work on anything else that needs building. This repository shows how I work: tests, CI, careful documentation and attention to security.
I also build WordPress and WooCommerce plugins, available at pluginsforstores.com.
If your team is hiring, write to me at hello@petrovicistefan.ro.