Add Docker scripts and validation tests for Zen MCP Server
- Created build.sh script for building the Docker image with environment variable checks. - Added deploy.sh script for deploying the Zen MCP Server with health checks and logging. - Implemented healthcheck.py to verify server process, Python imports, log directory, and environment variables. - Developed comprehensive tests for Docker configuration, environment validation, and integration with MCP. - Included performance tests for Docker image size and startup time. - Added validation script tests to ensure proper Docker and MCP setup.
This commit is contained in:
326
docker/README.md
Normal file
326
docker/README.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Zen MCP Server - Docker Setup
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Prerequisites
|
||||
|
||||
- Docker installed (Docker Compose optional)
|
||||
- At least one API key (Gemini, OpenAI, xAI, etc.)
|
||||
|
||||
### 2. Configuration
|
||||
|
||||
```bash
|
||||
# Copy environment template
|
||||
cp .env.docker.example .env
|
||||
|
||||
# Edit with your API keys (at least one required)
|
||||
# Required: GEMINI_API_KEY or OPENAI_API_KEY or XAI_API_KEY
|
||||
nano .env
|
||||
```
|
||||
|
||||
### 3. Build Image
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker build -t zen-mcp-server:latest .
|
||||
|
||||
# Or use the build script
|
||||
chmod +x docker/scripts/build.sh
|
||||
./docker/scripts/build.sh
|
||||
```
|
||||
|
||||
### 4. Usage Options
|
||||
|
||||
#### A. Direct Docker Run (Recommended for MCP)
|
||||
|
||||
```bash
|
||||
# Run with environment file
|
||||
docker run --rm -i --env-file .env \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
zen-mcp-server:latest
|
||||
|
||||
# Run with inline environment variables
|
||||
docker run --rm -i \
|
||||
-e GEMINI_API_KEY="your_key_here" \
|
||||
-e LOG_LEVEL=INFO \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
zen-mcp-server:latest
|
||||
```
|
||||
|
||||
#### B. Docker Compose (For Development/Monitoring)
|
||||
|
||||
```bash
|
||||
# Deploy with Docker Compose
|
||||
chmod +x docker/scripts/deploy.sh
|
||||
./docker/scripts/deploy.sh
|
||||
|
||||
# Interactive stdio mode
|
||||
docker-compose exec zen-mcp python server.py
|
||||
```
|
||||
|
||||
## Service Management
|
||||
|
||||
### Docker Commands
|
||||
|
||||
```bash
|
||||
# View running containers
|
||||
docker ps
|
||||
|
||||
# View logs from container
|
||||
docker logs <container_id>
|
||||
|
||||
# Stop all zen-mcp containers
|
||||
docker stop $(docker ps -q --filter "ancestor=zen-mcp-server:latest")
|
||||
|
||||
# Remove old containers and images
|
||||
docker container prune
|
||||
docker image prune
|
||||
```
|
||||
|
||||
### Docker Compose Management (Optional)
|
||||
|
||||
```bash
|
||||
# View logs
|
||||
docker-compose logs -f zen-mcp
|
||||
|
||||
# Check status
|
||||
docker-compose ps
|
||||
|
||||
# Restart service
|
||||
docker-compose restart zen-mcp
|
||||
|
||||
# Stop services
|
||||
docker-compose down
|
||||
|
||||
# Rebuild and update
|
||||
docker-compose build --no-cache zen-mcp
|
||||
docker-compose up -d zen-mcp
|
||||
```
|
||||
|
||||
## Health Monitoring
|
||||
|
||||
The container includes health checks that verify:
|
||||
- Server process is running
|
||||
- Python modules can be imported
|
||||
- Log directory is writable
|
||||
- API keys are configured
|
||||
|
||||
## Volumes
|
||||
|
||||
- `./logs:/app/logs` - Persistent log storage
|
||||
- `zen-mcp-config:/app/conf` - Configuration persistence
|
||||
|
||||
## Security
|
||||
|
||||
- Runs as non-root user `zenuser`
|
||||
- Read-only filesystem with tmpfs for temporary files
|
||||
- No network ports exposed (stdio communication only)
|
||||
- Secrets managed via environment variables
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container won't start
|
||||
|
||||
```bash
|
||||
# Check if image exists
|
||||
docker images zen-mcp-server
|
||||
|
||||
# Test container interactively
|
||||
docker run --rm -it --env-file .env zen-mcp-server:latest bash
|
||||
|
||||
# Check environment variables
|
||||
docker run --rm --env-file .env zen-mcp-server:latest env | grep API
|
||||
|
||||
# Test with minimal configuration
|
||||
docker run --rm -i -e GEMINI_API_KEY="test" zen-mcp-server:latest python server.py
|
||||
```
|
||||
|
||||
### MCP Connection Issues
|
||||
|
||||
```bash
|
||||
# Test Docker connectivity
|
||||
docker run --rm hello-world
|
||||
|
||||
# Verify container stdio
|
||||
echo '{"jsonrpc": "2.0", "method": "ping"}' | docker run --rm -i --env-file .env zen-mcp-server:latest python server.py
|
||||
|
||||
# Check Claude Desktop logs for connection errors
|
||||
```
|
||||
|
||||
### API Key Problems
|
||||
|
||||
```bash
|
||||
# Verify API keys are loaded
|
||||
docker run --rm --env-file .env zen-mcp-server:latest python -c "import os; print('GEMINI_API_KEY:', bool(os.getenv('GEMINI_API_KEY')))"
|
||||
|
||||
# Test API connectivity
|
||||
docker run --rm --env-file .env zen-mcp-server:latest python /usr/local/bin/healthcheck.py
|
||||
```
|
||||
|
||||
### Permission Issues
|
||||
|
||||
```bash
|
||||
# Fix log directory permissions (Linux/macOS)
|
||||
sudo chown -R $USER:$USER logs/
|
||||
chmod 755 logs/
|
||||
|
||||
# Windows: Run Docker Desktop as Administrator if needed
|
||||
```
|
||||
|
||||
### Memory/Performance Issues
|
||||
|
||||
```bash
|
||||
# Check container resource usage
|
||||
docker stats
|
||||
|
||||
# Run with memory limits
|
||||
docker run --rm -i --memory="512m" --env-file .env zen-mcp-server:latest
|
||||
|
||||
# Monitor Docker logs
|
||||
docker run --rm -i --env-file .env zen-mcp-server:latest 2>&1 | tee docker.log
|
||||
```
|
||||
|
||||
## MCP Integration (Claude Desktop)
|
||||
|
||||
### Configuration File Setup
|
||||
|
||||
Add to your Claude Desktop MCP configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"zen-docker": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"--rm",
|
||||
"-i",
|
||||
"--env-file",
|
||||
"/absolute/path/to/zen-mcp-server/.env",
|
||||
"-v",
|
||||
"/absolute/path/to/zen-mcp-server/logs:/app/logs",
|
||||
"zen-mcp-server:latest",
|
||||
"python",
|
||||
"server.py"
|
||||
],
|
||||
"env": {
|
||||
"DOCKER_BUILDKIT": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Windows MCP Configuration
|
||||
|
||||
For Windows users, use forward slashes in paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"zen-docker": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"--rm",
|
||||
"-i",
|
||||
"--env-file",
|
||||
"C:/Users/YourName/path/to/zen-mcp-server/.env",
|
||||
"-v",
|
||||
"C:/Users/YourName/path/to/zen-mcp-server/logs:/app/logs",
|
||||
"zen-mcp-server:latest",
|
||||
"python",
|
||||
"server.py"
|
||||
],
|
||||
"env": {
|
||||
"DOCKER_BUILDKIT": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment File Template
|
||||
|
||||
Create `.env` file with at least one API key:
|
||||
|
||||
```bash
|
||||
# Required: At least one API key
|
||||
GEMINI_API_KEY=your_gemini_key_here
|
||||
OPENAI_API_KEY=your_openai_key_here
|
||||
|
||||
# Optional configuration
|
||||
LOG_LEVEL=INFO
|
||||
DEFAULT_MODEL=auto
|
||||
DEFAULT_THINKING_MODE_THINKDEEP=high
|
||||
|
||||
# Optional API keys (leave empty if not used)
|
||||
ANTHROPIC_API_KEY=
|
||||
XAI_API_KEY=
|
||||
DIAL_API_KEY=
|
||||
OPENROUTER_API_KEY=
|
||||
CUSTOM_API_URL=
|
||||
```
|
||||
|
||||
## Quick Test & Validation
|
||||
|
||||
### 1. Test Docker Image
|
||||
|
||||
```bash
|
||||
# Test container starts correctly
|
||||
docker run --rm zen-mcp-server:latest python --version
|
||||
|
||||
# Test health check
|
||||
docker run --rm -e GEMINI_API_KEY="test" zen-mcp-server:latest python /usr/local/bin/healthcheck.py
|
||||
```
|
||||
|
||||
### 2. Test MCP Protocol
|
||||
|
||||
```bash
|
||||
# Test basic MCP communication
|
||||
echo '{"jsonrpc": "2.0", "method": "initialize", "params": {}}' | \
|
||||
docker run --rm -i --env-file .env zen-mcp-server:latest python server.py
|
||||
```
|
||||
|
||||
### 3. Validate Configuration
|
||||
|
||||
```bash
|
||||
# Run validation script
|
||||
python test_mcp_config.py
|
||||
|
||||
# Or validate JSON manually
|
||||
python -m json.tool .vscode/mcp.json
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
The Zen MCP Server provides these tools when properly configured:
|
||||
|
||||
- **chat** - General AI conversation and collaboration
|
||||
- **thinkdeep** - Multi-stage investigation and reasoning
|
||||
- **planner** - Interactive sequential planning
|
||||
- **consensus** - Multi-model consensus workflow
|
||||
- **codereview** - Comprehensive code review
|
||||
- **debug** - Root cause analysis and debugging
|
||||
- **analyze** - Code analysis and assessment
|
||||
- **refactor** - Refactoring analysis and suggestions
|
||||
- **secaudit** - Security audit workflow
|
||||
- **testgen** - Test generation with edge cases
|
||||
- **docgen** - Documentation generation
|
||||
- **tracer** - Code tracing and dependency mapping
|
||||
- **precommit** - Pre-commit validation workflow
|
||||
- **listmodels** - Available AI models information
|
||||
- **version** - Server version and configuration
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- **Image size**: ~293MB optimized multi-stage build
|
||||
- **Memory usage**: ~256MB base + model overhead
|
||||
- **Startup time**: ~2-3 seconds for container initialization
|
||||
- **API response**: Varies by model and complexity (1-30 seconds)
|
||||
|
||||
For production use, consider:
|
||||
- Using specific API keys for rate limiting
|
||||
- Monitoring container resource usage
|
||||
- Setting up log rotation for persistent logs
|
||||
- Using Docker health checks for reliability
|
||||
41
docker/scripts/build.sh
Normal file
41
docker/scripts/build.sh
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}=== Building Zen MCP Server Docker Image ===${NC}"
|
||||
|
||||
# Check if .env file exists
|
||||
if [[ ! -f .env ]]; then
|
||||
echo -e "${YELLOW}Warning: .env file not found. Copying from .env.example${NC}"
|
||||
if [[ -f .env.example ]]; then
|
||||
cp .env.example .env
|
||||
echo -e "${YELLOW}Please edit .env file with your API keys before running the server${NC}"
|
||||
else
|
||||
echo -e "${RED}Error: .env.example not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build the Docker image
|
||||
echo -e "${GREEN}Building Docker image...${NC}"
|
||||
docker-compose build --no-cache
|
||||
|
||||
# Verify the build
|
||||
if docker images | grep -q "zen-mcp-server"; then
|
||||
echo -e "${GREEN}✓ Docker image built successfully${NC}"
|
||||
echo -e "${GREEN}Image details:${NC}"
|
||||
docker images | grep zen-mcp-server
|
||||
else
|
||||
echo -e "${RED}✗ Failed to build Docker image${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}=== Build Complete ===${NC}"
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo -e " 1. Edit .env file with your API keys"
|
||||
echo -e " 2. Run: ${GREEN}docker-compose up -d${NC}"
|
||||
74
docker/scripts/deploy.sh
Normal file
74
docker/scripts/deploy.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}=== Deploying Zen MCP Server ===${NC}"
|
||||
|
||||
# Function to check if required environment variables are set
|
||||
check_env_vars() {
|
||||
local required_vars=("GOOGLE_API_KEY" "OPENAI_API_KEY")
|
||||
local missing_vars=()
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
missing_vars+=("$var")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing_vars[@]} -gt 0 ]]; then
|
||||
echo -e "${RED}Error: Missing required environment variables:${NC}"
|
||||
printf ' %s\n' "${missing_vars[@]}"
|
||||
echo -e "${YELLOW}Please set these variables in your .env file${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Load environment variables
|
||||
if [[ -f .env ]]; then
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
echo -e "${GREEN}✓ Environment variables loaded from .env${NC}"
|
||||
else
|
||||
echo -e "${RED}Error: .env file not found${NC}"
|
||||
echo -e "${YELLOW}Please copy .env.example to .env and configure your API keys${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check required environment variables
|
||||
check_env_vars
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
mkdir -p logs
|
||||
|
||||
# Stop existing containers
|
||||
echo -e "${GREEN}Stopping existing containers...${NC}"
|
||||
docker-compose down
|
||||
|
||||
# Start the services
|
||||
echo -e "${GREEN}Starting Zen MCP Server...${NC}"
|
||||
docker-compose up -d
|
||||
|
||||
# Wait for health check
|
||||
echo -e "${GREEN}Waiting for service to be healthy...${NC}"
|
||||
timeout 60 bash -c 'while [[ "$(docker-compose ps -q zen-mcp | xargs docker inspect -f "{{.State.Health.Status}}")" != "healthy" ]]; do sleep 2; done' || {
|
||||
echo -e "${RED}Service failed to become healthy${NC}"
|
||||
echo -e "${YELLOW}Checking logs:${NC}"
|
||||
docker-compose logs zen-mcp
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "${GREEN}✓ Zen MCP Server deployed successfully${NC}"
|
||||
echo -e "${GREEN}Service Status:${NC}"
|
||||
docker-compose ps
|
||||
|
||||
echo -e "${GREEN}=== Deployment Complete ===${NC}"
|
||||
echo -e "${YELLOW}Useful commands:${NC}"
|
||||
echo -e " View logs: ${GREEN}docker-compose logs -f zen-mcp${NC}"
|
||||
echo -e " Stop service: ${GREEN}docker-compose down${NC}"
|
||||
echo -e " Restart service: ${GREEN}docker-compose restart zen-mcp${NC}"
|
||||
99
docker/scripts/healthcheck.py
Normal file
99
docker/scripts/healthcheck.py
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Health check script for Zen MCP Server Docker container
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def check_process():
|
||||
"""Check if the main server process is running"""
|
||||
try:
|
||||
result = subprocess.run(["pgrep", "-f", "server.py"], capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
print(f"Process check failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def check_python_imports():
|
||||
"""Check if critical Python modules can be imported"""
|
||||
critical_modules = ["mcp", "google.genai", "openai", "pydantic", "dotenv"]
|
||||
|
||||
for module in critical_modules:
|
||||
try:
|
||||
__import__(module)
|
||||
except ImportError as e:
|
||||
print(f"Critical module {module} cannot be imported: {e}", file=sys.stderr)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error importing {module}: {e}", file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_log_directory():
|
||||
"""Check if logs directory is writable"""
|
||||
log_dir = "/app/logs"
|
||||
try:
|
||||
if not os.path.exists(log_dir):
|
||||
print(f"Log directory {log_dir} does not exist", file=sys.stderr)
|
||||
return False
|
||||
|
||||
test_file = os.path.join(log_dir, ".health_check")
|
||||
with open(test_file, "w") as f:
|
||||
f.write("health_check")
|
||||
os.remove(test_file)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Log directory check failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def check_environment():
|
||||
"""Check if essential environment variables are present"""
|
||||
# At least one API key should be present
|
||||
api_keys = [
|
||||
"GEMINI_API_KEY",
|
||||
"GOOGLE_API_KEY",
|
||||
"OPENAI_API_KEY",
|
||||
"XAI_API_KEY",
|
||||
"DIAL_API_KEY",
|
||||
"OPENROUTER_API_KEY",
|
||||
]
|
||||
|
||||
has_api_key = any(os.getenv(key) for key in api_keys)
|
||||
if not has_api_key:
|
||||
print("No API keys found in environment", file=sys.stderr)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Main health check function"""
|
||||
checks = [
|
||||
("Process", check_process),
|
||||
("Python imports", check_python_imports),
|
||||
("Log directory", check_log_directory),
|
||||
("Environment", check_environment),
|
||||
]
|
||||
|
||||
failed_checks = []
|
||||
|
||||
for check_name, check_func in checks:
|
||||
if not check_func():
|
||||
failed_checks.append(check_name)
|
||||
|
||||
if failed_checks:
|
||||
print(f"Health check failed: {', '.join(failed_checks)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Health check passed")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user