Prepare your FastMCP server for production with this comprehensive checklist covering security, performance, monitoring, and reliability.
Security
HTTPS Configuration
Always use HTTPS in production to encrypt traffic:
server . start ({
transportType: "httpStream" ,
httpStream: {
port: 8443 ,
sslCert: "/path/to/cert.pem" ,
sslKey: "/path/to/key.pem" ,
sslCa: "/path/to/ca.pem" , // Optional: for client cert auth
},
});
Obtain SSL Certificates
Use a trusted Certificate Authority: Let's Encrypt (Recommended)
Self-Signed (Development Only)
Commercial CA
# Install certbot
sudo apt-get install certbot
# Obtain certificate
sudo certbot certonly --standalone -d yourdomain.com
# Certificates will be in:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem
Configure Auto-Renewal
Set up automatic certificate renewal: # Test renewal
sudo certbot renew --dry-run
# Add to crontab for auto-renewal
0 0 * * * certbot renew --quiet --post-hook "systemctl restart fastmcp"
Use Strong TLS Settings
Ensure your SSL configuration uses modern TLS: import { readFileSync } from "fs" ;
server . start ({
transportType: "httpStream" ,
httpStream: {
port: 8443 ,
sslCert: readFileSync ( "/etc/letsencrypt/live/yourdomain.com/fullchain.pem" , "utf8" ),
sslKey: readFileSync ( "/etc/letsencrypt/live/yourdomain.com/privkey.pem" , "utf8" ),
},
});
Never use self-signed certificates in production. They provide encryption but no identity verification.
Authentication
Implement authentication to protect your MCP server:
OAuth 2.1 (Recommended)
API Key Authentication
JWT Token Validation
import { FastMCP , GoogleProvider , requireAuth } from "fastmcp" ;
const server = new FastMCP ({
name: "Production Server" ,
version: "1.0.0" ,
auth: new GoogleProvider ({
baseUrl: "https://mcp.yourdomain.com" ,
clientId: process . env . GOOGLE_CLIENT_ID ! ,
clientSecret: process . env . GOOGLE_CLIENT_SECRET ! ,
}),
});
server . addTool ({
name: "protected_tool" ,
description: "Requires authentication" ,
canAccess: requireAuth ,
execute : async () => "Authenticated access" ,
});
Environment Variables
Never hardcode secrets:
// ❌ Bad - hardcoded secrets
const server = new FastMCP ({
auth: new GoogleProvider ({
clientId: "123456.apps.googleusercontent.com" ,
clientSecret: "GOCSPX-abc123def456" ,
baseUrl: "https://example.com" ,
}),
});
// ✅ Good - use environment variables
const server = new FastMCP ({
auth: new GoogleProvider ({
clientId: process . env . GOOGLE_CLIENT_ID ! ,
clientSecret: process . env . GOOGLE_CLIENT_SECRET ! ,
baseUrl: process . env . BASE_URL ! ,
}),
});
Use a .env file (never commit to git):
GOOGLE_CLIENT_ID = your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET = your-client-secret
BASE_URL = https://mcp.yourdomain.com
API_KEY = your-secure-api-key
DATABASE_URL = postgresql://user:pass@host:5432/db
Add to .gitignore:
.env
.env.local
.env.*.local
*.pem
*.key
Rate Limiting
Protect against abuse:
import { ratelimit } from "@hono/rate-limit" ;
const server = new FastMCP ({
name: "Production Server" ,
version: "1.0.0" ,
});
const app = server . getApp ();
// Apply rate limiting
app . use (
"*" ,
ratelimit ({
windowMs: 15 * 60 * 1000 , // 15 minutes
max: 100 , // Limit each IP to 100 requests per windowMs
message: "Too many requests, please try again later" ,
})
);
Health Checks and Monitoring
Health Check Endpoints
Configure health and readiness checks:
const server = new FastMCP ({
name: "Production Server" ,
version: "1.0.0" ,
health: {
enabled: true ,
path: "/health" ,
message: "healthy" ,
status: 200 ,
},
});
Basic Health Check
Readiness Check
Kubernetes Probes
curl https://mcp.yourdomain.com/health
# Response: healthy
Structured Logging
Implement comprehensive logging:
import { FastMCP , Logger } from "fastmcp" ;
import winston from "winston" ;
const logger = winston . createLogger ({
level: process . env . LOG_LEVEL || "info" ,
format: winston . format . combine (
winston . format . timestamp (),
winston . format . errors ({ stack: true }),
winston . format . json ()
),
transports: [
new winston . transports . File ({ filename: "error.log" , level: "error" }),
new winston . transports . File ({ filename: "combined.log" }),
],
});
// Add console in development
if ( process . env . NODE_ENV !== "production" ) {
logger . add ( new winston . transports . Console ({
format: winston . format . simple (),
}));
}
class WinstonLogger implements Logger {
debug ( ... args : unknown []) : void {
logger . debug ( args . join ( " " ));
}
error ( ... args : unknown []) : void {
logger . error ( args . join ( " " ));
}
info ( ... args : unknown []) : void {
logger . info ( args . join ( " " ));
}
log ( ... args : unknown []) : void {
logger . info ( args . join ( " " ));
}
warn ( ... args : unknown []) : void {
logger . warn ( args . join ( " " ));
}
}
const server = new FastMCP ({
name: "Production Server" ,
version: "1.0.0" ,
logger: new WinstonLogger (),
});
Error Tracking
Integrate with error tracking services:
import * as Sentry from "@sentry/node" ;
Sentry . init ({
dsn: process . env . SENTRY_DSN ,
environment: process . env . NODE_ENV ,
tracesSampleRate: 1.0 ,
});
server . addTool ({
name: "monitored_tool" ,
description: "Tool with error tracking" ,
execute : async ({ input }) => {
const transaction = Sentry . startTransaction ({
op: "tool.execute" ,
name: "monitored_tool" ,
});
try {
// Tool logic
const result = await processInput ( input );
transaction . setStatus ( "ok" );
return result ;
} catch ( error ) {
transaction . setStatus ( "internal_error" );
Sentry . captureException ( error );
throw error ;
} finally {
transaction . finish ();
}
},
});
Connection Pooling
Reuse database and HTTP connections:
import { Pool } from "pg" ;
// Create connection pool outside request handlers
const dbPool = new Pool ({
connectionString: process . env . DATABASE_URL ,
max: 20 ,
idleTimeoutMillis: 30000 ,
connectionTimeoutMillis: 2000 ,
});
server . addTool ({
name: "query_database" ,
description: "Execute database query" ,
execute : async ({ query }) => {
const client = await dbPool . connect ();
try {
const result = await client . query ( query );
return JSON . stringify ( result . rows );
} finally {
client . release ();
}
},
});
Caching
Implement caching for expensive operations:
import { LRUCache } from "lru-cache" ;
const cache = new LRUCache < string , string >({
max: 500 ,
ttl: 1000 * 60 * 5 , // 5 minutes
});
server . addTool ({
name: "cached_operation" ,
description: "Expensive operation with caching" ,
execute : async ({ key }) => {
// Check cache first
const cached = cache . get ( key );
if ( cached ) {
return cached ;
}
// Perform expensive operation
const result = await expensiveOperation ( key );
// Store in cache
cache . set ( key , result );
return result ;
},
});
Timeout Configuration
Set appropriate timeouts:
server . addTool ({
name: "api_call" ,
description: "Call external API with timeout" ,
execute : async ({ url }) => {
const controller = new AbortController ();
const timeout = setTimeout (() => controller . abort (), 5000 ); // 5s timeout
try {
const response = await fetch ( url , {
signal: controller . signal ,
});
return await response . text ();
} catch ( error ) {
if ( error . name === "AbortError" ) {
throw new UserError ( "Request timeout after 5 seconds" );
}
throw error ;
} finally {
clearTimeout ( timeout );
}
},
});
Ping Configuration
Optimize ping behavior for your transport:
const server = new FastMCP ({
name: "Production Server" ,
version: "1.0.0" ,
ping: {
enabled: true ,
intervalMs: 10000 , // 10 seconds
logLevel: "debug" , // Don't spam logs
},
});
Process Management
Using PM2
Keep your server running with PM2:
# Install PM2
npm install -g pm2
# Start server
pm2 start dist/server.js --name fastmcp-server
# Configure auto-restart
pm2 startup
pm2 save
# Monitor
pm2 monit
# View logs
pm2 logs fastmcp-server
PM2 ecosystem file:
module . exports = {
apps: [{
name: "fastmcp-server" ,
script: "./dist/server.js" ,
instances: "max" ,
exec_mode: "cluster" ,
env: {
NODE_ENV: "production" ,
PORT: 8080 ,
},
error_file: "./logs/err.log" ,
out_file: "./logs/out.log" ,
log_date_format: "YYYY-MM-DD HH:mm:ss Z" ,
max_memory_restart: "1G" ,
}],
};
Using systemd
Create a systemd service:
/etc/systemd/system/fastmcp.service
[Unit]
Description =FastMCP Server
After =network.target
[Service]
Type =simple
User =fastmcp
WorkingDirectory =/opt/fastmcp
Environment = NODE_ENV =production
ExecStart =/usr/bin/node dist/server.js
Restart =always
RestartSec =10
[Install]
WantedBy =multi-user.target
# Enable and start service
sudo systemctl enable fastmcp
sudo systemctl start fastmcp
# Check status
sudo systemctl status fastmcp
# View logs
sudo journalctl -u fastmcp -f
Production Checklist
Next Steps