Skip to main content

MCP Guard

Security scanner for MCP servers — audit capabilities, detect risks, generate security reports.

CI Docker License Python Release

Why

The MCP ecosystem exploded (67,000+ servers in 18 months), but security hasn't kept up:

  • 87% of MCP servers fail high-trust thresholds
  • 72% expose sensitive capabilities unnecessarily
  • 53% rely on static API keys

MCP Guard helps you audit MCP servers before connecting agents to them.

Install

mcp-guard is not published on PyPI. Install it directly from the repository:

pip install git+https://github.com/yunaremaia/mcp-guard.git

Requires Python 3.10 or newer.

Alternatively, run it without installing via Docker:

docker run --rm -v "$PWD:/work" ghcr.io/yunaremaia/mcp-guard:latest scan /work/my-mcp-server

Quick Start

Get started in 5 minutes:

# Scan an MCP server directory
mcp-guard scan ./my-mcp-server

# Scan a specific config file
mcp-guard scan ./mcp.json

# Output as JSON
mcp-guard scan ./my-mcp-server --format json

# Output as SARIF (for GitHub Code Scanning)
mcp-guard scan ./my-mcp-server --format sarif --output results.sarif

# Fail CI if HIGH or CRITICAL findings
mcp-guard scan ./my-mcp-server --fail-on high

# Audit every MCP server installed as an npm dependency under node_modules
mcp-guard scan ./my-project --npm --format json

# Enforce security deny rules from a YAML config
mcp-guard scan ./my-mcp-server --config policy.yaml --deny

# Deny specific servers or tools on the fly
mcp-guard scan ./my-mcp-server --deny-server "untrusted-*" --deny

# Add Little Canary's structural filter to prompt injection checks
# (requires: pip install "mcp-guard[canary] @ git+https://github.com/yunaremaia/mcp-guard.git")
mcp-guard scan ./my-mcp-server --strict-injection

# Verify npm supply chain (sigstore attestations / provenance)
mcp-guard verify @modelcontextprotocol/server-memory

# Enforce a strict policy: fail if the package is unsigned (no provenance)
mcp-guard verify @scope/mcp-server@1.2.0 --policy strict --format json

# Show server info without scanning
mcp-guard info ./my-mcp-server

npm package scanning

MCP servers are commonly published to npm (@modelcontextprotocol/server-*, @mcp/*, mcp-*) and ship an mcp.json (or another known config name) next to their code. Point scan at a project root with --npm to audit the MCP surface a project pulls in:

# Report the MCP-related dependencies in package.json and scan every MCP
# config found under node_modules/ in JSON
mcp-guard scan ./my-project --npm --format json

# Emit a multi-run SARIF document for GitHub Code Scanning
mcp-guard scan ./my-project --npm --format sarif --output results.sarif

# Fail CI if any installed server trips a deny rule
mcp-guard scan ./my-project --npm --deny-server "untrusted-*" --deny

With --npm, the path is treated as a project root rather than a single manifest:

  • MCP-related dependencies are listed from package.json (dependencies, devDependencies, optionalDependencies).
  • Every mcp.json / mcp.config.json / .mcp.json / server.json under node_modules/ is scanned; the owning npm package is attributed to each result.
  • A manifest that cannot be parsed is skipped and reported, not fatal.
  • --deny, --fail-on, --config and the JSON / SARIF / CLI output formats all apply to the aggregated result set.

Discovery is filesystem-only: it never runs npm or node, never executes a discovered manifest, and makes no network call (the same offline posture as the supply-chain verifier).

Supported config formats

mcp-guard scan <file> (and MCPParser.from_file) auto-detects the format from the file extension:

  • .json — standard JSON manifest; the default.
  • .yaml / .yml — YAML manifest (parsed with a safe loader, no code execution).
  • .js — a JavaScript object-literal config (module.exports = { ... } or export default { ... }). The body is read as a JSON-compatible object literal and is never executed, so executable JS bodies are not supported. Keys and string values must use double quotes, as in JSON.

Directories (the scan ./my-mcp-server form) still look for a known JSON config file such as mcp.json; pass a YAML or .js file directly to use those formats.

Deny Rules & Policy Enforcement

You can configure deny rules in a YAML file (e.g. policy.yaml or default mcp-guard.yaml). Deny rules support exact matches, wildcards (*), and tool-level scoping:

# Security Policy Configuration
deny:
  # Block unverified or untrusted servers
  servers:
    - "malicious-server"
    - "github-*"
  # Block high-risk tools (exact name, wildcard, or scoped to server)
  tools:
    - "github/delete_repo"
    - "filesystem/write"
    - "sys_*"

Use the --deny flag to fail with exit code 1 whenever any denied server or tool is detected:

mcp-guard scan ./my-mcp-server --config policy.yaml --deny

Supply Chain Verification

mcp-guard verify checks whether an npm-distributed MCP server was published with sigstore attestations (provenance / SLSA). It queries the npm registry for the attestations attached to a package version and reports whether the artifact you are about to run was built with provenance:

# Unscoped, scoped, pinned or floating version references all work
mcp-guard verify some-mcp-server
mcp-guard verify @scope/some-mcp-server@1.2.0

# Machine-readable output for CI pipelines
mcp-guard verify @scope/some-mcp-server --format json

# Strict policy: exit code 1 when the version is unsigned
mcp-guard verify @scope/some-mcp-server --policy strict

Statuses:

status meaning
signed sigstore attestations found (and whether one is a SLSA provenance statement)
unsigned the version exists but no attestations were published with it
not_found the package does not exist on the registry
registry_error the registry could not be reached or answered unexpectedly

Exit codes: 0 when verified signed (or in report mode); 1 when the package is unsigned under --policy strict, when it is not found, or on registry errors — ready to gate CI jobs.

Notes:

  • No new runtime dependencies: the verifier uses only the standard library (urllib), and the network layer is injectable, so the test suite stays fully offline.
  • Verification currently covers npm packages. PyPI (PEP 740 attestations), Docker (cosign / SBOM) and reproducible-build checks are tracked as follow-ups in #74.

What It Detects

Rule Level Description
MCP001 HIGH Write operation without authentication
MCP002 CRITICAL Destructive operation without authentication
MCP003 MEDIUM Capability with excessive permissions
MCP004 LOW Capability without description
MCP005 MEDIUM Write capability without corresponding read
MCP006 HIGH Destructive operation without confirmation
MCP007 MEDIUM / HIGH / CRITICAL Explicitly disabled authentication ('auth': false)
MCP008 LOW / MEDIUM / HIGH Possible prompt injection in server or capability metadata
MCP009 HIGH Command execution without authentication
DENY001 CRITICAL Server matches security policy deny rule
DENY002 CRITICAL Tool capability matches security policy deny rule

Authentication Detection Semantics

mcp-guard validates that capability authentication fields contain truthy configuration rather than mere key presence:

  • Auth Required: Detected when auth, authorization, or security is present with a truthy value (true, configuration object/dict, non-empty string, or non-empty list).
  • Auth Explicitly Disabled: Flagged when auth or authorization is set to false or "disabled". Capabilities explicitly disabling authentication trigger rule MCP007 and cannot bypass MCP001 (write) or MCP002 (destructive) checks.
  • No Auth Field / Falsy: Falsy values like null, "", 0, or {} are treated as unauthenticated.
  • SARIF Integration: SARIF 2.1.0 output records properties.auth_status as "required", "disabled", or "unknown" for each capability finding.

Prompt Injection Detection (MCP008)

MCP clients pass server and tool metadata to the model, so instructions hidden there act like part of the prompt. MCP008 checks the server name and description, every capability name and description, and every key and string value in inputSchema (parameter names, descriptions, titles, enum/default values, nested objects, array items, anyOf/$defs). $ref is not resolved; local definitions are scanned where they appear.

  • HIGH: direct injection phrasing, e.g. "ignore previous instructions", "override safety checks", "you are now ...", "DAN mode", chat-template tokens such as <|im_start|>, "do not tell the user".
  • MEDIUM: suspicious phrasing with legitimate uses, e.g. a bare "jailbreak", <IMPORTANT> tags, a line starting with system:, directions to read files such as ~/.ssh/id_rsa, and invisible Unicode (zero-width, bidi, tag characters). A HIGH pattern inside quotes (for example a security tool describing an attack) is reported as MEDIUM with quoted: "true", never suppressed.
  • LOW: a field longer than 65,536 characters or a schema nested deeper than 64 levels; the remainder is not scanned.

Text is normalized before matching: invisible characters are removed, Unicode tag characters are decoded to the ASCII they hide, and full-width lookalikes are folded (NFKC).

--strict-injection also runs Little Canary's StructuralFilter. It adds a broader signature set and decodes base64/hex/ROT13 payloads before re-checking them. It is deterministic pattern matching. It makes no model, API or network calls and needs the optional canary extra. Strict mode finds more and has more false positives (for example sudo or "developer mode" in a shell tool's description).

To keep scan time linear, the filter sees each field in overlapping 2,000-character windows (200-character overlap). A Little Canary match longer than 200 characters that crosses a window boundary can be missed. Base64 and hex tokens longer than 200 characters are decoded whole by mcp-guard instead, and the decoded text is checked by both detectors. A long token nested inside a decoded token is not decoded again. Cue words that enable ROT13 and reversed-text decoding ("rot13", "reverse", ...) are passed to every window of the field. ROT13 or reversed text longer than 200 characters can still be split across windows.

These are pattern matches, not proof of intent, and they do not catch rephrased or novel injections. On the hand-written benign fixture in tests/fixtures/benign_tools.json (121 tools), the default mode flags 5 tools (4.13%, all MEDIUM) and strict mode flags 6 (4.96%). Those rates describe that fixture only.

In JSON and SARIF output each MCP008 finding has injection_type (e.g. instruction-override, safety-bypass, role-hijack, jailbreak, fake-authority, concealment, prompt-extraction, exfiltration, hidden-characters, encoded-payload), metadata_field (e.g. inputSchema.properties.path.description), detector (mcp-guard or little-canary), evidence and quoted properties.

Command Execution Detection (MCP009)

"Runs arbitrary code" is a different trust boundary from "mutates data" (is_write) and "loses data" (is_destructive), so command execution gets its own classification: a shell tool neither writes nor destroys, and MCP001's "performs write operations" text would be false in its output. MCP009 fires at HIGH when a capability with no authentication matches the execution family — exec, execute, eval, spawn, shell, bash, cmd, command, subprocess, popen, terminal — as whole identifier segments or inflected description words, exactly like MCP001/MCP002. Read-verb suppression applies, so get_exec_summary and get_command_history stay unflagged, while get_and_exec would still match (and marks a second operation). run and evaluate are deliberately excluded: measured against this repo's own 121-tool corpus, they add read-shaped false positives (run_query, evaluate_model) without catching any execution name the shell/process nouns miss.

Known residuals, accepted on purpose: execute_workflow and execute_sql flag (executing a caller-supplied workflow or SQL string is running arbitrary logic), and a description like "List the available commands" on a neutral name can trip the phrase match — the same description-phrase artifact MCP001/MCP002 have. The rule is not coupled into MCP005 (a shell has no meaningful "corresponding read") or MCP006 (a confirmation field does not make an unauthenticated shell safe; authentication does).

Example Output

MCP Server
test-server v1.0.0
A test MCP server

Risk Score: CRITICAL

Summary
Total Capabilities    3
Critical              1
High                  1
Medium                0
Low                   0

Findings
Rule      Level       Capability          Message                              Suggestion
MCP002    CRITICAL    delete_database     Destructive operation without auth    Add authentication
MCP001    HIGH        create_user         Write operation without auth         Add authentication

CI Integration

GitHub Action

name: MCP Security Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install git+https://github.com/yunaremaia/mcp-guard.git
      - run: mcp-guard scan ./mcp-server --format sarif --output results.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

If this tool is useful to you, a star helps other people find it.

Part of a family of focused, single-purpose developer tools — each one does one thing and does it well.

License

MIT

Sponsoring / Treasury

This project is MIT licensed and free to use. If you want to support its maintenance, you can sponsor on GitHub or contribute to the development treasury wallet on Solana:

Trust boundary

Treat MCP tool responses as untrusted input until validated by your agent policy.

Metadata

Release files for mcp-guard-py 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-guard-py 0.3.1
File Size Uploaded Provenance
mcp_guard_py-0.3.1.tar.gz 85.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-guard-py 0.3.1
File Interpreter ABI Platform
mcp_guard_py-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 131.8 kB

Release files / mcp_guard_py-0.3.1.tar.gz

Download URL mcp_guard_py-0.3.1.tar.gz
Size 85.1 kB
Tags Source
SHA-256 checksum
How to use checksums
32d4f4186d9c1cf92e9f1640d4082989952057e8bd5a7f8660abc459d0a2fa73
BLAKE2b-256 checksum
How to use checksums
b42900c1ce2c8df264619344ccca5cc21c41ead26ff3881254f4d7cede84c9cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 19, 2026.

Transparency log

Release files / mcp_guard_py-0.3.1-py3-none-any.whl

Download URL mcp_guard_py-0.3.1-py3-none-any.whl
Size 46.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
27e6223968590929035a655192a1cc141a6b48b2c964402c9a2ad9f4d697836d
BLAKE2b-256 checksum
How to use checksums
7febdad78a685ca2fdd13dc46595c8ecc394bb4d5739522acf53a29f1c20d7de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 19, 2026.

Transparency log

Security for mcp-guard-py 0.3.1

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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_guard_py-0.3.1.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 Oct 19, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page