apiVersion: v1 kind: Service metadata: name: mcp-server-service spec: selector: app: mcp-server ports: - protocol: TCP port: 80 targetPort: 3000 type: LoadBalancer ```
Deploying to Kubernetes
# Apply the deployment
kubectl apply -f mcp-deployment.yaml
# Check deployment status
kubectl get deployments
# View pods
kubectl get pods
# Scale deployment
kubectl scale deployment mcp-server --replicas=5
# View logs
kubectl logs -f deployment/mcp-server
# Update deployment
kubectl set image deployment/mcp-server mcp-server=your-registry/mcp-server:v2
Python-Based MCP Server Deployment
For Python MCP servers using FastAPI or similar frameworks:
Gunicorn with Nginx
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
keepalive = 5
timeout = 30
accesslog = "/var/log/mcp/access.log"
errorlog = "/var/log/mcp/error.log"
loglevel = "info"
# Install Gunicorn
pip install gunicorn uvicorn
# Run with Gunicorn
gunicorn main:app -c gunicorn.conf.py
Nginx Configuration
Create /etc/nginx/sites-available/mcp-server
:
upstream mcp_server {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name mcp.yourdomain.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name mcp.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/mcp.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourdomain.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
location / {
proxy_pass http://mcp_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Security Best Practices
1. Environment Variables and Secrets Management
Never hardcode sensitive data. Use environment variables or secret management tools:
# Using environment files
echo "MCP_API_KEY=your_secret_key" > .env
echo "DATABASE_URL=postgresql://user:pass@localhost/db" >> .env
# Or use cloud secret managers
# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id mcp-server-credentials
# Google Cloud Secret Manager
gcloud secrets versions access latest --secret="mcp-api-key"
2. Rate Limiting
Implement rate limiting to prevent abuse:
// Using express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
3. Authentication and Authorization
// API key authentication middleware
const authenticateApiKey = (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey || !isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
};
app.use(authenticateApiKey);
4. CORS Configuration
const cors = require('cors');
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
methods: ['GET', 'POST'],
credentials: true
}));
Monitoring and Logging
Application Monitoring
// Using Prometheus metrics
const promClient = require('prom-client');
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
registers: [register]
});
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
Structured Logging
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
defaultMeta: { service: 'mcp-server' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
Cloud Platform Deployments
AWS Elastic Container Service (ECS)
# Create ECR repository
aws ecr create-repository --repository-name mcp-server
# Build and push Docker image
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin your-account.dkr.ecr.us-east-1.amazonaws.com
docker build -t mcp-server .
docker tag mcp-server:latest your-account.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest
docker push your-account.dkr.ecr.us-east-1.amazonaws.com/mcp-server:latest
# Deploy to ECS (using AWS CLI or Terraform)
Google Cloud Run
# Build and deploy
gcloud builds submit --tag gcr.io/PROJECT_ID/mcp-server
gcloud run deploy mcp-server \
--image gcr.io/PROJECT_ID/mcp-server \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--max-instances 10
Azure Container Instances
# Create container registry
az acr create --resource-group myResourceGroup --name mcpRegistry --sku Basic
# Build and push
az acr build --registry mcpRegistry --image mcp-server:latest .
# Deploy
az container create \
--resource-group myResourceGroup \
--name mcp-server \
--image mcpRegistry.azurecr.io/mcp-server:latest \
--cpu 2 --memory 4 \
--registry-login-server mcpRegistry.azurecr.io \
--ports 3000
Performance Optimization
Caching Strategies
Implement caching to reduce load on your MCP server:
const Redis = require('redis');
const client = Redis.createClient({
url: process.env.REDIS_URL
});
async function getCachedResponse(key, fetchFunction, ttl = 3600) {
const cached = await client.get(key);
if (cached) {
return JSON.parse(cached);
}
const result = await fetchFunction();
await client.setEx(key, ttl, JSON.stringify(result));
return result;
}
Load Balancing
Use load balancers to distribute traffic across multiple MCP server instances, similar to how you might handle browser sessions in Puppeteer across multiple browser instances.
Backup and Disaster Recovery
#!/bin/bash
# Backup script
BACKUP_DIR="/backup/mcp-server"
DATE=$(date +%Y%m%d_%H%M%S)
# Database backup
pg_dump $DATABASE_URL > "$BACKUP_DIR/db_$DATE.sql"
# Configuration backup
tar -czf "$BACKUP_DIR/config_$DATE.tar.gz" /opt/mcp-server/config
# Upload to S3
aws s3 cp "$BACKUP_DIR/db_$DATE.sql" s3://your-backup-bucket/
aws s3 cp "$BACKUP_DIR/config_$DATE.tar.gz" s3://your-backup-bucket/
# Cleanup old backups (keep last 30 days)
find $BACKUP_DIR -name "*.sql" -mtime +30 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
Health Checks and Readiness Probes
Implement robust health check endpoints:
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
app.get('/ready', async (req, res) => {
try {
// Check database connection
await db.ping();
// Check Redis connection
await redis.ping();
res.status(200).json({
status: 'ready',
checks: {
database: 'connected',
redis: 'connected'
}
});
} catch (error) {
res.status(503).json({
status: 'not ready',
error: error.message
});
}
});
Continuous Deployment
GitHub Actions Workflow
name: Deploy MCP Server
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t mcp-server:${{ github.sha }} .
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker tag mcp-server:${{ github.sha }} your-registry/mcp-server:latest
docker push your-registry/mcp-server:latest
- name: Deploy to production
run: |
kubectl set image deployment/mcp-server mcp-server=your-registry/mcp-server:${{ github.sha }}
kubectl rollout status deployment/mcp-server
Troubleshooting Common Issues
Memory Leaks
Monitor memory usage and implement automatic restarts:
// Memory monitoring
setInterval(() => {
const usage = process.memoryUsage();
logger.info('Memory usage', {
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`
});
// Restart if memory exceeds threshold
if (usage.heapUsed > 900 * 1024 * 1024) { // 900MB
logger.error('Memory threshold exceeded, graceful shutdown');
gracefulShutdown();
}
}, 30000);
Connection Timeouts
Configure appropriate timeout settings for different scenarios, much like handling timeouts in Puppeteer when scraping dynamic content:
// Server timeouts
server.keepAliveTimeout = 65000; // slightly higher than ALB timeout
server.headersTimeout = 66000;
// Request timeouts
const axios = require('axios');
const client = axios.create({
timeout: 30000,
maxRedirects: 5
});
Conclusion
Deploying an MCP server in production requires attention to security, scalability, and reliability. Whether you choose Docker, PM2, systemd, or Kubernetes, ensure you implement proper monitoring, logging, and backup procedures. Start with a simple deployment method and scale up as your requirements grow. Regular testing, monitoring, and maintenance are key to keeping your MCP server running smoothly in production.
For web scraping workloads that need to handle browser automation in Docker, consider the additional resource requirements and isolation benefits that containerization provides for your MCP server deployment.