mcplint
Local-first, CI-native security scanner for MCP servers. OWASP MCP Top 10 rules, a lockfile for rug-pull detection, and an AIBOM export. It never executes your MCP servers.
uvx mcplint-sec scan
PyPI distribution is
mcplint-sec(themcplintname collides with an existing project); it installs themcplintcommand. Withuvx, invoke it by distribution name:uvx mcplint-sec ….
Why
MCP went from a few hundred servers to a 10,000+ server ecosystem — and the security model did not keep up:
- ~40% of internet-exposed MCP servers have no authentication (Censys, 2026)
- A single compromised MCP server reaches a 78% attack success rate when five servers share one agent (arXiv 2601.17549)
CVE-2025-6514inmcp-remote(CVSS 9.6) affected a package with 437k+ downloads- Registries accepted typosquatted MCP servers; tool descriptions are mutable after approval ("rug pulls")
Most scanners run on your machine and hand your tool descriptions to a vendor API. mcplint scans the configs in your repo, in CI, with nothing leaving your environment.
Quickstart
# scan the current repo
uvx mcplint-sec scan
# also scan user-level client configs and skills
uvx mcplint-sec scan --home
# pin server fingerprints, detect drift in CI
uvx mcplint-sec lock
uvx mcplint-sec lock --check
# CycloneDX AIBOM of every MCP server
uvx mcplint-sec inventory -o aibom.json
# probe a RUNNING gateway for missing authentication (read-only, loopback by default)
uvx mcplint-sec gate
uvx mcplint-sec gate https://gateway.internal:4000 --allow-host
# what do the rules mean?
uvx mcplint-sec rules list
uvx mcplint-sec rules explain MCP004
# scaffold a GitHub Actions workflow + starter config
uvx mcplint-sec init
Exit code is 1 when a finding at --fail-on severity (default high)
exists — drop it into CI as-is.
What it scans
| Input | Examples |
|---|---|
| MCP configs | .mcp.json, mcp.json, .cursor/mcp.json, .vscode/mcp.json, opencode.json[c], .codex/config.toml, cline_mcp_settings.json, ~/.claude.json, ~/.codeium/windsurf/mcp_config.json, ~/.gemini/settings.json |
| Instruction / skill files | SKILL.md, AGENTS.md, CLAUDE.md, .cursorrules, .windsurfrules, .github/copilot-instructions.md, .cursor/rules/*.mdc |
Scans recurse into subdirectories (node_modules, virtualenvs and build
outputs are skipped), so monorepos work out of the box.
Rules
| Rule | Severity | OWASP MCP Top 10 | Checks |
|---|---|---|---|
| MCP001 | critical | MCP01 Token Mismanagement | hardcoded secrets in env/args/headers |
| MCP002 | medium | MCP01 | unsafe config file permissions |
| MCP003 | medium | MCP04 Supply Chain | unpinned npx/uvx/pipx packages |
| MCP004 | high | MCP04 Supply Chain | typosquat/lookalike package names |
| MCP005 | high | MCP07 Auth | remote endpoint over plain http:// |
| MCP006 | medium | MCP07 Auth | remote endpoint with no auth material |
| MCP007 | medium | MCP02 Scope Creep | filesystem server scoped to /, $HOME, ... |
| MCP008 | high | MCP05 Command Execution | shell / command-execution servers |
| MCP009 | high | MCP03 Tool Poisoning / MCP06 Intent Flow Subversion | prompt-injection indicators in instructions |
| MCP010 | critical | MCP03 Tool Poisoning / MCP06 Intent Flow Subversion | zero-width / bidi unicode (hidden text) |
| MCP011 | medium | MCP03 Tool Poisoning | cross-config server name shadowing |
| MCP012 | high | MCP03 Tool Poisoning | lockfile drift (rug-pull detection) |
| MCP013 | high | MCP04 Supply Chain | pinned packages matching OSV advisories (--online) |
Rules are data: plain YAML in src/mcplint/rules_data/.
Bring your own with --rules-dir ./my-rules.
Coverage: the 13 rules map to 7 of the 10 OWASP MCP Top 10 categories; MCP08 (audit & telemetry), MCP09 (shadow servers) and MCP10 (context over-sharing) are runtime and operational risks outside the reach of static config scanning.
Runtime gate
Static rules can tell you a config looks right. mcplint gate tells you
whether a gateway that is already running actually enforces
authentication. It sends one small, read-only request per known failure class —
derived from public CVEs and advisories — and checks that every one of them is
denied.
- Read-only. No tool calls, no state changes: the battery only asks "does this endpoint reject anonymous callers?".
- Loopback by default. Anything that is not
localhost/127.0.0.1requires--allow-host(confirming the gateway is yours). - Rules as data. Profiles live in
src/mcplint/gate_data/; bring your own with--profiles-dir.
uvx mcplint-sec gate # http://localhost:4000
uvx mcplint-sec gate https://gateway.internal # + --allow-host
uvx mcplint-sec gate --json --fail-on high # CI-friendly
| Probe | Severity | Derived from | Checks |
|---|---|---|---|
| GATE001 | critical | CVE-2026-59822 | MCP /mcp accepts a fabricated Authorization bearer |
| GATE002 | critical | CVE-2026-59822 | MCP /mcp accepts an invalid x-litellm-api-key |
| GATE003 | high | — | MCP /mcp answers anonymous callers at all |
| GATE004 | high | — | Legacy /sse endpoint answers anonymous callers |
| GATE005 | high | CVE-2026-42271 | /mcp-rest/test/connection reachable without credentials |
| GATE006 | high | — | MCP management API reachable without credentials |
| GATE007 | medium | CVE-2026-49468 | Management route authenticates from a spoofed Host header |
Exit codes: 0 clean, 1 finding at --fail-on severity or above, 2
operational error (bad profile, unreachable target, non-loopback target
without --allow-host). Add it to your deploy pipeline and re-run it after
every gateway upgrade.
Lab-verified against real LiteLLM releases: patched 1.100.0 denies every
probe; pre-fix 1.83.14 errors instead of denying on the MCP routes —
see research/gate-lab-verification.md.
GitHub Actions
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: dtduc-git/mcplint@v0.1.2
with:
fail-on: high
Findings show up as annotations and in the repo's code-scanning tab (SARIF).
Or scaffold this workflow and a starter config with uvx mcplint-sec init.
Design principles
- Never executes your MCP servers. Scanning is static by default; running
arbitrary server commands in CI is not acceptable. The one active command is
gate: read-only HTTP probes against a target you own, no tool calls. - Nothing leaves your machine unless you opt in with
--online(OSV CVE lookups only) or explicitly pointgateat a remote host. - Pin and diff.
.mcplint.lock.jsonfingerprints every server (salted hashes for env values) so post-approval changes are visible ingit diff. - Rules as data. YAML + a small, tested check engine — contributions do not need to touch the scanner core.
- Non-goals: no gateway, no proxy, no runtime traffic monitoring, no SaaS.
Research
State of MCP configs in the wild — an
aggregate scan of 1,210 public MCP configs from 1,197 repositories (56.5% have
at least one finding). Methodology and the reproducible script
(scripts/ecosystem_scan.py) are included.
Read the write-up: We scanned 1,210 MCP configs on GitHub. 56% have a security finding.
Development
uv sync --all-groups
uv run pytest
uv run ruff check .
uv run mcplint scan fixtures/vulnerable-repo --fail-on none
License
Apache-2.0
Release files for mcplint-sec 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcplint_sec-0.2.0.tar.gz | 78.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcplint_sec-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 126.8 kB
Release files / mcplint_sec-0.2.0.tar.gz
| Download URL | mcplint_sec-0.2.0.tar.gz |
|---|---|
| Size | 78.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b96d5249132428e4fc0c34ee7318e5e077fb51303d285ccda1e263ce24cedbf0
|
|
BLAKE2b-256 checksum How to use checksums |
af1549255bfc33093241e8a685987502714fa7aa8e6869d91ae03436894bbad9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / mcplint_sec-0.2.0-py3-none-any.whl
| Download URL | mcplint_sec-0.2.0-py3-none-any.whl |
|---|---|
| Size | 47.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b0dfee73d6a9fb226f9bce3919a0e61f042882c4149212c78301e88af9f1838
|
|
BLAKE2b-256 checksum How to use checksums |
db0d0ca5e181c851c3c57c67f7e7148a4515b3c7750590dbe06c0575a9ffa31f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|