Skip to content

Commit 1d880aa

Browse files
nishadeborahphilipsdebbiephilips-00aarora79
authored
Add secure scanning of MCP Servers (#184)
* change v0 -> v0.1 * v0 -> v0.1 doc and test changes * using a constant variable * api version to the improt script * removed version from readme.md * replace v0 to ANTHROPIC_API_VERSION in md * fixed the syntax error * changed name back to v0 * reverting release notes change * changed v0.1 to {ANTHROPIC_API_VERSION} * move v0.1 -> v0 * v0.1 -> v0 * more updates for uniformity * docker changes * updated readme.md * updated docs/anthropic-registry-import.md * updated anthropic_registry_spi.md * updated design docs * changed {REGISTRY_CONSTANTS.ANTHROPIC_API_VERSION} to v0.1 in design docs * removed date and issue number * renamed v0_routes -> registry_routes * renamed test_v0_routes to test_registry_routes * refactored * changed import name from v0_router to registry_router * updates to md file * changed {{API_VERSION}} to {{{ANTHROPIC_API_VERSION}} * cisco security scanner * added llm to default analyzer * setting MCP_SCANNER_LLM_API_KEY to env variable * reverting llm change * both yara and llm * added #Security scans to gitignore * checking for placeholder value * Add security scanning enhancements and workflow improvements - Add scan_all_servers.py CLI tool for bulk security scanning - Supports --token and --token-file parameters with priority handling - Generates comprehensive markdown reports with detailed findings - Reports saved to security_scans/scan_report.md (latest) and security_scans/reports/ (timestamped archives) - Masks tokens in logs for security (shows first 20 and last 10 chars) - Enhance service_mgmt.sh security scan workflow - Auto-append /mcp to proxy_pass_url if not ending with /mcp or /sse - Load ADMIN_PASSWORD from .env file for auto-disabling unsafe servers - Fix authentication header forwarding in auth-server - Update auth_server/server.py - Add fallback to check Authorization header if X-Authorization not present - Explicit priority: X-Authorization > Authorization - Add mcp_security_scanner.py header support - Parse --headers argument and extract Bearer token - Pass token to mcp-scanner via --bearer-token - Add example configs - shawndurrani-ai-server-config.json for external MCP server - Update .gitignore - Add .roo/ for Roo IDE files * Update security scanner documentation Changes: - Renamed docs/cisco-security-scanner-setup.md to docs/security-scanner.md - Rewrote documentation to be generic (not Cisco-specific) - Added MCP Supply Chain Security introduction - Documented integration with Cisco AI Defence MCP Scanner - Section 1: Security scanning during server addition - Command format and examples - Real config example (cloudflare-docs-server-config.json) - Real scan output example (docs.mcp.cloudflare.com_mcp.json) - Explained disabled state and security-pending tag - Added placeholder for screenshot - Section 2: Periodic registry scans - Command examples for scan_all_servers.py - Report location and structure (security_scans/scan_report.md) - Reference to scan_report_example.md - Updated README.md: - Added security scanning to "What's New" section - Added "Security Scanning" subsection to Enterprise Features - Removed unnecessary prerequisites: - MCP Scanner install (already in pyproject.toml) - Registry admin credentials (handled by .env) - Removed redundant troubleshooting section Files changed: - docs/cisco-security-scanner-setup.md → docs/security-scanner.md - README.md (What's New + Enterprise Features sections) - cli/examples/cloudflare-docs-server-config.json (new example) - docs/scan_report_example.md (new reference report) * Fix health checks and tool fetching for Cloudflare and streamable-http servers This commit fixes multiple issues with health checks and automatic tool discovery: 1. Health Check - Proper MCP Session Management - Add proper MCP initialize flow to get session ID from server - Use server-generated session ID for subsequent ping requests - Skip URL pattern shortcut when supported_transports contains streamable-http - Handle auth failures during initialize by falling back to ping without auth 2. Tool Fetching - Header and URL Fixes - Add required Accept header: application/json, text/event-stream - Remove trailing slash from MCP URLs (Cloudflare rejects it) - Fix MCP client to properly handle Cloudflare's requirements 3. Tool Auto-Discovery - Enhanced Logic - Always fetch tools on first health check (previous_status == UNKNOWN) - Fetch tools when server transitions to healthy - Fetch tools if server is healthy but has empty tool_list - Ensures tools populate automatically on startup and registration 4. Import Script - Preserve Transport Type - Stop removing supported_transports field during import - Allows SSE servers to be registered with correct transport type - Fixes health checks for servers like ai.shawndurrani-mcp-merchant Fixes Cloudflare Documentation MCP Server health checks and tool discovery. Fixes sre-gateway showing unhealthy when auth token expires. * Update security scanner documentation with screenshot reference Replace placeholder text with actual screenshot reference for failed security scan. Shows how servers that fail security scans are added in disabled state with security-pending tag. * Replace specific domain with example.com in security scanner docs Use mcpgateway.example.com instead of mcpgateway.ddns.net for better documentation practices with a generic example domain. * Render example report summary as markdown instead of code block Remove markdown code block wrapper from example report summary to display it as rendered markdown for better readability. * Remove redundant installation note from prerequisites The statement about MCP Scanner being included in pyproject.toml is unnecessary in the Prerequisites section. --------- Co-authored-by: Nisha Deborah Philips <[email protected]> Co-authored-by: Amit Arora <[email protected]>
1 parent 68259e8 commit 1d880aa

17 files changed

+2740
-49
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ SMITHERY_API_KEY=your_smithery_api_key_here
130130
# Get this from https://console.anthropic.com/
131131
ANTHROPIC_API_KEY=your_anthropic_api_key_here
132132

133+
# MCP Security Scanner LLM API Key (optional - only needed for LLM-based security analysis)
134+
# Default analyzer is YARA (no API key required)
135+
# To use LLM analyzer: ./cli/service_mgmt.sh add config.json yara,llm
136+
# Get OpenAI API key from https://platform.openai.com/api-keys
137+
MCP_SCANNER_LLM_API_KEY=your_openai_api_key_here
138+
133139
# =============================================================================
134140
# CONTAINER REGISTRY CREDENTIALS (for CI/CD and local builds)
135141
# =============================================================================

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ cookies.txt
191191
# Scratchpad for temporary notes and planning
192192
.scratchpad/
193193

194+
# Roo IDE files
195+
.roo/
196+
194197
# OAuth tokens and credentials - never commit these!
195198
.oauth-tokens/
196199
.agentcore-params
@@ -281,4 +284,7 @@ coverage/
281284

282285
# Anthropic registry temporary files
283286
anthropic_servers_*.json
284-
curated_import_list.txt
287+
curated_import_list.txt
288+
289+
#Security scans
290+
security_scans/

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ The **MCP Gateway & Registry** is an enterprise-ready platform that centralizes
8282

8383
## What's New
8484

85+
- **🔒 MCP Server Security Scanning** - Integrated vulnerability scanning with Cisco AI Defence MCP Scanner. Automatic security scans during server registration, periodic registry-wide scans with detailed markdown reports, and automatic disabling of servers with security issues.
8586
- **📥 Import Servers from Anthropic MCP Registry** - Import curated MCP servers from Anthropic's registry with a single command. [Import Guide](docs/anthropic-registry-import.md)
8687
- **🔌 Anthropic MCP Registry REST API Compatibility** - Full compatibility with Anthropic's MCP Registry REST API specification. [API Documentation](docs/anthropic_registry_api.md)
8788
- **🚀 Pre-built Images** - Deploy instantly with pre-built Docker images. [Get Started](#option-a-pre-built-images-instant-setup) | [macOS Guide](docs/macos-setup-guide.md)
@@ -373,6 +374,14 @@ Seamlessly integrate with Anthropic's official MCP Registry to import and access
373374

374375
[Import Guide](docs/anthropic-registry-import.md) | [Registry API Documentation](docs/anthropic_registry_api.md)
375376

377+
### Security Scanning
378+
379+
**Integrated Vulnerability Detection:**
380+
- **Automated Security Scanning** - Integrated vulnerability scanning for MCP servers using Cisco AI Defence MCP Scanner, with automatic scans during registration and support for periodic registry-wide scans
381+
- **Detailed Security Reports** - Comprehensive markdown reports with vulnerability details, severity assessments, and remediation recommendations
382+
- **Automatic Protection** - Servers with security issues are automatically disabled with security-pending status to protect your infrastructure
383+
- **Compliance Ready** - Security audit trails and vulnerability tracking for enterprise compliance requirements
384+
376385
### Authentication & Authorization
377386

378387
**Multiple Identity Modes:**

auth_server/server.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,11 @@ async def validate_request(request: Request):
846846

847847
try:
848848
# Extract headers
849+
# Check for X-Authorization first (custom header used by this gateway)
850+
# Only if X-Authorization is not present, check standard Authorization header
849851
authorization = request.headers.get("X-Authorization")
852+
if not authorization:
853+
authorization = request.headers.get("Authorization")
850854
cookie_header = request.headers.get("Cookie", "")
851855
user_pool_id = request.headers.get("X-User-Pool-Id")
852856
client_id = request.headers.get("X-Client-Id")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"server_name": "Cloudflare Documentation MCP Server",
3+
"description": "Search Cloudflare documentation and get migration guides",
4+
"path": "/cloudflare-docs",
5+
"proxy_pass_url": "https://docs.mcp.cloudflare.com/mcp",
6+
"supported_transports": ["streamable-http"]
7+
}

cli/examples/minimal-server-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"server_name": "Minimal MCP Server",
33
"description": "A minimal server configuration with only required fields",
44
"path": "/minimal-server",
5-
"proxy_pass_url": "http://minimal-server:9001/"
5+
"proxy_pass_url": "http://minimal-server:9001/",
6+
"supported_transports": ["streamable-http"]
67
}

cli/import_from_anthropic_registry.sh

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
# and registers them with the local MCP Gateway Registry.
77
#
88
# Usage:
9-
# ./import_from_anthropic_registry.sh [--dry-run] [--import-list <file>]
9+
# ./import_from_anthropic_registry.sh [--dry-run] [--import-list <file>] [--analyzers <analyzers>]
1010
#
1111
# Environment Variables:
1212
# GATEWAY_URL - Gateway URL (default: http://localhost)
1313
# Example: export GATEWAY_URL=https://mcpgateway.ddns.net
14+
# MCP_SCANNER_LLM_API_KEY - API key for LLM-based security analysis (required if using llm analyzer)
1415
#
1516

1617
set -e
@@ -87,17 +88,37 @@ validate_package() {
8788
# Parse arguments
8889
DRY_RUN=false
8990
IMPORT_LIST="$SCRIPT_DIR/import_server_list.txt"
91+
ANALYZERS="yara"
9092

9193
while [[ $# -gt 0 ]]; do
9294
case $1 in
9395
--dry-run) DRY_RUN=true; shift ;;
9496
--import-list) IMPORT_LIST="$2"; shift 2 ;;
97+
--analyzers) ANALYZERS="$2"; shift 2 ;;
9598
--help)
96-
echo "Usage: $0 [--dry-run] [--import-list <file>]"
99+
echo "Usage: $0 [--dry-run] [--import-list <file>] [--analyzers <analyzers>]"
100+
echo ""
101+
echo "Options:"
102+
echo " --dry-run Dry run mode (don't register servers)"
103+
echo " --import-list <file> Server list file (default: import_server_list.txt)"
104+
echo " --analyzers <list> Security analyzers: yara, llm, or yara,llm (default: yara)"
97105
echo ""
98106
echo "Environment Variables:"
99107
echo " GATEWAY_URL - Gateway URL (default: http://localhost)"
100108
echo " Example: export GATEWAY_URL=https://mcpgateway.ddns.net"
109+
echo " MCP_SCANNER_LLM_API_KEY - API key for LLM analyzer (required if using llm)"
110+
echo ""
111+
echo "Examples:"
112+
echo " # Import with default YARA analyzer"
113+
echo " $0"
114+
echo ""
115+
echo " # Import with both YARA and LLM analyzers"
116+
echo " export MCP_SCANNER_LLM_API_KEY=sk-..."
117+
echo " $0 --analyzers yara,llm"
118+
echo ""
119+
echo " # Import with only LLM analyzer"
120+
echo " export MCP_SCANNER_LLM_API_KEY=sk-..."
121+
echo " $0 --analyzers llm"
101122
exit 0 ;;
102123
*) echo "Unknown option: $1"; exit 1 ;;
103124
esac
@@ -108,6 +129,21 @@ command -v jq >/dev/null || { print_error "jq required"; exit 1; }
108129
command -v curl >/dev/null || { print_error "curl required"; exit 1; }
109130
[ -f "$IMPORT_LIST" ] || { print_error "Import list not found: $IMPORT_LIST"; exit 1; }
110131

132+
# Check if LLM analyzer is requested and API key is available
133+
if [[ "$ANALYZERS" == *"llm"* ]]; then
134+
if [ -z "$MCP_SCANNER_LLM_API_KEY" ] || [[ "$MCP_SCANNER_LLM_API_KEY" == *"your_"* ]] || [[ "$MCP_SCANNER_LLM_API_KEY" == *"placeholder"* ]]; then
135+
echo ""
136+
print_error "LLM analyzer requested but MCP_SCANNER_LLM_API_KEY is not configured"
137+
print_info "Current value: ${MCP_SCANNER_LLM_API_KEY:-<not set>}"
138+
print_info ""
139+
print_info "Options:"
140+
print_info " 1. Add real API key to .env file: MCP_SCANNER_LLM_API_KEY=sk-..."
141+
print_info " 2. Set environment variable: export MCP_SCANNER_LLM_API_KEY=sk-..."
142+
print_info " 3. Use only YARA analyzer: $0 --analyzers yara"
143+
exit 1
144+
fi
145+
fi
146+
111147
mkdir -p "$TEMP_DIR"
112148

113149
# Read server list
@@ -119,6 +155,7 @@ while IFS= read -r line; do
119155
done < "$IMPORT_LIST"
120156

121157
print_info "Found ${#servers[@]} servers to import"
158+
print_info "Security analyzers: $ANALYZERS"
122159

123160
# Process each server
124161
success_count=0
@@ -192,10 +229,9 @@ result['path'] = '/$safe_path'
192229
193230
# Remove unsupported fields for register_service tool
194231
# The user-facing register_service tool only supports basic fields
195-
# Note: auth_type, auth_provider, and headers are now kept for proper auth handling
232+
# Note: auth_type, auth_provider, headers, supported_transports, and tool_list are kept
196233
unsupported_fields = [
197-
'repository_url', 'website_url', 'package_npm', 'remote_url',
198-
'supported_transports', 'tool_list'
234+
'repository_url', 'website_url', 'package_npm', 'remote_url'
199235
]
200236
for field in unsupported_fields:
201237
result.pop(field, None)
@@ -209,14 +245,14 @@ with open('$config_file', 'w') as f:
209245

210246
# Register with service_mgmt.sh (if not dry run)
211247
if [ "$DRY_RUN" = false ]; then
212-
if GATEWAY_URL="$GATEWAY_URL" "$SCRIPT_DIR/service_mgmt.sh" add "$config_file"; then
248+
if GATEWAY_URL="$GATEWAY_URL" "$SCRIPT_DIR/service_mgmt.sh" add "$config_file" "$ANALYZERS"; then
213249
print_success "Registered $server_name"
214250
success_count=$((success_count + 1))
215251
else
216252
print_error "Failed to register $server_name"
217253
fi
218254
else
219-
print_info "[DRY RUN] Would register $server_name"
255+
print_info "[DRY RUN] Would register $server_name with analyzers: $ANALYZERS"
220256
success_count=$((success_count + 1))
221257
fi
222258

0 commit comments

Comments
 (0)