mcp-security-scanner
Static security scanner for MCP servers and AI agent skills.
Detects excessive permissions (unrestricted shell, unsanitized SQL), risky dependencies, and malicious patterns. Emits SARIF v2.1.0 for CI integration.
Install
Requires Python 3.13.
Install the published package from PyPI:
pip install mcp-skill-scanner
Opt into semantic detection with the [semantic] extra:
pip install mcp-skill-scanner[semantic]
Or install from source with uv:
git clone https://github.com/A-Djihadi/mcp-security-scanner.git
cd mcp-security-scanner
uv sync
Usage
# Scan a directory, write SARIF to a file (default format)
uv run mcpsec scan . --output results.sarif
# Human-readable output
uv run mcpsec scan . --format text
# Only error-level findings
uv run mcpsec scan . --severity error
# Only specific rules
uv run mcpsec scan . --rules MCP-shell-unrestricted,DEP-unpinned
# Exclude a rule
uv run mcpsec scan . --exclude-rule SKL-shell-execution
# Skip paths (globs, repeatable), e.g. to scan a repo that contains test fixtures
uv run mcpsec scan . --exclude tests/fixtures
# Network-backed rules (OSV.dev, PyPI/npm, reachability) -- opt-in
uv run mcpsec scan . --online
Options
| Flag | Description |
|---|---|
--format sarif|text |
Output format (default sarif). |
--output FILE |
Write output to FILE instead of stdout. |
--severity error|warning|note |
Only report findings at/above this severity. |
--rules IDS |
Comma-separated rule IDs to run; all others are disabled. |
--exclude-rule IDS |
Comma-separated rule IDs to disable. |
--exclude GLOB |
Skip paths matching GLOB (relative to the scan root, matches parent directories too; repeatable). |
--online |
Enable network-backed rules (DEP-known-vulnerability, DEP-outdated, DEP-unknown, MCP-unreachable). Only http(s) is fetched; private, loopback and link-local addresses are never probed. |
--fail-on error|warning|note |
Minimum severity that fails the scan (default error). |
--semantic |
Enable semantic malicious-intent detection via the local embedding model (requires the [semantic] extra). |
Semantic detection (optional)
--semantic opts into embedding-based malicious-intent detection for skills
(SKL-semantic-malicious). It is off by default: the base install stays
PyYAML-only, and a default scan never loads the model runtime.
# Opt into the heavier install once:
uv sync --extra semantic # or: pip install mcp-skill-scanner[semantic]
# Then scan with semantic detection enabled:
uv run mcpsec scan . --semantic
The default embedder is a small, CPU-only ONNX sentence-transformer:
- Model:
all-MiniLM-L6-v2(ONNX exportXenova/all-MiniLM-L6-v2), Apache-2.0. - Pinned revision:
751bff37182d3f1213fa05d7196b954e230abad9(files verified by SHA-256). - Corpus: a curated, versioned set of malicious-intent anchors
(
src/mcpsec/corpus.py, version1); the model revision and corpus version are recorded on everySKL-semantic-maliciousfinding for reproducibility.
To verify: the similarity threshold (default 0.85) and the corpus anchor
coverage are calibrated against synthetic probes, not a production corpus; they
should be re-tuned against real-world skill data before relying on this rule as
a CI gate.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Scan completed; no findings at or above --fail-on. |
| 1 | Scan completed; one or more findings at or above --fail-on. |
| 2 | Internal error (bad args, unreadable path, parse error). |
Rules
| Rule ID | Level | Detects |
|---|---|---|
MCP-shell-unrestricted |
error | Server command is a shell interpreter (sh, bash, zsh, dash, ksh, fish, csh, powershell, pwsh, cmd…), including behind a launcher (env bash, sudo sh). |
MCP-allow-all |
error | args contains --allow-all / --allow-everything / -A. |
MCP-broad-network |
error | --allow-net with no host restriction (bare flag or empty value). |
MCP-inline-code |
warning | Interpreter runs code passed on the command line (python -c, node -e, ruby -e, perl -e, php -r). |
MCP-insecure-transport |
error | Remote MCP server uses plaintext http://. |
MCP-header-secret |
warning | A secret is stored inline in a remote server's headers. |
MCP-remote-no-auth |
note | Remote MCP server has no auth-bearing header. |
MCP-parse-ambiguous |
note | Server command cannot be statically resolved from the config. |
MCP-unreachable |
note | Remote endpoint refused the connection (--online). |
FLOW-shell-injection |
error | Tainted MCP tool input reaches a shell execution sink (Python taint analysis). |
FLOW-sql-injection |
error | Tainted input reaches a SQL query sink. |
FLOW-code-eval |
warning | Tainted input reaches eval / exec / compile. |
MCP-broad-filesystem |
warning | --allow-read / --allow-write at a root-level path (/, ~, C:\, C:/) or with no path. |
SKL-shell-execution |
warning | Skill body invokes a shell with interpolated input (f-string / concatenation). |
SKL-remote-code-exec |
error | Skill body pipes remote content to a shell (curl … | sh, wget … | bash, eval(fetch(…))). |
SKL-obfuscated-command |
warning | Shell command decodes a base64 payload (base64 -d / --decode). |
SKL-sql-concatenation |
error | SQL built by string concatenation/interpolation; parameterized ? queries do not trigger. |
DEP-unpinned |
warning | A direct dependency whose version spec is unpinned (*, latest, ^, ~, empty). |
DEP-known-vulnerability |
error | Declared version has a known vulnerability in OSV.dev (--online). |
DEP-outdated |
warning | Declared version is behind the latest published one (--online). |
DEP-unknown |
warning | Name closely resembles a popular package, a typosquat signal (--online). |
SKL-semantic-malicious |
warning | Skill prose is semantically similar to a known-malicious reference (opt-in via --semantic). |
LLM triage (optional)
mcpsec triage runs the same scan, then explains each finding and proposes a fix with a
local model (Ollama). It is advisory: findings, levels and the exit code are never
changed by the model (ADR-0011).
uv sync --extra agent # LangGraph + Langfuse
ollama pull qwen2.5-coder:3b
uv run mcpsec triage . --exclude tests/fixtures # text report
uv run mcpsec triage . --format sarif --output triage.sarif # SARIF + properties.triage
How it works: a LangGraph graph fans out one branch per finding (Send); each branch loads
the file excerpt, asks the model for explanation, fix and evidence, and keeps the answer
only if the evidence line really exists in the file (one stricter retry, then a rule-based
explanation). The excerpt is passed as untrusted data, so instructions hidden in a scanned
skill cannot change the result. Set LANGFUSE_BASE_URL, LANGFUSE_PUBLIC_KEY and
LANGFUSE_SECRET_KEY to trace every node, model call (tokens) and grounded score in a
self-hosted Langfuse.
CI integration
The scanner is deterministic and offline, so it fits a CI gate. This repository
ships its own GitHub Actions workflow —
.github/workflows/ci.yml — which lints (ruff), runs the
test suite, scans this repository with mcpsec itself and uploads the SARIF to the
Security tab, and runs a gitleaks secrets scan on every push and pull request.
To gate another project on mcpsec (actions pinned by commit SHA, as in this repo):
jobs:
mcpsec:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required to upload SARIF
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
- name: Scan for risky MCP configs and skills
run: uvx --from git+https://github.com/A-Djihadi/mcp-security-scanner mcpsec scan . --output results.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
sarif_file: results.sarif
uvx --from git+… installs mcpsec in an isolated environment (pin it with @<tag or sha>
in real use); uv run mcpsec only works inside this repository.
The default --fail-on error makes mcpsec exit non-zero when an error-level
finding exists, which blocks the build.
License
MIT © 2026 Djihadi Ahamdy.
Metadata
Release files for mcp-skill-scanner 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 | Provenance | Actions |
|---|---|---|---|---|
| mcp_skill_scanner-0.2.0.tar.gz | 51.5 kB | With provenance | Details for mcp_skill_scanner-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mcp_skill_scanner-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for mcp_skill_scanner-0.2.0-py3-none-any.whl |
Total release size: 117.6 kB
Release files / mcp_skill_scanner-0.2.0.tar.gz
| Download URL | mcp_skill_scanner-0.2.0.tar.gz |
|---|---|
| Size | 51.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f5b32b79f4eb83eb0caa530a00bd50077a2c5f4b372595b89e7664d67176e55
|
|
BLAKE2b-256 checksum How to use checksums |
cf4066041606c4a91380f7c7d4575d4af67b1553faac2174faf61774ac1331a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logRelease files / mcp_skill_scanner-0.2.0-py3-none-any.whl
| Download URL | mcp_skill_scanner-0.2.0-py3-none-any.whl |
|---|---|
| Size | 66.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
03776be6249f79de9b5087fa6f10c122c146c7c0add7c4ea64f37eb335acee32
|
|
BLAKE2b-256 checksum How to use checksums |
d27c9f210e2c291f19b568766f385c42cd333423f9e7652c617a74b06e3d96d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logSecurity for mcp-skill-scanner 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for mcp_skill_scanner-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.