mcp-triage
An MCP security scanner. Audit an MCP server before you let an agent near it.
mcp-triage is a command-line security scanner for MCP (Model Context Protocol)
servers. It scans a server for prompt injection in tool descriptions, hardcoded
secrets, dangerous code execution paths, and unsafe defaults — locally, with no
account and no network calls.
Connecting an agent to a Model Context Protocol server hands that server two things at once: code execution on your machine, and a direct line to the model's context. The second one is what makes MCP different from an ordinary dependency.
A tool's description field isn't documentation for humans — it's fed
straight to the LLM as instructions. A server author (or someone who
compromised one) can write a description that tells your agent to do something
you never asked for, and you'd never see it in a code review that only looked
at the implementation. That's a new class of supply-chain risk, and there are
now 20,000+ MCP servers in public directories with almost no
security review between them and your agent.
mcp-triage is a fast, local, dependency-light first pass over any MCP
server — one you didn't write, or one you're about to publish.
Quick start
Audit a server you're thinking about trusting:
pip install mcp-triage
git clone https://github.com/some-org/some-mcp-server
mcp-triage scan ./some-mcp-server
That's the whole workflow. No account, no config file, no network calls — see Runs entirely on your machine.
What it catches
- 🧠 Prompt-injection-prone tool descriptions — phrasing aimed at the calling model rather than at a human reader ("ignore previous instructions", "do not tell the user"), hidden zero-width unicode, and descriptions long enough to bury instructions in. Checked in static JSON manifests and in the source-embedded string literals where real servers actually keep them.
- 🔓 Over-broad capabilities — tools advertising shell/exec/arbitrary file
access, or accepting unvalidated free-form input (
additionalProperties: true). - 💣 Dangerous code paths in the implementation —
eval,subprocess(..., shell=True),os.system,pickle.loads, unsafeyaml.load. - 🔑 Hardcoded secrets — API keys, AWS keys, GitHub/Slack tokens committed into source or config.
- 🌐 Unsafe defaults — binding to
0.0.0.0,trust/skip_authflags left on.
Full rule reference below. Every rule ID is stable — see the rule stability policy before you pin one in CI.
Use it in CI
GitHub Action — no pip install boilerplate:
- uses: YashkantG/mcp-triage@main
with:
path: ./my-mcp-server
fail-on: high
SARIF → GitHub Code Scanning, so findings land in the Security tab instead of scrolling past in a build log:
- uses: YashkantG/mcp-triage@main
with:
path: ./my-mcp-server
format: sarif
upload-sarif: "true"
pre-commit:
repos:
- repo: https://github.com/YashkantG/mcp-triage
rev: v0.3.0
hooks:
- id: mcp-triage
Any other CI — mcp-triage scan . --fail-on high exits non-zero when it
finds something at or above that severity. --format json for machine-readable
output.
Show your posture
If you publish an MCP server, --format badge emits a
shields.io endpoint payload you can
commit and display, so the people evaluating your server can see it was
checked:
mcp-triage scan . --format badge > .github/badges/mcp-security.json
[](https://github.com/YashkantG/mcp-triage)
Grades are deliberately blunt: A clean, B/C medium findings, D/F high findings. The badge at the top of this README is this repo scanning itself, regenerated and verified on every CI run.
Tuning it
A pattern-based scanner will flag things you've already reviewed. Three levers, narrowest first:
Inline, on the offending line:
subprocess.run(cmd, shell=True) # mcp-triage: ignore[MCP102]
subprocess.run(cmd, shell=True) # mcp-triage: ignore ← all rules, this line
Project config — .mcptriage.toml at the scan root:
[ignore]
rules = ["MCP004"] # repo-wide
paths = ["tests/fixtures/**"]
[severity]
MCP003 = "LOW" # downgrade rather than silence
[[custom_rules]] # your own checks, no fork required
id = "CUSTOM001"
pattern = "InternalOnlyApi\\.execute"
message = "Internal-only API called from an MCP tool handler"
severity = "HIGH"
CLI, for one-offs: mcp-triage scan . --ignore-rule MCP004
This repo's own .mcptriage.toml is a worked example.
Runs entirely on your machine
mcp-triage makes zero network calls. It doesn't phone home, doesn't
upload your code, and has no telemetry. Runtime dependencies are typer,
rich, and tomli (Python < 3.11 only).
Releases publish through PyPI Trusted Publishing (OIDC — no long-lived tokens), so every release traces back to the GitHub Actions run that built it. If your security team needs to approve a new tool: read the source, then run it with no network access at all. It doesn't need any.
Reporting a vulnerability — in this tool, or one you found with it — see SECURITY.md.
What the MCP ecosystem actually looks like
research/ecosystem_scan.py surveys public MCP servers harvested from the
curated awesome-lists. Across a seeded sample of 141 successfully scanned
repositories (from a pool of 3,701):
| Repositories with at least one finding | 41% |
| Most widespread: dangerous code sinks (MCP101/102) | 34 and 30 repos |
| Hardcoded secrets (MCP201) | 13 repos |
| Prompt injection (MCP001) | 1 repo |
Read those numbers with the caveats they deserve, because they cost something to learn:
- They are unverified automated output, not audited vulnerabilities. The
first pass of this survey reported 37% and 931 HIGH findings — then
inspection showed most were fake credentials in test fixtures,
evalin benchmark harnesses, andpattern.exec(line)(the JavaScript RegExp API, which alone accounted for 77% of the code-execution hits). Those became precision fixes, not a blog post. - MCP001 firing once in 141 repos is the honest headline. Deliberate tool poisoning is an adversarial attack, and public repositories are mostly written by people acting in good faith. The rule exists for the server you didn't expect to be hostile — not because the ecosystem is full of them.
- No repository is named here. If this tool finds something real in someone else's server, disclose it to them privately.
Reproduce it yourself: python research/ecosystem_scan.py --sample-size 150.
Rules
| ID | Check |
|---|---|
| MCP001 | Prompt injection / tool poisoning in tool description |
| MCP002 | Hidden/invisible unicode characters in tool description |
| MCP003 | Suspiciously long tool description (payload smuggling risk) |
| MCP004 | Over-broad capability exposed by tool name/description |
| MCP005 | Tool schema accepts arbitrary/unvalidated input |
| MCP101 | Dangerous code execution sink (eval, exec, new Function) |
| MCP102 | Shell command built from untrusted input |
| MCP103 | Unsafe deserialization (pickle.loads, unsafe yaml.load) |
| MCP201 | Hardcoded secret or credential |
| MCP301 | Server bound to all network interfaces |
| MCP302 | Authentication / trust check disabled |
Plus any [[custom_rules]] you define.
Design
Deliberately pattern/regex-based rather than a full taint-tracking analyser. That's a real tradeoff, stated plainly:
- You get: sub-second scans, a rule set you can read end-to-end in one sitting, no compilation or language runtime per target, trivial extensibility.
- You give up: certainty. It cannot tell you whether attacker-controlled
data actually reaches a
shell=Truecall. It is a first pass that tells you where a human should look — not a proof of safety, and it makes no soundness claim.
Anyone selling you a scanner that claims to be complete is selling you something else.
Contributing
Issues and PRs welcome, especially new rules, more language coverage (Python / JS / TS today), and real-world servers that break it. See CONTRIBUTING.md for setup and the fixture-pair pattern every rule follows, or pick up a good first issue.
False positives are treated as real bugs — report them.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_triage-0.4.0.tar.gz.
File metadata
- Download URL: mcp_triage-0.4.0.tar.gz
- Upload date:
- Size: 30.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b3e92f80a4970aa2ab83cb2535d535e3f5942fc6ec10709e70c378822a18d90d
|
|
| MD5 |
9a9029128739dbd51e0321f0ed376148
|
|
| BLAKE2b-256 |
8647c81f2368f5d3f33171ebf7d355df869e48cef423361ed035136dc1764e49
|
Provenance
The following attestation bundles were made for mcp_triage-0.4.0.tar.gz:
Publisher:
publish.yml on YashkantG/mcp-triage
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_triage-0.4.0.tar.gz -
Subject digest:
b3e92f80a4970aa2ab83cb2535d535e3f5942fc6ec10709e70c378822a18d90d - Sigstore transparency entry: 2740161226
- Sigstore integration time:
-
Permalink:
YashkantG/mcp-triage@9c1304b2fd0dbdc90ea59758f97e1c15c6d35a6b -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/YashkantG
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9c1304b2fd0dbdc90ea59758f97e1c15c6d35a6b -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcp_triage-0.4.0-py3-none-any.whl.
File metadata
- Download URL: mcp_triage-0.4.0-py3-none-any.whl
- Upload date:
- Size: 25.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a64846e2e569f469237507cf222b5b6831f2f5e30c7be334f6c55c992bcd7402
|
|
| MD5 |
ea3681dfcb798afff2a877b2be9d11ca
|
|
| BLAKE2b-256 |
ccd32e36574b9f914c9833f07299f667d28caecf53712447c3e4bd26448aafaf
|
Provenance
The following attestation bundles were made for mcp_triage-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on YashkantG/mcp-triage
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_triage-0.4.0-py3-none-any.whl -
Subject digest:
a64846e2e569f469237507cf222b5b6831f2f5e30c7be334f6c55c992bcd7402 - Sigstore transparency entry: 2740161525
- Sigstore integration time:
-
Permalink:
YashkantG/mcp-triage@9c1304b2fd0dbdc90ea59758f97e1c15c6d35a6b -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/YashkantG
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9c1304b2fd0dbdc90ea59758f97e1c15c6d35a6b -
Trigger Event:
release
-
Statement type: