Skip to main content

MCP-Scanner

PyPI version Python versions License: MIT Tests

Scan MCP server configs and source code for risky exposures before your agent connects to them.

This week, researchers documented MONST3R-AI: a criminal toolkit built on stolen API keys and more than 800 fake AI-skill and MCP repositories. Malicious MCP servers are not a theory anymore. They are a distribution channel.

You would not install a random npm package without looking at it. Your agent connects to MCP servers with far less scrutiny, and an MCP server runs on your machine, with your credentials, able to offer your agent any tool it likes. A tool that runs shell commands. A tool that reads any file. A server that exfiltrates through tools that look boring on their own.

MCP-Scanner is a fast, dependency-light static check you run before you trust a server, and in CI after you do.

Quickstart (30 seconds)

# No install (uv)
uvx --from runtime-mcp-scanner mcp-scan path/to/config.json

# Or with pipx
pipx install runtime-mcp-scanner
mcp-scan path/to/server.py

# Or plain pip
pip install runtime-mcp-scanner

Point it at client configs (Claude Desktop, Cursor, VS Code, Claude Code), Python servers (FastMCP and the low-level SDK), or TypeScript servers (@modelcontextprotocol/sdk). Directories are scanned recursively. The exit code is nonzero when any CRITICAL finding exists, so it drops straight into CI.

Try it on the intentionally vulnerable examples in this repo:

git clone https://github.com/AdamDiStefanoAI/mcp-scanner
cd mcp-scanner
mcp-scan examples/vulnerable_server.py    # 15 findings, exit 1
mcp-scan examples/vulnerable_config.json  # 6 findings, exit 1
mcp-scan examples/safe_server.py          # clean, exit 0

Sample output

Full output of mcp-scan examples/vulnerable_server.py
MCP-Scanner v0.1.0

CRITICAL (3)
  [MCP-001] examples/vulnerable_server.py:20
  Shell execution exposed
  Evidence: subprocess.run(command, shell=True, capture_output=True, text=True)
  Why it matters: A tool that runs shell commands gives anyone who can steer the agent, including
  content injected into its context, arbitrary code execution on the host machine.
  Fix: Remove shell execution from the tool surface. Replace it with a fixed allowlist of specific
  operations that take validated, typed arguments.

  [MCP-002] examples/vulnerable_server.py:26
  Exfiltration path (network fetch plus file write)
  Evidence: server combines network fetch with local file writes
  Why it matters: One server that can both read local files or fetch network resources and write or
  send data out is a complete exfiltration channel. Prompt injection only has to convince the agent
  to use tools that already exist.
  Fix: Split network access and file writes into separate servers with least privilege, and put an
  egress allowlist in front of any fetch tool.

  [MCP-001] examples/vulnerable_server.py:63
  Shell execution exposed
  Evidence: subprocess.run(f"pip install {package}", shell=True, capture_output=True)
  Why it matters: A tool that runs shell commands gives anyone who can steer the agent, including
  content injected into its context, arbitrary code execution on the host machine.
  Fix: Remove shell execution from the tool surface. Replace it with a fixed allowlist of specific
  operations that take validated, typed arguments.

HIGH (7)
  [MCP-003] examples/vulnerable_server.py:27
  Unscoped filesystem access
  Evidence: open(dest, "w")
  Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
  the server process can reach: home directories, SSH keys, cloud credentials, other projects.
  Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
  (resolve the path, then check the prefix).

  [MCP-003] examples/vulnerable_server.py:34
  Unscoped filesystem access
  Evidence: open(file_path)
  Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
  the server process can reach: home directories, SSH keys, cloud credentials, other projects.
  Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
  (resolve the path, then check the prefix).

  [MCP-004] examples/vulnerable_server.py:40
  Secret access
  Evidence: os.environ[var_name]
  Why it matters: Tools or configs that expose environment variables, credential files, or the cloud
  metadata endpoint hand live secrets to the model context, where they can be logged, leaked, or
  exfiltrated.
  Fix: Never expose raw environment or credential stores as tool output. Scope credentials per
  server, inject only what that server needs, and block the link-local metadata address at the
  network layer.

  [MCP-004] examples/vulnerable_server.py:45
  Secret access
  Evidence: ~/.ssh/id_rsa
  Why it matters: Tools or configs that expose environment variables, credential files, or the cloud
  metadata endpoint hand live secrets to the model context, where they can be logged, leaked, or
  exfiltrated.
  Fix: Never expose raw environment or credential stores as tool output. Scope credentials per
  server, inject only what that server needs, and block the link-local metadata address at the
  network layer.

  [MCP-010] examples/vulnerable_server.py:58
  Dynamic code execution
  Evidence: eval(code)
  Why it matters: eval, exec, and dynamic imports inside a tool handler turn any string the model
  produces into running code, bypassing every other control in the server.
  Fix: Remove dynamic evaluation from tool handlers. Replace it with a dispatch table of named,
  audited operations.

  [MCP-003] examples/vulnerable_server.py:70
  Unscoped filesystem access
  Evidence: open(config_path)
  Why it matters: A file tool that takes a raw path from tool input can read or overwrite anything
  the server process can reach: home directories, SSH keys, cloud credentials, other projects.
  Fix: Pin file tools to an explicit root directory and reject any resolved path that escapes it
  (resolve the path, then check the prefix).

  [MCP-005] examples/vulnerable_server.py:75
  Network transport without authentication
  Evidence: network transport started with no authentication check in file
  Why it matters: An MCP server listening on HTTP or SSE with no authentication lets anyone who can
  reach the port call its tools directly, no agent required.
  Fix: Require an Authorization header or token check on every request, bind to localhost when
  remote access is not needed, and put the server behind a gateway that authenticates callers.

MEDIUM (5)
  [MCP-007] examples/vulnerable_server.py:18
  Overbroad tool description
  Evidence: description of exec_shell: "anything"
  Why it matters: Tool descriptions are instructions to the model. Descriptions that advertise
  unrestricted capability ("run any command", "access all files") teach the agent to use the tool as
  a general escape hatch and are a hallmark of malicious MCP packages.
  Fix: Describe exactly what the tool does, what it may touch, and what it must never do. Treat
  description text as part of your attack surface.

  [MCP-007] examples/vulnerable_server.py:33
  Overbroad tool description
  Evidence: description of read_file: "any file"
  Why it matters: Tool descriptions are instructions to the model. Descriptions that advertise
  unrestricted capability ("run any command", "access all files") teach the agent to use the tool as
  a general escape hatch and are a hallmark of malicious MCP packages.
  Fix: Describe exactly what the tool does, what it may touch, and what it must never do. Treat
  description text as part of your attack surface.

  [MCP-006] examples/vulnerable_server.py:50
  Mutating tool with no input schema
  Evidence: tool delete_data mutates state but its parameters have no type/schema annotations
  Why it matters: A write, delete, or execute tool with an empty input schema accepts arbitrary
  arguments with no validation layer between the model and the action.
  Fix: Give every mutating tool a strict input schema with typed, bounded fields, and validate
  arguments server-side before acting on them.

  [MCP-008] examples/vulnerable_server.py:63
  Self-modifying server
  Evidence: pip install
  Why it matters: A tool that can rewrite MCP client configs or install new packages can persist
  itself, add new malicious servers, and survive removal of the original package.
  Fix: MCP servers must never write to client configuration files or invoke package managers. Keep
  config changes manual and reviewable.

  [MCP-008] examples/vulnerable_server.py:69
  Self-modifying server
  Evidence: /home/user/.config/claude/claude_desktop_config.json
  Why it matters: A tool that can rewrite MCP client configs or install new packages can persist
  itself, add new malicious servers, and survive removal of the original package.
  Fix: MCP servers must never write to client configuration files or invoke package managers. Keep
  config changes manual and reviewable.

Summary: 1 file(s) scanned, 1 server(s) found. Findings: 3 critical, 7 high, 5 medium, 0 low, 0 info.

What it checks

Rule Severity What it catches
MCP-001 CRITICAL Shell execution exposed: subprocess, os.system, child_process, or a config whose command is a shell
MCP-002 CRITICAL Exfiltration path: one server that can both fetch the network and write files
MCP-003 HIGH Unscoped filesystem access: file tools driven by raw input paths with no root or allowlist check
MCP-004 HIGH Secret access: environment variables exposed as tools, credential file reads, the 169.254.169.254 metadata endpoint, plaintext secrets in config env
MCP-005 HIGH Network transport without authentication: HTTP/SSE servers or config url servers with no auth headers
MCP-006 MEDIUM Mutating tool with no input schema: write/delete/exec tools whose arguments are untyped or undeclared
MCP-007 MEDIUM Overbroad tool description: "run any command", "access all files", and other escape-hatch language aimed at the model
MCP-008 MEDIUM Self-modifying server: tools that rewrite MCP client configs or run pip install / npm install
MCP-009 HIGH Package provenance: unpinned or latest auto-installs via npx -y / uvx, and package names within edit distance 2 of well-known MCP packages (typosquats)
MCP-010 HIGH Dynamic code execution: eval, exec, __import__, new Function inside a tool handler

Rules are data, not code: severities, explanations, fixes, and detection hints live in src/mcp_scan/rules.yaml. Point --rules at your own copy to tune them.

Output formats

mcp-scan server.py                    # grouped terminal report
mcp-scan server.py --format json      # machine-readable findings
mcp-scan server.py --format sarif     # SARIF 2.1.0 for GitHub code scanning

GitHub Action

Fail the build when a scanned server has a CRITICAL finding:

name: mcp-security
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: AdamDiStefanoAI/MCP-Scanner@v0.1.1
        with:
          targets: "mcp/ examples/"

For SARIF upload to GitHub code scanning, run the CLI directly:

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install runtime-mcp-scanner
      - run: mcp-scan servers/ --format sarif > results.sarif
        continue-on-error: true
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

What a risky config looks like

{
  "mcpServers": {
    "helper": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesytem"]
    },
    "shell": {
      "command": "bash",
      "args": ["-lc", "run the bridge"]
    },
    "docs": {
      "url": "http://docs.internal:8000/sse"
    }
  }
}

That config gets flagged for a shell-wrapped server (MCP-001), an unpinned auto-install and a one-character typosquat of server-filesystem (MCP-009), and a remote server with no auth headers (MCP-005). The full version, with a plaintext database password for good measure, is in examples/vulnerable_config.json.

Honest limits

MCP-Scanner is a heuristic static check, not a proof of safety. It flags visible risk patterns in code and config; it does not sandbox servers, trace data flow across files, or catch a server that behaves during review and turns later. Run it as one layer: before install, in CI, and whenever a config changes. A clean scan means "nothing obviously dangerous," which is a much better starting point than "nobody looked."

Contributing

Issues and pull requests are welcome. New rules are the highest-value contribution: if a malicious or sloppy MCP pattern is circulating, open an issue with a sample. See CONTRIBUTING.md.

License

MIT. See LICENSE.

Built by Adam DiStefano, creator of the ACR Standard for runtime control of AI agents. Companion tool: AI-Key-Scanner.

Metadata

Release files for runtime-mcp-scanner 0.1.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 runtime-mcp-scanner 0.1.1
File Size Uploaded Provenance
runtime_mcp_scanner-0.1.1.tar.gz 17.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for runtime-mcp-scanner 0.1.1
File Interpreter ABI Platform
runtime_mcp_scanner-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 24.2 kB

Release files / runtime_mcp_scanner-0.1.1.tar.gz

Download URL runtime_mcp_scanner-0.1.1.tar.gz
Size 17.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a9901652e452e9ca490757a344b8c3cd30fa50fcc8bbf1ec7713219f6a5d71f2
BLAKE2b-256 checksum
How to use checksums
85703b38449bc45070e4cdf3b3a4ff62b75bba558c983cc1066c9f514367a5f4
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 20, 2026.

Transparency log

Release files / runtime_mcp_scanner-0.1.1-py3-none-any.whl

Download URL runtime_mcp_scanner-0.1.1-py3-none-any.whl
Size 7.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef8d18d5ffec87bd077f8c4a28b58645e367fdaabfb676e3fc5f3bab4460d18e
BLAKE2b-256 checksum
How to use checksums
2ef8376df66717d6e7100881ed6348383aa198ee819d467ebb8fab86da69707c
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 20, 2026.

Transparency log

Security for runtime-mcp-scanner 0.1.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 runtime_mcp_scanner-0.1.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 20, 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.1.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