Static attack surface analyzer for AI agents, MCP servers, and LLM tool definitions.
Project description
mcpmap
Static attack surface analyzer for MCP servers and LLM tool definitions.
mcpmap scans MCP server configurations and OpenAI-style tool definitions for security risks, mapping every finding to OWASP LLM Top 10 and MITRE ATLAS. Use it in CI/CD pipelines, pre-commit hooks, or as a REST API.
Features
- 24 detection rules across CRITICAL / HIGH / MEDIUM / LOW severity
- Supports Claude Desktop
claude_desktop_config.json, MCP config format, OpenAI tool definitions, and remote HTTP/SSE MCP servers - Entropy-based secret detection: catches hardcoded secrets even when env var names don't look like keys
- Unpinned package detection: flags
npx -y @pkg/namewithout a version pin - Typosquatting detection: edit-distance comparison against trusted publishers
- Adversarial instruction detection: flags prompt-injection patterns embedded in tool descriptions
- Suppression / allow-list via
.mcpmap-ignore: silences known findings without losing other coverage - Baseline / diff mode:
--baselineshows only what changed since the last scan - Context-aware remediation: advice names the exact path, key, or package to fix
- Output formats: Markdown, JSON, HTML (dark-mode report), SARIF (for GitHub Code Scanning)
- REST API (
mcpmap serve) for integration with web tooling --fail-onflag for CI gate enforcement- Custom rules via
--rulesflag
Installation
pip install mcpmap
Or from source:
git clone https://github.com/bogdanticu88/mcp-map
cd mcp-map
pip install -e ".[dev]"
Works on Windows, macOS, and Linux. Requires Python 3.9+.
Set NO_COLOR=1 to disable all colour output.
Quick Start
# Find your Claude Desktop config (cross-platform)
mcpmap find
# Scan a config file
mcpmap scan ~/.config/Claude/claude_desktop_config.json
# Scan a directory for all JSON/YAML configs
mcpmap scan ./configs/
# Show all built-in detection rules
mcpmap rules
# Output an HTML report
mcpmap scan config.json --format html --output report.html
# Fail CI if any HIGH or CRITICAL finding is detected
mcpmap scan config.json --fail-on HIGH
# Show only new findings since the last scan
mcpmap scan config.json --save-baseline baseline.json
mcpmap scan config.json --baseline baseline.json
Claude Desktop Config Location
| OS | Default path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json (or $XDG_CONFIG_HOME/Claude/) |
Run mcpmap find to check all known paths on your current system.
CLI Reference
mcpmap [OPTIONS] COMMAND [ARGS]...
mcpmap scan
mcpmap scan [OPTIONS] TARGETS...
TARGETS can be individual files or directories. Directories are walked recursively for .json, .yaml, and .yml files.
| Option | Description |
|---|---|
--format, -f |
Output format: markdown (default), json, html, sarif |
--output, -o |
Write report to file instead of stdout |
--fail-on |
Exit with code 1 if any finding is at or above this severity (CRITICAL, HIGH, MEDIUM, LOW) |
--summary |
Print terminal summary table only |
--ascii |
ASCII-only output (no emoji/badges) |
--rules |
Path to custom rules YAML; replaces the built-in rule set |
--ignore-file |
Path to a suppression file (default: .mcpmap-ignore in target dir or CWD) |
--no-ignore |
Disable all suppression file loading |
--show-suppressed |
Include suppressed findings in the report |
--baseline |
Path to a previous JSON scan result; marks new findings as NEW |
--save-baseline |
Save current results as a baseline JSON file |
--version, -V |
Show version and exit |
Exit codes
| Code | Meaning |
|---|---|
0 |
No findings at or above the --fail-on threshold (or --fail-on not set) |
1 |
One or more findings at or above the --fail-on severity threshold |
2 |
Invalid --rules or --baseline path, target not found, or output file not writable |
mcpmap rules
mcpmap rules [--rules PATH]
Lists all detection rules in a table (ID, severity, category, name). Pass --rules to preview a custom rules file before using it in a scan.
mcpmap find
mcpmap find
Detects your OS and lists known Claude Desktop config locations, indicating which exist. Prints a ready-to-paste mcpmap scan command for found files.
mcpmap serve
mcpmap serve [OPTIONS]
| Option | Description |
|---|---|
--host |
Bind host (default: 127.0.0.1) |
--port, -p |
Bind port, 1-65535 (default: 8000) |
--rules |
Path to custom rules YAML |
Suppression / Allow-list
Create a .mcpmap-ignore file in your project root or next to the config file:
# Suppress this rule everywhere
MCM-014
# Suppress only for the named server
MCM-010:my-internal-server
# Suppress all findings for a server you've audited
*:legacy-mcp-server
Format per line: RULE_ID or RULE_ID:server_name or *:server_name. Lines starting with # are comments.
mcpmap looks for .mcpmap-ignore adjacent to the first scan target, then in the current working directory. Override with --ignore-file PATH or disable entirely with --no-ignore.
# Scan with a custom suppression file
mcpmap scan config.json --ignore-file security/approved.ignore
# Show suppressed findings alongside active ones
mcpmap scan config.json --show-suppressed
Baseline / Diff Mode
Track what changes between scans. Useful in CI to alert only on regressions.
# Step 1: save the current findings as a baseline
mcpmap scan config.json --save-baseline baseline.json
# Step 2 (later, in CI): compare against it
mcpmap scan config.json --baseline baseline.json
Each finding in the output is labelled:
- NEW: finding did not exist in the baseline
- Resolved: finding was in the baseline but is no longer detected
- Unchanged: present in both (hidden from the diff summary)
Combine with --fail-on to fail CI only when new findings are introduced:
mcpmap scan config.json --baseline baseline.json --fail-on HIGH
REST API
Start the server:
mcpmap serve
POST /analyze
Analyze a config payload.
curl -s http://localhost:8000/analyze \
-H "Content-Type: application/json" \
-d '{
"content": {
"mcpServers": {
"bash": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-bash"],
"env": {"API_KEY": "sk-..."}
}
}
},
"filename": "config.json",
"format": "json"
}'
Request fields:
| Field | Type | Description |
|---|---|---|
content |
object | The config to analyze |
filename |
string | Hint for format detection (default: config.json) |
format |
string | Report format in report field: json, markdown, html, sarif |
fail_on |
string | Optional severity threshold; sets failed: true in response |
Response:
{
"results": [...],
"summary": {"CRITICAL": 2, "HIGH": 1, "MEDIUM": 0, "LOW": 0, "TOTAL": 3},
"failed": true,
"report": "..."
}
GET /rules
List all loaded rules with severity, category, and framework mappings.
GET /health
Returns {"status": "ok", "version": "..."}.
Supported Input Formats
MCP Config (claude_desktop_config.json)
Local process-based server:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem@1.2.3", "/home/user/projects"],
"env": {}
}
}
}
Remote HTTP/SSE server (also scanned):
{
"mcpServers": {
"remote-assistant": {
"url": "https://mcp.example.com/sse",
"headers": {"Authorization": "Bearer sk-..."}
}
}
}
OpenAI Tool Definitions
{
"tools": [
{
"type": "function",
"function": {
"name": "run_bash",
"description": "Execute a bash command",
"parameters": {
"type": "object",
"properties": {"command": {"type": "string"}}
}
}
}
]
}
Rule Set
CRITICAL
| ID | Name | Category |
|---|---|---|
| MCM-001 | Shell / Command Execution Capability | Excessive Agency |
| MCM-002 | Arbitrary Code Execution Capability | Excessive Agency |
| MCM-003 | Credential and Secret Store Access | Sensitive Data Exposure |
| MCM-004 | Process Management Capability | Excessive Agency |
HIGH
| ID | Name | Category |
|---|---|---|
| MCM-005 | Filesystem Write Access | Excessive Agency |
| MCM-006 | Unrestricted Network Fetch / HTTP Request | Data Exfiltration |
| MCM-007 | Email Send Capability | Data Exfiltration |
| MCM-008 | Git Write / Repository Push Access | Supply Chain |
| MCM-009 | Database Write / Delete Access | Excessive Agency |
| MCM-010 | API Key or Token Exposed in Server Environment | Sensitive Data Exposure |
| MCM-011 | Broad Filesystem Access (Root or Home Directory) | Sensitive Data Exposure |
| MCM-012 | Overprivileged MCP Server | Excessive Agency |
| MCM-020 | Unpinned Package Version in Auto-Accept Runner | Supply Chain |
| MCM-021 | Adversarial Instructions Embedded in Tool Description | Prompt Injection Risk |
| MCM-022 | High-Entropy Secret in Server Environment | Sensitive Data Exposure |
| MCM-024 | Package Name Resembles Trusted Publisher (Typosquatting) | Supply Chain |
MEDIUM
| ID | Name | Category |
|---|---|---|
| MCM-013 | Unrestricted Web Browsing / Browser Automation | Prompt Injection Risk |
| MCM-014 | Unverified Third-Party MCP Server | Supply Chain |
| MCM-015 | Tool Missing Description (Tool Confusion Risk) | Tool Confusion |
| MCM-016 | Sensitive Directory Read Access | Sensitive Data Exposure |
| MCM-023 | Remote MCP Server over HTTP/SSE | Supply Chain |
LOW
| ID | Name | Category |
|---|---|---|
| MCM-017 | Calendar Write Access | Data Exfiltration |
| MCM-018 | Social Media Post Capability | Data Exfiltration |
| MCM-019 | Push Notification Send Capability | Data Exfiltration |
Each finding includes:
- OWASP LLM Top 10 reference(s)
- MITRE ATLAS technique reference(s)
- Context-aware remediation guidance (names the exact path, key, or package to fix)
- Evidence string (what matched and where)
Detection techniques
| Rule | Detection method |
|---|---|
| MCM-001 to MCM-019 | Pattern matching on server names, package names, tool names, descriptions, env keys, and filesystem paths |
| MCM-020 | Detects npx/uvx/bunx with -y/--yes and no version-pinned package arg |
| MCM-021 | Matches 20+ known prompt-injection phrase patterns in tool descriptions |
| MCM-022 | Shannon entropy >= 4.5 bits/char on env variable values >= 20 chars (with URL/path exclusions) |
| MCM-023 | Detects servers configured with a url field instead of a local command |
| MCM-024 | Levenshtein distance <= 2 between package scope and trusted publisher names |
Custom Rules
Extend or replace the built-in rules with your own YAML file:
rules:
- id: CUSTOM-001
name: My Custom Rule
description: Detects a dangerous pattern specific to our environment.
severity: HIGH
category: Excessive Agency
owasp_llm:
- id: LLM06
name: "Excessive Agency"
url: "https://genai.owasp.org/llmrisk/llm06-excessive-agency/"
mitre_atlas:
- id: AML.T0051.001
name: "LLM Prompt Injection: Indirect"
url: "https://atlas.mitre.org/techniques/AML.T0051.001"
remediation: Remove or gate this tool behind human confirmation.
detection:
tool_name_patterns:
- my_dangerous_tool
tool_description_keywords:
- dangerous operation
mcpmap scan config.json --rules my_rules.yaml
# Preview which rules would be loaded
mcpmap rules --rules my_rules.yaml
Detection fields
| Field | Type | Description |
|---|---|---|
server_name_patterns |
list[str] | Substring match on server name |
package_patterns |
list[str] | Substring match on package name |
broad_path_patterns |
list[str] | Exact or subdirectory-prefix match on args |
sensitive_path_patterns |
list[str] | Substring match on args |
env_key_patterns |
list[str] | Substring match on env variable keys |
env_value_entropy_threshold |
float | Flag env values with Shannon entropy above this level |
env_value_min_length |
int | Minimum value length for entropy check (default: 20) |
tool_name_patterns |
list[str] | Substring match on tool name |
tool_description_keywords |
list[str] | Substring match on tool description |
description_injection_patterns |
list[str] | Adversarial phrases to detect in tool descriptions |
parameter_names |
list[str] | Substring match on tool parameter names |
check_empty_description |
bool | Flag tools with no description |
check_unpinned_package |
bool | Flag auto-accept runners without a version pin |
check_remote_server |
bool | Flag servers configured with a url field |
check_typosquatting |
bool | Enable edit-distance check against trusted publishers |
typosquatting_distance |
int | Maximum edit distance to flag (default: 2) |
trusted_package_prefixes |
list[str] | Trusted publisher prefixes for supply chain checks |
high_risk_tool_threshold |
int | Number of high-risk findings to trigger MCM-012 |
CI/CD Integration
GitHub Actions
- name: Scan MCP config
run: |
pip install mcpmap
mcpmap scan claude_desktop_config.json --fail-on HIGH --format sarif --output mcpmap.sarif
- name: Upload SARIF to Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: mcpmap.sarif
With baseline diff to fail only on new regressions:
- name: Restore baseline
uses: actions/cache@v4
with:
path: mcpmap-baseline.json
key: mcpmap-baseline-${{ github.base_ref }}
- name: Scan MCP config
run: |
pip install mcpmap
mcpmap scan config.json \
--baseline mcpmap-baseline.json \
--save-baseline mcpmap-baseline.json \
--fail-on HIGH
- name: Save updated baseline
uses: actions/cache@v4
with:
path: mcpmap-baseline.json
key: mcpmap-baseline-${{ github.ref_name }}
Pre-commit Hook
repos:
- repo: local
hooks:
- id: mcpmap
name: mcpmap MCP security scan
entry: mcpmap scan
args: ["--fail-on", "CRITICAL"]
language: python
files: "claude_desktop_config\\.json|mcp.*\\.json"
Docker
docker build -t mcpmap .
docker run --rm -v $(pwd):/work mcpmap scan /work/config.json
Development
pip install -e ".[dev]"
pytest
ruff check mcpmap/
You can also invoke the CLI as a module:
python -m mcpmap scan config.json
python -m mcpmap find
python -m mcpmap rules
Security Framework Mapping
mcpmap findings map to:
- OWASP LLM Top 10: LLM01 Prompt Injection, LLM02 Sensitive Information Disclosure, LLM03 Supply Chain, LLM06 Excessive Agency
- MITRE ATLAS: AML.T0051.000 (Direct Prompt Injection), AML.T0051.001 (Indirect Prompt Injection), AML.T0054 (LLM Jailbreak), AML.T0048 (Societal Harm)
Limitations
mcpmap is a static analyzer. It reads configuration files without executing any code or making network requests.
- It cannot detect runtime misconfigurations or vulnerabilities introduced after the agent starts.
- Entropy-based secret detection (MCM-022) may produce false positives on long random-looking values (e.g., base64-encoded certificates, concatenated UUIDs). Use
.mcpmap-ignoreto suppress confirmed non-secrets. - Typosquatting detection (MCM-024) compares the package scope against a built-in list of trusted publishers. Publishers not in that list are not checked.
- Pattern matching rules (MCM-001 to MCM-019) look for known-dangerous names and capabilities. Novel tools with custom names that wrap dangerous operations are not detected unless they match a known pattern or custom rule.
- mcpmap does not perform sandbox execution, import analysis, or dynamic taint analysis.
License
MIT. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcpmap-1.0.0.tar.gz.
File metadata
- Download URL: mcpmap-1.0.0.tar.gz
- Upload date:
- Size: 50.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91a0705c4c34170953c631d25a5625801a9c2d776dccd58070b0d0440712f659
|
|
| MD5 |
1ef0044970e03994d712e42f2aa0b28c
|
|
| BLAKE2b-256 |
da4ee50175051a001e8f2d06a9c0cf991a639d8de70a8e5d3bde981bd4ff8086
|
File details
Details for the file mcpmap-1.0.0-py3-none-any.whl.
File metadata
- Download URL: mcpmap-1.0.0-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9167f557a30b3a5735d3e945836308d469ec9b3f4f21586c8ad37fd895718483
|
|
| MD5 |
7cd43af9d33e4280b9074d42773a6a22
|
|
| BLAKE2b-256 |
072cbdfa8a4d2743bac814068e08570bb9027c25771f10196c89df0e3b9d19ea
|