Complete guide for deploying TestOps MCP as a shared server for your team.
This is for Option 2 (Shared Server Setup).
For per-user setup, see README.md
- Quick Start (Docker Compose)
- Expose to External Access
- Docker Container Registry
- Kubernetes Deployment
- Reverse Proxy Setup
- Systemd Service (Linux)
- Security Best Practices
- Monitoring & Logging
- Troubleshooting
Fastest way to get started.
- Docker & Docker Compose
- One Allure TestOps API token (shared service account recommended)
- Your Allure TestOps URL
# 1. Create docker-compose.yml (no clone needed — use the pre-built image)
cat > docker-compose.yml << 'EOF'
services:
testops-mcp:
image: ghcr.io/MimoJanra/TestOpsMCP:latest
restart: unless-stopped
command: ./testops-mcp --http
ports:
- "3000:3000"
env_file:
- .env
healthcheck:
test: ["CMD", "wget", "-qO", "/dev/null", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
EOF
# 2. Create .env file
cat > .env << 'EOF'
ALLURE_BASE_URL=https://your-testops.com
ALLURE_TOKEN=your-shared-token
PORT=3000
LOG_LEVEL=INFO
# Named user tokens — each team member gets their own token
# Format: name:token,name:token,... (use "openssl rand -hex 32" to generate)
MCP_AUTH_TOKENS=alice:token-for-alice,bob:token-for-bob
CORS_ALLOWED_ORIGIN=https://claude.ai
AUDIT_LOG_PATH=/app/audit
AUDIT_RETENTION_DAYS=30
EOF
# 3. Start
docker-compose up -d
# 4. Check logs
docker-compose logs -f testops-mcpDocker image: ghcr.io/MimoJanra/TestOpsMCP:latest — available on GitHub Container Registry, multi-arch (linux/amd64, linux/arm64). Pin a version with e.g. :v2.0.3.
Server runs on http://localhost:3000
Add to Claude Desktop config:
Windows:
{
"mcpServers": {
"testops": {
"command": "cmd",
"args": [
"/c", "npx", "-y", "mcp-remote",
"http://your-server.com:3000/mcp",
"--header", "Authorization:Bearer your-mcp-auth-token",
"--header", "X-Allure-Token:your-personal-allure-token"
]
}
}
}macOS / Linux:
{
"mcpServers": {
"testops": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"http://your-server.com:3000/mcp",
"--header", "Authorization:Bearer your-mcp-auth-token",
"--header", "X-Allure-Token:your-personal-allure-token"
]
}
}
}Authorization(optional) — each user's named bearer token fromMCP_AUTH_TOKENS. Omit the--headerarg if no auth is configured.X-Allure-Token— each user's personal Allure API token. Actions in Allure are performed under that account.
After docker-compose up -d the server listens on 0.0.0.0:3000 — reachable from outside the host once the firewall allows it. Choose the method that fits your situation:
| Method | Use case | HTTPS | Setup time |
|---|---|---|---|
| Open firewall port | Internal network / VPN | No | 1 min |
| ngrok | Quick demo, dev testing | Yes (auto) | 2 min |
| Reverse proxy (nginx/Caddy) | Production, stable domain | Yes | 15 min |
Docker already binds to 0.0.0.0:3000 — you just need to open the port on the host firewall.
Linux (ufw):
sudo ufw allow 3000/tcp
sudo ufw reloadLinux (firewalld):
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --reloadCloud provider firewall rules:
| Provider | Command |
|---|---|
| AWS EC2 | Add inbound rule TCP 3000 in the Security Group |
| GCP | gcloud compute firewall-rules create testops-mcp --allow tcp:3000 --target-tags testops-mcp |
| DigitalOcean / Hetzner | Add inbound rule TCP 3000 in the cloud firewall dashboard |
Team members connect to http://<your-server-ip>:3000/mcp.
Caution: plain HTTP — credentials travel in cleartext. Use only on a trusted internal network or VPN. For public internet use ngrok or a reverse proxy with HTTPS.
ngrok gives you a public HTTPS URL in seconds without DNS setup or certificates. Ideal for demos and short-lived testing.
# Install: https://ngrok.com/download
# Then run (while docker-compose is up):
ngrok http 3000ngrok prints a URL like https://abc123.ngrok-free.app. Use it directly in the Claude Desktop config:
{
"mcpServers": {
"testops": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://abc123.ngrok-free.app/mcp",
"--header", "Authorization:Bearer your-mcp-auth-token",
"--header", "X-Allure-Token:your-personal-allure-token"
]
}
}
}The free ngrok URL changes every time you restart ngrok. For a stable URL, use a paid ngrok plan (
ngrok http --domain=your-domain.ngrok-free.app 3000) or switch to a reverse proxy.
Put nginx or Caddy in front for a stable public domain with TLS termination. See Reverse Proxy Setup below.
# Build with specific tag
docker build -t testops-mcp:1.0 .
# Push to registry
docker push myregistry.com/testops-mcp:1.0Create docker-compose.prod.yml:
version: '3.8'
services:
testops-mcp:
image: ghcr.io/MimoJanra/TestOpsMCP:latest
restart: unless-stopped
command: ./testops-mcp --http
ports:
- "3000:3000"
env_file:
- .env.prod
environment:
LOG_LEVEL: INFO
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
cpus: '0.5'
memory: 128M
healthcheck:
test: ["CMD", "wget", "-qO", "/dev/null", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "10"Run:
docker-compose -f docker-compose.prod.yml up -d
docker-compose -f docker-compose.prod.yml logs -fCreate /etc/nginx/sites-available/allure-mcp:
upstream testops_mcp_backend {
server localhost:3000;
}
server {
listen 443 ssl http2;
server_name testops.company.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/testops.company.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/testops.company.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req zone=api_limit burst=20 nodelay;
# SSE endpoint
location /sse {
proxy_pass http://testops_mcp_backend;
proxy_http_version 1.1;
# SSE requires these headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_set_header X-Forwarded-Host $server_name;
# Pass auth header
proxy_pass_request_headers on;
# Disable buffering for SSE
proxy_buffering off;
# Timeouts for long-lived connection
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# Streamable HTTP endpoint (MCP 2025-11-25)
location /mcp {
proxy_pass http://testops_mcp_backend;
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass_request_headers on;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
# Legacy SSE messages endpoint
location /messages {
proxy_pass http://testops_mcp_backend;
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass_request_headers on;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
# Health check (no auth required)
location /health {
proxy_pass http://testops_mcp_backend;
proxy_http_version 1.1;
access_log off;
}
# Deny all other paths
location / {
return 404;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name testops.company.com;
return 301 https://$server_name$request_uri;
}Enable and reload:
sudo ln -s /etc/nginx/sites-available/allure-mcp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxCreate Caddyfile:
allure-mcp.example.com {
# Proxy to local backend
reverse_proxy localhost:3000 {
# SSE support
header_up Upgrade websocket
header_up Connection upgrade
# Preserve client headers
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
# Rate limiting
rate_limit 10r/s burst=20
# Security headers (built-in to Caddy 2+)
}Run:
caddy runsudo apt install certbot python3-certbot-nginx
# Generate certificate
sudo certbot certonly --nginx -d allure-mcp.example.com
# Auto-renewal
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# Check renewal
sudo certbot renew --dry-runopenssl req -x509 -newkey rsa:4096 -nodes \
-out cert.pem -keyout key.pem -days 365 \
-subj "/CN=allure-mcp.local"Use with Caddy:
allure-mcp.local {
tls cert.pem key.pem
reverse_proxy localhost:3000
}Already configured in Dockerfile:
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO /dev/null http://localhost:3000/health || exit 1livenessProbe:
httpGet:
path: /health
port: 3000
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 2curl -f http://localhost:3000/health
# Should return: okAllure MCP Server is stateless and can run multiple replicas behind a load balancer.
upstream allure_mcp {
server localhost:3001;
server localhost:3002;
server localhost:3003;
keepalive 32;
}
server {
listen 80;
location / {
proxy_pass http://allure_mcp;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}Scale replicas:
kubectl scale deployment allure-mcp --replicas=3docker swarm init
docker stack deploy -c docker-compose.yml allure
docker service scale allure_allure-mcp=3Per instance:
- CPU: 0.5-1 core
- Memory: 128-256 MB
- Storage: 1 GB (logs)
For 10 concurrent users: 2 replicas
For 50+ concurrent users: 5+ replicas
Logs are JSON-formatted to stderr. Capture with:
docker-compose logs -f allure-mcpOr with systemd:
journalctl -u allure-mcp -f -S "1 hour ago"{"level":"INFO","msg":"Starting MCP server","mode":"http","port":3000,"timestamp":"2025-01-15T10:30:00Z"}
{"level":"DEBUG","msg":"Tool called","tool":"run_allure_launch","project_id":1,"timestamp":"2025-01-15T10:30:15Z"}For production monitoring, collect logs with:
- Filebeat → Elasticsearch/Kibana
- Loki + Grafana
- Datadog / New Relic agents
Example Grafana alert:
alert: AllureMCPError
expr: |
count(rate(container_logs_errors[5m])) > 0.1
annotations:
summary: "High error rate in Allure MCP"Use external monitors like:
- Pingdom
- UptimeRobot
- New Relic Synthetics
curl -f https://allure-mcp.example.com/health > /dev/null 2>&1Store .env securely:
# Encrypt before storing
gpg --encrypt -r your-key-id .env
# Or use secret manager
vault write secret/allure-mcp \
base_url="https://allure.example.com" \
token="xxxxx"Allure MCP Server is stateless — no database to backup. All state is in Allure TestOps.
To restore service:
- Spin up new server with same configuration
- Point to existing Allure TestOps instance
- No data migration needed
docker-compose -f docker-compose.prod.yml up -dDefault Go HTTP client reuses connections. For high throughput:
# Increase file descriptors (Linux)
ulimit -n 65536
# Or in systemd service
[Service]
LimitNOFILE=65536
LimitNPROC=65536Adjust in .env:
REQUEST_TIMEOUT=60 # Increase for slow Allure instancesConsider adding HTTP caching headers for team deployments:
location /sse {
add_header Cache-Control "no-store, must-revalidate";
proxy_cache off;
}- SSL/HTTPS enabled
- Firewall rules configured
- Health checks passing
- Monitoring alerts set up
- Backup strategy documented
- Log rotation configured
- Rate limiting enabled
- MCP_AUTH_TOKENS configured with strong per-user tokens
- Audit log volume mounted and retention days configured
- CORS_ALLOWED_ORIGIN restricted (not
*) - Secrets not in
.env(use secret manager)