docs/HTTP_DEPLOYMENT.md
Deploy n8n-MCP as a remote HTTP server to provide n8n knowledge to compatible MCP Client from anywhere.
n8n-MCP HTTP mode enables:
Use stdio mode - Claude Desktop connects directly to the Node.js process:
Claude Desktop ā n8n-mcp (stdio mode)
Run HTTP server locally for testing remote features:
Claude Desktop ā http-bridge.js ā localhost:3000
Deploy to cloud for access from anywhere:
Claude Desktop ā mcp-remote ā https://your-server.com
Server Requirements:
Client Requirements:
# 1. Create environment file
cat > .env << EOF
AUTH_TOKEN=$(openssl rand -base64 32)
MCP_MODE=http
PORT=3000
# Optional: Enable n8n management tools
# N8N_API_URL=https://your-n8n-instance.com
# N8N_API_KEY=your-api-key-here
# Security Configuration (v2.16.3+)
# Rate limiting (default: 20 attempts per 15 minutes)
AUTH_RATE_LIMIT_WINDOW=900000
AUTH_RATE_LIMIT_MAX=20
# SSRF protection mode (default: strict)
# Use 'moderate' for local n8n, 'strict' for production
WEBHOOK_SECURITY_MODE=strict
EOF
# 2. Deploy with Docker
docker run -d \
--name n8n-mcp \
--restart unless-stopped \
--env-file .env \
-p 3000:3000 \
ghcr.io/czlonkowski/n8n-mcp:latest
# 3. Verify deployment
curl http://localhost:3000/health
# 1. Clone and setup
git clone https://github.com/czlonkowski/n8n-mcp.git
cd n8n-mcp
npm install
npm run build
npm run rebuild
# 2. Configure environment
export MCP_MODE=http
export AUTH_TOKEN=$(openssl rand -base64 32)
export PORT=3000
# 3. Start server
npm run start:http
Skip HTTP entirely and use stdio mode directly:
{
"mcpServers": {
"n8n-local": {
"command": "node",
"args": [
"/path/to/n8n-mcp/dist/mcp/index.js"
],
"env": {
"N8N_API_URL": "https://your-n8n-instance.com",
"N8N_API_KEY": "your-api-key-here"
}
}
}
}
š” Save your AUTH_TOKEN - clients will need it to connect!
| Variable | Description | Example |
|---|---|---|
MCP_MODE | Must be set to http | http |
AUTH_TOKEN or AUTH_TOKEN_FILE | Authentication method | See security section |
| Variable | Description | Default | Since |
|---|---|---|---|
PORT | Server port | 3000 | v1.0 |
HOST | Bind address | 0.0.0.0 | v1.0 |
LOG_LEVEL | Log verbosity (error/warn/info/debug) | info | v1.0 |
NODE_ENV | Environment | production | v1.0 |
TRUST_PROXY | Trust proxy headers (0=off, 1+=hops) | 0 | v2.7.6 |
BASE_URL | Explicit public URL | Auto-detected | v2.7.14 |
PUBLIC_URL | Alternative to BASE_URL | Auto-detected | v2.7.14 |
CORS_ORIGIN | CORS allowed origins | * | v2.7.8 |
AUTH_TOKEN_FILE | Path to token file | - | v2.7.10 |
Enable 16 additional tools for managing n8n workflows by configuring API access:
ā ļø Requires v2.7.1+ - Earlier versions had an issue with tool registration in Docker environments.
| Variable | Description | Example |
|---|---|---|
N8N_API_URL | Your n8n instance URL | https://your-n8n.com |
N8N_API_KEY | n8n API key (from Settings > API) | n8n_api_key_xxx |
N8N_API_TIMEOUT | Request timeout (ms) | 30000 |
N8N_API_MAX_RETRIES | Max retry attempts | 3 |
When configured, you get 16 additional tools (total: 39 tools):
Workflow Management (11 tools):
n8n_create_workflow - Create new workflowsn8n_get_workflow - Get workflow by IDn8n_update_full_workflow - Update entire workflown8n_update_partial_workflow - Update using diff operations (v2.7.0+)n8n_delete_workflow - Delete workflowsn8n_list_workflows - List all workflowsExecution Management (4 tools):
n8n_trigger_webhook_workflow - Execute via webhooksn8n_get_execution - Get execution detailsn8n_list_executions - List workflow runsn8n_delete_execution - Delete execution recordsSystem Tools:
n8n_health_check - Check n8n connectivityn8n_diagnostic - System diagnosticsn8n_validate_workflow - Validate from n8n instanceā ļø Security Note: Store API keys securely and never commit them to version control.
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
ā Claude Desktop ā stdio ā mcp-remote ā HTTP ā n8n-MCP ā
ā (stdio only) āāāāāāāāāŗā (bridge) āāāāāāāāāŗā HTTP Server ā
āāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāā āāāāāāāāāāāāāāāā
ā
ā¼
āāāāāāāāāāāāāāāā
ā Your n8n ā
ā Instance ā
āāāāāāāāāāāāāāāā
Key Points:
mcp-remote acts as a bridge, converting stdio ā HTTPn8n-MCP intelligently detects your public URL:
Explicit Configuration (highest priority):
BASE_URL=https://n8n-mcp.example.com # Full public URL
# or
PUBLIC_URL=https://api.company.com:8443/mcp
Auto-Detection (when TRUST_PROXY is enabled):
TRUST_PROXY=1 # Required for proxy header detection
# Server reads X-Forwarded-Proto and X-Forwarded-Host
Fallback (local binding):
# No configuration needed
# Shows: http://localhost:3000 (or configured HOST:PORT)
[INFO] Starting n8n-MCP HTTP Server v2.7.17...
[INFO] Server running at https://n8n-mcp.example.com
[INFO] Endpoints:
[INFO] Health: https://n8n-mcp.example.com/health
[INFO] MCP: https://n8n-mcp.example.com/mcp
When running n8n-MCP behind a reverse proxy (Nginx, Traefik, etc.), enable trust proxy to log real client IPs instead of proxy IPs:
# Enable trust proxy in your environment
TRUST_PROXY=1 # Trust 1 proxy hop (standard setup)
# or
TRUST_PROXY=2 # Trust 2 proxy hops (CDN ā Load Balancer ā n8n-mcp)
Without TRUST_PROXY:
[INFO] GET /health { ip: '172.19.0.2' } # Docker internal IP
With TRUST_PROXY=1:
[INFO] GET /health { ip: '203.0.113.1' } # Real client IP
This is especially important when:
All requests require Bearer token authentication:
# Test authentication
curl -H "Authorization: Bearer $AUTH_TOKEN" \
https://your-server.com/health
Use a reverse proxy for SSL termination:
Nginx example:
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /mcp {
proxy_pass http://localhost:3000;
proxy_set_header Authorization $http_authorization;
# Important: Forward client IP headers
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;
}
}
Caddy example (automatic HTTPS):
your-domain.com {
reverse_proxy /mcp localhost:3000
}
ā ļø Requirements: Node.js 18+ must be installed on the client machine for mcp-remote
{
"mcpServers": {
"n8n-remote": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://your-server.com/mcp",
"--header",
"Authorization: Bearer YOUR_AUTH_TOKEN_HERE"
]
}
}
}
Note: Replace YOUR_AUTH_TOKEN_HERE with your actual token. Do NOT use ${AUTH_TOKEN} syntax - Claude Desktop doesn't support environment variable substitution in args.
For local testing or when mcp-remote isn't available:
{
"mcpServers": {
"n8n-local-http": {
"command": "node",
"args": [
"/path/to/n8n-mcp/scripts/http-bridge.js"
],
"env": {
"MCP_URL": "http://localhost:3000/mcp",
"AUTH_TOKEN": "your-auth-token-here"
}
}
}
}
When testing locally with Docker:
{
"mcpServers": {
"n8n-docker-http": {
"command": "node",
"args": [
"/path/to/n8n-mcp/scripts/http-bridge.js"
],
"env": {
"MCP_URL": "http://localhost:3001/mcp",
"AUTH_TOKEN": "docker-test-token"
}
}
}
}
version: '3.8'
services:
n8n-mcp:
image: ghcr.io/czlonkowski/n8n-mcp:latest
container_name: n8n-mcp
restart: unless-stopped
environment:
# Core configuration
MCP_MODE: http
NODE_ENV: production
# Security - Using file-based secret
AUTH_TOKEN_FILE: /run/secrets/auth_token
# Networking
HOST: 0.0.0.0
PORT: 3000
TRUST_PROXY: 1 # Behind Nginx/Traefik
CORS_ORIGIN: https://app.example.com # Restrict in production
# URL Configuration
BASE_URL: https://n8n-mcp.example.com
# Logging
LOG_LEVEL: info
# Optional: n8n API Integration
N8N_API_URL: ${N8N_API_URL}
N8N_API_KEY_FILE: /run/secrets/n8n_api_key
secrets:
- auth_token
- n8n_api_key
ports:
- "127.0.0.1:3000:3000" # Only expose to localhost
volumes:
- n8n-mcp-data:/app/data:ro # Read-only database
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.1'
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
secrets:
auth_token:
file: ./secrets/auth_token.txt
n8n_api_key:
file: ./secrets/n8n_api_key.txt
volumes:
n8n-mcp-data:
# /etc/systemd/system/n8n-mcp.service
[Unit]
Description=n8n-MCP HTTP Server
Documentation=https://github.com/czlonkowski/n8n-mcp
After=network.target
Requires=network.target
[Service]
Type=simple
User=n8n-mcp
Group=n8n-mcp
WorkingDirectory=/opt/n8n-mcp
# Use file-based secret
Environment="AUTH_TOKEN_FILE=/etc/n8n-mcp/auth_token"
Environment="MCP_MODE=http"
Environment="NODE_ENV=production"
Environment="TRUST_PROXY=1"
Environment="BASE_URL=https://n8n-mcp.example.com"
# Additional config from file
EnvironmentFile=-/etc/n8n-mcp/config.env
ExecStartPre=/usr/bin/test -f /etc/n8n-mcp/auth_token
ExecStart=/usr/bin/node dist/mcp/index.js --http
# Restart configuration
Restart=always
RestartSec=10
StartLimitBurst=5
StartLimitInterval=60s
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/n8n-mcp/data
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
# Resource limits
LimitNOFILE=65536
MemoryLimit=512M
CPUQuota=50%
[Install]
WantedBy=multi-user.target
Setup:
# Create user and directories
sudo useradd -r -s /bin/false n8n-mcp
sudo mkdir -p /opt/n8n-mcp /etc/n8n-mcp
sudo chown n8n-mcp:n8n-mcp /opt/n8n-mcp
# Create secure token
sudo sh -c 'openssl rand -base64 32 > /etc/n8n-mcp/auth_token'
sudo chmod 600 /etc/n8n-mcp/auth_token
sudo chown n8n-mcp:n8n-mcp /etc/n8n-mcp/auth_token
# Deploy application
sudo -u n8n-mcp git clone https://github.com/czlonkowski/n8n-mcp.git /opt/n8n-mcp
cd /opt/n8n-mcp
sudo -u n8n-mcp npm install --production
sudo -u n8n-mcp npm run build
sudo -u n8n-mcp npm run rebuild
# Start service
sudo systemctl daemon-reload
sudo systemctl enable n8n-mcp
sudo systemctl start n8n-mcp
Enable:
sudo systemctl enable n8n-mcp
sudo systemctl start n8n-mcp
# Basic health check
curl -H "Authorization: Bearer $AUTH_TOKEN" \
https://your-server.com/health
# Response:
{
"status": "ok",
"mode": "http-fixed",
"version": "2.7.17",
"uptime": 3600,
"memory": {
"used": 95,
"total": 512,
"percentage": 18.5
},
"node": {
"version": "v20.11.0",
"platform": "linux"
},
"features": {
"n8nApi": true, // If N8N_API_URL configured
"authFile": true // If using AUTH_TOKEN_FILE
}
}
Built-in rate limiting protects authentication endpoints from brute force attacks:
Configuration:
# Defaults (15 minutes window, 20 attempts per IP)
AUTH_RATE_LIMIT_WINDOW=900000 # milliseconds
AUTH_RATE_LIMIT_MAX=20
Features:
Behavior:
Prevents Server-Side Request Forgery attacks. The same gate applies to webhook trigger URLs (chat, form, generic webhook), the n8n API client base URL (N8N_API_URL), and per-request URLs supplied via the x-n8n-url header in multi-tenant mode.
Three Security Modes:
Strict Mode (default) - Production deployments
WEBHOOK_SECURITY_MODE=strict
Moderate Mode - Local development with local n8n
WEBHOOK_SECURITY_MODE=moderate
Permissive Mode - Internal networks only
WEBHOOK_SECURITY_MODE=permissive
Important: Cloud metadata endpoints are ALWAYS blocked in all modes for security.
DO:
DON'T:
# Generate strong token
openssl rand -base64 32
# Secure storage options:
# 1. Docker secrets (recommended)
echo $(openssl rand -base64 32) | docker secret create auth_token -
# 2. Kubernetes secrets
kubectl create secret generic n8n-mcp-auth \
--from-literal=token=$(openssl rand -base64 32)
# 3. HashiCorp Vault
vault kv put secret/n8n-mcp token=$(openssl rand -base64 32)
# Run as non-root user (already configured)
# Read-only filesystem
docker run --read-only \
--tmpfs /tmp \
-v n8n-mcp-data:/app/data \
n8n-mcp
# Security scanning
docker scan ghcr.io/czlonkowski/n8n-mcp:latest
"Unauthorized" error:
# Check token is set correctly
docker exec n8n-mcp env | grep AUTH
# Test with curl
curl -v -H "Authorization: Bearer YOUR_TOKEN" \
https://your-server.com/health
# Common causes:
# - Extra spaces in token
# - Missing "Bearer " prefix
# - Token file has newline at end
# - Wrong quotes in JSON config
Default token warning:
ā ļø SECURITY WARNING: Using default AUTH_TOKEN
"TransformStream is not defined":
# Check Node.js version on CLIENT machine
node --version # Must be 18+
# Update Node.js
# macOS: brew upgrade node
# Linux: Use NodeSource repository
# Windows: Download from nodejs.org
"Cannot connect to server":
# 1. Check server is running
docker ps | grep n8n-mcp
# 2. Check logs for errors
docker logs n8n-mcp --tail 50
# 3. Test locally first
curl http://localhost:3000/health
# 4. Check firewall
sudo ufw status # Linux
"Stream is not readable":
Bridge script not working:
# Test the bridge manually
export MCP_URL=http://localhost:3000/mcp
export AUTH_TOKEN=your-token
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node /path/to/http-bridge.js
Connection refused:
# Check server is running
curl http://localhost:3000/health
# Check Docker status
docker ps
docker logs n8n-mcp
# Check firewall
sudo ufw status
Authentication failed:
"Why use 'node' instead of 'docker' in Claude config?"
Claude Desktop only supports stdio. The architecture is:
Claude ā stdio ā mcp-remote ā HTTP ā Docker container
The node command runs mcp-remote (the bridge), not the server directly.
"Command not found: npx":
# Install Node.js 18+ which includes npx
# Or use full path:
which npx # Find npx location
# Use that path in Claude config
# 1. Enable debug logging
docker run -e LOG_LEVEL=debug ...
# 2. Test MCP endpoint
curl -X POST https://your-server.com/mcp \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}'
# 3. Test with mcp-remote directly
MCP_URL=https://your-server.com/mcp \
AUTH_TOKEN=your-token \
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | \
npx mcp-remote $MCP_URL --header "Authorization: Bearer $AUTH_TOKEN"
Railway: See our Railway Deployment Guide
When n8n API is configured, Claude can manage workflows directly:
# Test n8n connectivity first
curl -X POST https://your-server.com/mcp \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "n8n_health_check",
"params": {},
"id": 1
}'
For governance-sensitive environments where the AI agent should be able to read workflow and execution data but must not modify or delete anything, combine two layers of control:
Layer 1 ā MCP layer:
Some tools are purely write/destructive and should be removed entirely via DISABLED_TOOLS:
DISABLED_TOOLS=n8n_create_workflow,n8n_update_full_workflow,n8n_update_partial_workflow,n8n_delete_workflow,n8n_autofix_workflow,n8n_deploy_template,n8n_test_workflow,n8n_generate_workflow,n8n_manage_credentials,n8n_manage_datatable
Two tools bundle read and write operations under a single name. Use DISABLED_TOOL_OPERATIONS to block only their destructive branches while keeping list and get:
DISABLED_TOOL_OPERATIONS=n8n_workflow_versions:delete,rollback,prune;n8n_executions:delete
The operation parameter enum in the tool schema is updated to exclude disabled values, reducing the likelihood the model attempts them. Any attempt that does reach the server is rejected at dispatch before the handler runs.
Layer 2 ā n8n API key RBAC:
Scope the N8N_API_KEY to read-only permissions in your n8n instance (Settings ā API ā create a key with read-only scope). This ensures that even if the MCP layer is misconfigured, the n8n API itself will reject destructive requests.
Both layers together provide defence in depth. The MCP layer is a convenience knob; the n8n API RBAC is the authoritative enforcement boundary.
# Check current version
docker exec n8n-mcp node -e "console.log(require('./package.json').version)"
# Update to latest
docker pull ghcr.io/czlonkowski/n8n-mcp:latest
docker stop n8n-mcp
docker rm n8n-mcp
# Re-run with same environment
# Update to specific version
docker pull ghcr.io/czlonkowski/n8n-mcp:v2.7.17
# The database is read-only and pre-built
# No backups needed for the node database
# Updates include new database versions
# Check database stats
curl -X POST https://your-server.com/mcp \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "get_database_statistics",
"id": 1
}'