From Localhost to Production — 6 Steps
Saves 15+ hours — every config copy-paste ready, one afternoon to production.
What You'll Build
1. Dev Server
Hot-reload + MCP Inspector for local testing
2. HTTP & ASGI
Switch from stdio to HTTP with uvicorn workers
3. nginx + TLS
Reverse proxy with certbot auto-HTTPS
4. Auth
Bearer tokens, JWT, OAuth SSO
5. Docker
Containerized with health checks + compose
6. Kubernetes
Production-grade with secrets + probes
Step 1: Local Development Server
FastMCP's dev mode gives you hot-reload and the MCP Inspector for testing. This is your local-only phase — no network exposure.
pip install "fastmcp>=3.0"
fastmcp dev server.py
The Inspector opens at http://localhost:5173 — use it to test every tool, resource, and prompt before deploying.
Step 2: HTTP Deployment with FastMCP v3
Switch from stdio to HTTP with the ASGI app pattern — gives you control over workers, middleware, and process management:
# app.py
from fastmcp import FastMCP
mcp = FastMCP("ProductionServer")
@mcp.tool()
def search_docs(query: str) -> str:
"""Search internal documentation."""
return f"Found results for: {query}"
# Create ASGI app for uvicorn/gunicorn
app = mcp.http_app()
uvicorn app:app --host 127.0.0.1 --port 8000 --workers 4
Or with gunicorn for process management:
gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:8000
Your MCP endpoint is now at http://127.0.0.1:8000/mcp.
Step 3: Reverse Proxy with nginx + TLS
Never expose FastMCP directly to the internet. Put nginx in front with TLS.
# /etc/nginx/sites-available/mcp
server {
listen 443 ssl http2;
server_name mcp.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/mcp.yourcompany.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.yourcompany.com/privkey.pem;
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
# MCP sessions can be long-lived
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
location /health {
proxy_pass http://127.0.0.1:8000/health;
}
}
server {
listen 80;
server_name mcp.yourcompany.com;
return 301 https://$host$request_uri;
}
sudo certbot --nginx -d mcp.yourcompany.com
expose_headers:
app = mcp.http_app(
cors_allow_origins=["https://your-app.com"],
cors_expose_headers=["mcp-session-id"], # Without this, JS clients break silently
)
Step 4: Authentication Layer
FastMCP v3 supports Bearer tokens, JWT, and OAuth. For team use, start with Bearer token auth:
from fastmcp.server.auth import BearerAuthProvider
import os
auth = BearerAuthProvider(
token=os.environ["MCP_API_TOKEN"],
jwt_secret=os.environ.get("JWT_SECRET"), # Survives restarts
)
mcp = FastMCP("SecureServer", auth=auth)
Enterprise OAuth (Google/GitHub/Azure SSO)
from fastmcp.server.auth import OAuthProvider
auth = OAuthProvider(
client_id=os.environ["OAUTH_CLIENT_ID"],
client_secret=os.environ["OAUTH_CLIENT_SECRET"],
authorize_url="https://accounts.google.com/o/oauth2/auth",
token_url="https://oauth2.googleapis.com/token",
jwt_secret=os.environ["JWT_SECRET"],
token_store="redis://localhost:6379/0",
)
mcp = FastMCP("EnterpriseServer", auth=auth)
jwt_secret and token_store, tokens are generated in-memory and lost on every restart. Every client must re-authenticate. Set these explicitly.
Step 5: Docker Deployment
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m mcpuser
USER mcpuser
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
# docker-compose.yml
services:
mcp-server:
build: .
ports:
- "127.0.0.1:8000:8000"
environment:
- MCP_API_TOKEN=${MCP_API_TOKEN}
- JWT_SECRET=${JWT_SECRET}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
nginx:
image: nginx:alpine
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
depends_on:
- mcp-server
restart: unless-stopped
Step 6: Kubernetes Deployment
Use K8s when you need auto-scaling, rolling deployments, or multi-region. For most teams, Docker Compose is enough.
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastmcp-server
spec:
replicas: 3
selector:
matchLabels:
app: fastmcp-server
template:
metadata:
labels:
app: fastmcp-server
spec:
containers:
- name: fastmcp
image: your-registry/fastmcp-server:latest
ports:
- containerPort: 8000
env:
- name: MCP_API_TOKEN
valueFrom:
secretKeyRef:
name: mcp-secrets
key: api-token
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: mcp-secrets
key: jwt-secret
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Pair with an Ingress controller (nginx-ingress, Traefik) for TLS termination and routing.
FAQ
How do I monitor my MCP server in production?
Add a /health endpoint (the nginx config above already proxies it), then point UptimeRobot, BetterStack, or Prometheus at it. FastMCP exposes /mcp for API calls and /health for liveness checks by default. For metrics, wrap the ASGI app with prometheus-fastapi-instrumentator.
What happens to active sessions when the server restarts?
They're lost unless you set token_store (Redis) and jwt_secret. With both set, JWT tokens survive restarts and clients reconnect transparently. Without them, every client must re-authenticate after a restart.
Docker Compose vs Kubernetes — what should I start with?
Start with Docker Compose. It handles 95% of MCP deployments — single server, TLS via nginx, health checks, auto-restart. Graduate to Kubernetes when you need multi-region, auto-scaling, or you already run a K8s cluster. The overhead is rarely worth it for a single MCP server.
How do I handle TLS if I don't have a domain or can't use certbot?
Use a self-signed cert for internal/VPN deployments, or put Cloudflare Tunnel in front (free, no certbot needed). For public deployments without certbot: buy a cert, use ssl_certificate and ssl_certificate_key pointing to your CA-signed files — the nginx config is identical otherwise.
Save 20+ Hours
The Production Manual includes every config from this guide, plus 30+ error fixes, monitoring with Prometheus, and enterprise RBAC patterns. From $39, lifetime updates.
Save 20+ Hours