CLAUDE.md•12.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