Skip to main content

Security scanner for Model Context Protocol (MCP) servers

Project description

mcp-bandit

Beta — install via pip install mcp-bandit. API and rule set may change before the 0.1.0 stable release.

Security scanner for Model Context Protocol (MCP) servers. Detects vulnerabilities via static AST analysis of Python source code and dynamic probing of live MCP servers.

37% of public MCP servers have SSRF vulnerabilities and there's no equivalent of npm audit for the MCP ecosystem. mcp-bandit fills that gap.

Installation

pip install mcp-bandit
# or
uv add mcp-bandit

Requires Python 3.11+.

Note: Until v0.1.0 stable ships to PyPI, install from source:

git clone https://github.com/giridharpandurangi/mcp-scan
cd mcp-scan
pip install -e .

Quickstart

# Scan a Python MCP server for vulnerabilities
mcp-bandit static --path my_server.py

# Scan a whole directory, fail on HIGH+ findings
mcp-bandit static --path src/ --severity HIGH --format sarif

Detection Rules

Rule Category Severity Description
MCP001 SSRF HIGH Tainted URL passed to httpx/requests HTTP client
MCP002 SSRF LOW HTTP client call missing timeout argument
MCP003 SSRF HIGH Tainted URL passed to urllib.request.urlopen
MCP004 Command Injection CRITICAL Tainted argument passed to subprocess with shell=True
MCP005 Path Traversal HIGH Tainted path passed to open()
MCP010 Secrets CRITICAL Hardcoded API key, token, or secret in source code
MCP011 Secrets CRITICAL Hardcoded password in source code
MCP012 Auth MEDIUM Insecure http:// URL used in auth/API context
MCP013 Auth HIGH OAuth authorization URL missing PKCE code_challenge
MCP014 Secrets HIGH Credential variable passed to print() or logging
MCP020 Prompt Injection HIGH Tool description contains non-Latin-1 Unicode (> U+00FF)
MCP021 Prompt Injection HIGH Tool description contains prompt injection phrases

Usage

Static analysis

Scan a Python source file or directory:

mcp-bandit static --path src/
mcp-bandit static --path my_server.py

Dynamic probing

Connect to a running MCP server and send crafted probe inputs:

# stdio transport (command string)
mcp-bandit dynamic --target "python my_server.py"

# HTTP transport
mcp-bandit dynamic --target "http://localhost:8080"

Warning: Dynamic probing sends crafted inputs to live MCP tools. Always run against a development or staging server, never production.

Both modes combined

mcp-bandit all --path src/ --target "python my_server.py"

List all rules

mcp-bandit rules

CLI Options

Shared flags

Flag Description
--format / -f Output format: rich (default), json, sarif, markdown
--output / -o Write output to a file instead of stdout
--severity / -s Minimum severity to report: CRITICAL, HIGH, MEDIUM, LOW, INFO
--rule / -r Run only a specific rule (e.g. MCP001)
--config / -c Path to a TOML or JSON configuration file

Dynamic-only flags

Flag Description
--allow-destructive Send probes to tools with destructive names (delete*, drop*, write*, etc.)
--yes / -y Skip the --allow-destructive confirmation prompt
--ssrf-listener Address of a controlled HTTP listener for confirmed SSRF detection

Exit codes

Code Meaning
0 Scan completed, no findings at or above HIGH severity
1 Scan completed, one or more HIGH+ findings found
2 Scan failed due to configuration or runtime error

Output Formats

# Rich terminal table (default)
mcp-bandit static --path src/

# JSON (ScanResult schema)
mcp-bandit static --path src/ --format json

# SARIF 2.1.0 (for GitHub Code Scanning)
mcp-bandit static --path src/ --format sarif --output results.sarif

# Markdown report
mcp-bandit static --path src/ --format markdown --output report.md

Configuration File

Create a mcp-bandit.toml (or .json) file to set persistent options:

# mcp-bandit.toml
disabled_rules = ["MCP002"]
exclude_paths = ["tests/**", "docs/**"]
min_severity = "MEDIUM"
output_format = "rich"
trusted_validators = ["validate_url", "is_safe_path"]

Or embed it in pyproject.toml:

[tool.mcp-scan]
disabled_rules = ["MCP002"]
min_severity = "HIGH"

Load it with:

mcp-bandit static --path src/ --config mcp-bandit.toml

CLI flags always override config file values.

trusted_validators

The taint tracker is intra-procedural — it can't follow calls into helper functions. If you have a custom sanitizer (e.g. validate_url(x)), register its name to suppress false positives:

trusted_validators = ["validate_url", "is_safe_path", "check_host"]

GitHub Action

Add mcp-bandit to your CI pipeline:

# .github/workflows/security.yml
- name: Run mcp-bandit
  uses: ./.github/actions/mcp-scan
  with:
    path: src/
    severity: HIGH
    format: sarif

The action uploads SARIF output as a workflow artifact and fails the step when HIGH+ findings are found.

Python API

from mcp_scan import Scanner, Finding, ScanResult, Severity

scanner = Scanner()

# Static analysis
result: ScanResult = scanner.scan_static("src/")

# Dynamic probing
import asyncio
result = asyncio.run(scanner.scan_dynamic("python my_server.py"))

# Filter findings
high_plus = [f for f in result.findings if f.severity >= Severity.HIGH]

Configuration via API

from mcp_scan.models import ScanConfig

config = ScanConfig(
    disabled_rules=["MCP002"],
    min_severity=Severity.MEDIUM,
    exclude_paths=["tests/**"],
    trusted_validators=["validate_url"],
)
scanner = Scanner(config=config)

Development

# Install with dev dependencies
uv sync --extra dev

# Run tests
pytest

# Run tests with coverage
pytest --cov=mcp_scan --cov-fail-under=80

# Run a specific test file
pytest tests/test_rules_ssrf.py -v

The test suite includes unit tests, snapshot tests for all formatters, and property-based tests (via Hypothesis) covering:

  • ScanResult JSON round-trip fidelity
  • Static analysis determinism
  • No false positives on clean files (SSRF, secrets, injection rules)
  • Severity filter monotonicity
  • All rules applied to every analyzed file
  • Taint sanitization suppresses findings

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_bandit-0.1.1.tar.gz (13.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_bandit-0.1.1-py3-none-any.whl (39.6 kB view details)

Uploaded Python 3

File details

Details for the file mcp_bandit-0.1.1.tar.gz.

File metadata

  • Download URL: mcp_bandit-0.1.1.tar.gz
  • Upload date:
  • Size: 13.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for mcp_bandit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6278b1c6b31343db7be6e5ead58383a613ff019ea1766e6ac5b6cae70d59dba4
MD5 15e5a99be559ce424fa5a9a6a9cf5349
BLAKE2b-256 3653898ee52665e4bbfe37f012ddda97969dfd5695314e001719707e1f614f92

See more details on using hashes here.

File details

Details for the file mcp_bandit-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: mcp_bandit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 39.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for mcp_bandit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 156298291c45569d96820ec53758c9858c2db987a945933658dd4bfa501a4d51
MD5 66b1edd9b6a5b28b23620edbc56a7dbb
BLAKE2b-256 e3410015721c63958369f72e6e7cfedb74bbe2a9a7846b7c8e367386d69d8de3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page