6.3 KiB
6.3 KiB
Deployment Guide for TypeScript MCP Servers
Complete guide to deploying MCP servers on Cloudflare Workers.
Quick Deployment
# Build
npm run build
# Deploy
wrangler deploy
Environment Setup
Development (.dev.vars)
Create .dev.vars for local secrets:
WEATHER_API_KEY=abc123
DATABASE_URL=http://localhost:3306
Never commit .dev.vars to git!
Production Secrets
# Set secrets
wrangler secret put WEATHER_API_KEY
wrangler secret put DATABASE_URL
# List secrets
wrangler secret list
# Delete secret
wrangler secret delete OLD_KEY
Multiple Environments
wrangler.jsonc:
{
"name": "mcp-server",
"main": "src/index.ts",
"env": {
"staging": {
"name": "mcp-server-staging",
"vars": {
"ENVIRONMENT": "staging"
},
"d1_databases": [
{ "binding": "DB", "database_id": "staging-db-id" }
]
},
"production": {
"name": "mcp-server-production",
"vars": {
"ENVIRONMENT": "production"
},
"d1_databases": [
{ "binding": "DB", "database_id": "prod-db-id" }
]
}
}
}
Deploy to specific environment:
wrangler deploy --env staging
wrangler deploy --env production
Custom Domains
Setup
-
Add domain in Cloudflare dashboard:
- Workers & Pages → your worker → Settings → Domains & Routes
- Add custom domain:
mcp.example.com
-
Or via wrangler.jsonc:
{
"routes": [
{
"pattern": "mcp.example.com/*",
"custom_domain": true
}
]
}
SSL/TLS
Cloudflare provides automatic SSL certificates for custom domains.
CI/CD with GitHub Actions
.github/workflows/deploy.yml:
name: Deploy MCP Server
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env production
Setup Secrets
-
Get Cloudflare API token:
- Dashboard → My Profile → API Tokens
- Create token with "Edit Cloudflare Workers" permissions
-
Add to GitHub:
- Repository → Settings → Secrets → Actions
- Add
CLOUDFLARE_API_TOKEN - Add
CLOUDFLARE_ACCOUNT_ID
Database Migrations
D1 Migrations
Create migration:
wrangler d1 migrations create my-db add-users-table
migrations/0001_add_users_table.sql:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Apply migrations:
# Local
wrangler d1 migrations apply my-db --local
# Production
wrangler d1 migrations apply my-db --remote
In CI/CD:
- name: Run D1 migrations
run: wrangler d1 migrations apply my-db --remote
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Monitoring & Logs
Real-time Logs
# Tail logs
wrangler tail
# Filter by status
wrangler tail --status error
# Filter by method
wrangler tail --method POST
Workers Analytics
View in dashboard:
- Requests per second
- Error rate
- CPU time
- Bandwidth
Custom Logging
app.post('/mcp', async (c) => {
console.log('MCP request:', {
method: c.req.method,
path: c.req.path,
timestamp: new Date().toISOString()
});
// ... handle request
console.log('MCP response:', { status: 200, duration: '15ms' });
});
Rollback Strategy
Quick Rollback
# List deployments
wrangler deployments list
# Rollback to specific deployment
wrangler rollback --deployment-id abc123
Git-based Rollback
# Revert to previous commit
git revert HEAD
git push
# CI/CD will auto-deploy reverted version
Performance Optimization
1. Enable Compression
Cloudflare automatically compresses responses. No configuration needed.
2. Caching
app.get('/mcp-schema', async (c) => {
const schema = { ... };
return c.json(schema, 200, {
'Cache-Control': 'public, max-age=3600',
'CDN-Cache-Control': 'max-age=86400'
});
});
3. Edge Caching with KV
async function getCachedOrFetch(key: string, fetcher: () => Promise<string>, env: Env) {
const cached = await env.CACHE.get(key);
if (cached) return cached;
const fresh = await fetcher();
await env.CACHE.put(key, fresh, { expirationTtl: 3600 });
return fresh;
}
Health Checks
app.get('/health', (c) => {
return c.json({
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0'
});
});
Monitor with UptimeRobot, Pingdom, etc.
Cost Optimization
Workers Pricing
- Free: 100,000 requests/day
- Paid: $5/month + $0.50/million requests
Tips
- Use KV for caching (reduces computation)
- Optimize D1 queries (use indexes)
- Batch operations where possible
- Set reasonable rate limits
- Monitor usage in dashboard
Security Checklist
Before production:
- Authentication implemented
- Rate limiting enabled
- CORS configured correctly
- Secrets in Wrangler secrets (not code)
- Error messages don't leak data
- HTTPS only (enforced by CF)
- Input validation on all tools
- SQL injection protection
- API keys rotated regularly
Troubleshooting Deployments
Deployment Fails
# Check syntax
npm run build
# Validate wrangler.jsonc
wrangler deploy --dry-run
# View detailed logs
wrangler deploy --verbose
Worker Not Responding
# Check logs
wrangler tail
# Test locally first
wrangler dev
# Verify bindings
wrangler d1 list
wrangler kv namespace list
Performance Issues
# Check CPU time
wrangler tail --status ok | grep "CPU time"
# Profile with Analytics
# Dashboard → Workers → Analytics
Last Updated: 2025-10-28