Skip to main content
Glama

MCP CLI Command Server

by jbmurphy
CLAUDE.md12.7 kB
# CLAUDE.md This file provides guidance to Claude Code when working with the MCP CLI Command Server. ## Project Overview MCP CLI Command Server is a Model Context Protocol (MCP) server that provides safe, controlled access to common network and system CLI commands. This server allows AI assistants to execute whitelisted commands like nmap, ping, dig, and curl programmatically with strict security controls. ## Architecture **Language:** Python 3.11 **Framework:** Flask + MCP Python SDK **Transport:** HTTP (JSON-RPC 2.0) on port 3017 **Pattern:** Whitelist-based command execution with subprocess ### Key Files - `src/http_server.py` - Main server implementation - Flask HTTP endpoints for MCP protocol - 10 whitelisted CLI tools with security validation - Command execution with subprocess - Input validation and sanitization - `Dockerfile` - Container with CLI tools (nmap, ping, curl, dig, etc.) - `docker-compose.yml` - Service configuration with security hardening - `.env` - Environment variables - `requirements.txt` - Python dependencies (flask, mcp, httpx) ## Tools Provided ### Network Diagnostic Tools (4 tools) - `cli_ping` - Ping hosts (max 10 packets, 30s timeout) - `cli_traceroute` - Trace network route (max 64 hops, 60s timeout) - `cli_mtr` - Combined ping/traceroute diagnostic (max 20 cycles, 60s timeout) - `cli_netcat_test` - Port connectivity test (10s timeout) ### Network Scanning Tools (2 tools) - `cli_nmap_ping_scan` - Host discovery (300s timeout) - `cli_nmap_port_scan` - Port scanning with service detection (600s timeout) ### DNS Tools (3 tools) - `cli_dig` - DNS lookups (A, AAAA, MX, TXT, etc., 30s timeout) - `cli_host` - Quick DNS lookups (10s timeout) - `cli_whois` - WHOIS domain/IP lookups (30s timeout) ### HTTP Tools (1 tool) - `cli_curl_get` - HTTP GET requests (max 30s timeout) ## Common Development Tasks ### Testing the Server ```bash # Health check curl http://localhost:3017/health # List tools curl http://localhost:3017/mcp/list_tools | jq # Test ping curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_ping", "arguments": {"host": "8.8.8.8", "count": 4}}' # Test nmap host discovery curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_nmap_ping_scan", "arguments": {"target": "192.168.1.0/24"}}' # Test port scan curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_nmap_port_scan", "arguments": {"host": "scanme.nmap.org", "ports": "22,80,443", "service_version": true}}' # Test DNS lookup curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_dig", "arguments": {"domain": "github.com", "record_type": "A", "short": true}}' # Test traceroute curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_traceroute", "arguments": {"host": "google.com", "max_hops": 15}}' ``` ### Rebuilding After Changes ```bash docker-compose down docker-compose up -d --build # View logs docker logs -f cli-mcp-server ``` ## Environment Variables - `PORT` - HTTP server port (default: 3017) ## Security Architecture ### Whitelist-Only Execution **IMPORTANT**: This server does NOT allow arbitrary command execution. Only predefined commands in `ALLOWED_COMMANDS` dictionary are permitted. ```python ALLOWED_COMMANDS = { 'ping': { 'binary': 'ping', 'allowed_flags': ['-c', '-W', '-i'], 'max_timeout': 30, 'description': 'Ping a host to check connectivity' }, # ... more commands } ``` ### Input Validation Layers **1. Shell Metacharacter Blocking:** - Regex pattern: `[;&|`$()<>\\]` - Blocks command injection attempts - Applied to all arguments before execution **2. Format Validation:** - **IP addresses**: Regex + range check (0-255 per octet) - **Hostnames**: RFC-compliant pattern, max 255 chars - **CIDR notation**: IP + prefix validation - **Ports**: Range check (1-65535) - **Port ranges**: Format and range validation - **URLs**: Protocol and format validation **3. Timeout Enforcement:** - Each command has `max_timeout` in configuration - User-supplied timeout cannot exceed maximum - Prevents long-running commands - Timeout exceptions caught and returned as errors **4. Argument Sanitization:** ```python # Example validation for ping if not (validate_ip(host) or validate_hostname(host)): return error args = ['-c', str(count), '-W', str(timeout), host] result = execute_cli_command('ping', args, timeout=30) ``` ### Docker Security Hardening **Resource Limits:** ```yaml deploy: resources: limits: cpus: '0.5' # Half CPU core memory: 512M # 512MB RAM ``` **Security Options:** ```yaml security_opt: - no-new-privileges:true # Prevent privilege escalation cap_drop: - ALL # Drop all capabilities cap_add: - NET_RAW # Required for ping, traceroute - NET_ADMIN # Required for nmap ``` **User Permissions:** - Runs as non-root user (uid 1000: mcpuser) - Created in Dockerfile: `RUN useradd -m -u 1000 mcpuser` - Switched before execution: `USER mcpuser` ### Subprocess Execution Pattern All commands use secure subprocess execution: ```python def execute_cli_command(command_name: str, args: List[str], timeout: Optional[int] = None): # Validate command is whitelisted if command_name not in ALLOWED_COMMANDS: return error # Validate arguments for shell metacharacters for arg in args: if not check_shell_metacharacters(str(arg)): return error # Build command array (NOT shell string) cmd = [binary] + args # Execute with timeout and capture output result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout, check=False # Don't raise on non-zero exit ) return { 'success': result.returncode == 0, 'stdout': result.stdout.strip(), 'stderr': result.stderr.strip(), 'returncode': result.returncode } ``` **Key Security Points:** - `cmd` is a list, not a string (prevents shell injection) - No `shell=True` parameter (would enable shell features) - `check=False` to handle errors gracefully - Timeout enforced to prevent hanging - All output captured for logging ## Error Handling ### Validation Errors ```python if not validate_ip(host): return [TextContent( type="text", text=f"Error: Invalid host format: {host}" )] ``` ### Timeout Errors ```python except subprocess.TimeoutExpired: return { 'success': False, 'stderr': f'Command timed out after {timeout}s', 'returncode': 124 } ``` ### Execution Errors ```python except Exception as e: logger.error(f"Command execution failed: {str(e)}") return { 'success': False, 'stderr': f'Execution error: {str(e)}', 'returncode': 1 } ``` ## Integration with MCP Aggregator ### Adding to Aggregator **1. Update aggregator's docker-compose.yml:** ```yaml environment: - MCP_SERVER_CLI=http://host.docker.internal:3017 ``` **2. Restart aggregator:** ```bash cd mcp-aggregator docker-compose restart ``` **3. Verify discovery:** ```bash curl -s http://localhost:3006/mcp/list_tools | \ jq '.tools[] | select(.name | startswith("cli_"))' ``` ### Tool Naming in Aggregator Tools are prefixed with `cli_`: - Original: `ping` → Aggregated: `cli_ping` - Original: `nmap_port_scan` → Aggregated: `cli_nmap_port_scan` - Original: `dig` → Aggregated: `cli_dig` ## Integration with Mattermost Bot ### Automatic Discovery 1. **Aggregator polls server** - Every 5 minutes via `/mcp/list_tools` 2. **Bot polls aggregator** - Every 5 minutes for tool updates 3. **Tools available in chat** - No restart needed! ### Example Bot Interactions ``` User: Can you ping 8.8.8.8? Bot: → calls cli_ping(host="8.8.8.8", count=4) Bot: ✓ 8.8.8.8 is reachable! Average latency: 12ms User: Scan ports 22, 80, 443 on github.com Bot: → calls cli_nmap_port_scan(host="github.com", ports="22,80,443") Bot: ✓ Port scan results: - Port 22: Open (SSH) - Port 80: Open (HTTP) - Port 443: Open (HTTPS) User: What's the IP for google.com? Bot: → calls cli_dig(domain="google.com", record_type="A", short=true) Bot: ✓ Google.com resolves to: 142.250.185.46 ``` ## Debugging Tips ```bash # Check which CLI tools are installed docker exec cli-mcp-server which nmap ping curl dig traceroute whois host nc mtr # Test command execution inside container docker exec cli-mcp-server ping -c 1 8.8.8.8 docker exec cli-mcp-server nmap -sn localhost docker exec cli-mcp-server dig google.com +short # View detailed logs docker logs cli-mcp-server --tail=100 -f # Check container capabilities docker inspect cli-mcp-server | jq '.[0].HostConfig.CapAdd' # Verify non-root user docker exec cli-mcp-server whoami # Should output: mcpuser # Check resource limits docker stats cli-mcp-server ``` ## Best Practices ### When Adding New Commands 1. **Add to ALLOWED_COMMANDS dictionary:** ```python 'new_command': { 'binary': 'command_binary', 'allowed_flags': ['-flag1', '-flag2'], 'max_timeout': 30, 'description': 'What it does' } ``` 2. **Add validation logic in call_tool():** ```python elif name == "cli_new_command": # Validate inputs if not validate_something(input): return error # Build args safely args = ['-flag', value, target] # Execute result = execute_cli_command('new_command', args) ``` 3. **Add Tool definition in list_tools():** ```python Tool( name="cli_new_command", description="...", inputSchema={...} ) ``` 4. **Update Dockerfile if new binary needed:** ```dockerfile RUN apt-get update && apt-get install -y \ new-package \ && rm -rf /var/lib/apt/lists/* ``` ### Security Review Checklist - [ ] Command in whitelist? - [ ] All arguments validated? - [ ] No shell metacharacters allowed? - [ ] Timeout enforced? - [ ] Binary exists in Docker image? - [ ] Capabilities needed (NET_RAW, NET_ADMIN)? - [ ] Test with malicious inputs - [ ] Audit logging present? ### Common Mistakes to Avoid ❌ **Don't use shell=True:** ```python subprocess.run(f"ping {host}", shell=True) # VULNERABLE! ``` ✅ **Use argument list:** ```python subprocess.run(['ping', host], shell=False) # SAFE ``` ❌ **Don't skip validation:** ```python args = [host] # No validation! ``` ✅ **Always validate:** ```python if not validate_ip(host): return error args = [host] ``` ❌ **Don't allow unlimited timeout:** ```python timeout = user_timeout # Could be huge! ``` ✅ **Enforce maximum:** ```python timeout = min(user_timeout, max_timeout) ``` ## Testing New Commands ```bash # 1. Build with new command docker-compose build # 2. Start server docker-compose up -d # 3. Test tool listing curl http://localhost:3017/mcp/list_tools | jq '.tools[] | select(.name == "cli_new_command")' # 4. Test execution curl -X POST http://localhost:3017/mcp/call_tool \ -H "Content-Type: application/json" \ -d '{"name": "cli_new_command", "arguments": {...}}' # 5. Test validation # Try invalid inputs (shell chars, bad IPs, etc.) # 6. Test timeout # Use very long timeout, should cap at max_timeout # 7. Check logs docker logs cli-mcp-server --tail=50 ``` ## Performance Considerations - **Subprocess overhead**: Each command spawns new process (~10-50ms) - **Timeout buffer**: Set timeouts with buffer for network variance - **Resource limits**: 0.5 CPU and 512MB should handle most commands - **Concurrent requests**: Flask handles multiple requests, but commands run sequentially - **Nmap scans**: Can be slow (minutes for large ranges) ## Known Limitations 1. **No interactive commands**: Only non-interactive commands supported 2. **No pipes/redirects**: Shell features not available (by design) 3. **Output size**: Very large outputs may be truncated by MCP 4. **Network access**: Depends on Docker network configuration 5. **Privileged operations**: Cannot perform operations requiring root 6. **Stateful commands**: Each invocation is independent (no session state) ## Future Enhancements Potential additions (requires security review): - `cli_ss` - Socket statistics - `cli_iftop` - Network bandwidth snapshot - `cli_iperf3` - Network performance testing - `cli_tcpdump` - Limited packet capture - `cli_openssl` - SSL/TLS testing - `cli_nslookup` - Alternative DNS lookup - `cli_arp` - ARP table display

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jbmurphy/mcp-cli'

If you have feedback or need assistance with the MCP directory API, please join our Discord server