Skip to main content

Configuration

This guide covers advanced configuration options, deployment strategies, and best practices for production MCP servers.

Server Configuration

Basic Configuration

The server accepts configuration during initialization:
import { createMCPServer } from 'mcp-use/server'

const server = createMCPServer('my-server', {
  version: '1.0.0',              // Semantic version
  description: 'Server purpose',  // Human-readable description
  host: 'localhost',             // Hostname (defaults to 'localhost')
  baseUrl: process.env.MCP_URL || "http://localhost:3000", // MCP Server production URL (needed for UI Widgets to work correctly)
  allowedOrigins: process.env.NODE_ENV === 'production' 
    ? ['https://myapp.com']      // Production: explicit origins
    : undefined,                 // Development: allows all origins
  sessionIdleTimeoutMs: 300000,  // Session idle timeout (default: 5 minutes)
  autoCreateSessionOnInvalidId: true  // Auto-create session on invalid ID (default: true, compatible with ChatGPT)
})

Environment Variables

Use environment variables for configuration:
// .env file
PORT=3000
NODE_ENV=production
LOG_LEVEL=info
DATABASE_URL=postgresql://user:pass@localhost:5432/db
API_KEY=your-secret-api-key
MCP_URL=https://api.example.com

// In your server
import dotenv from 'dotenv'
dotenv.config()

const config = {
  port: parseInt(process.env.PORT || '3000'),
  environment: process.env.NODE_ENV || 'development',
  logLevel: process.env.LOG_LEVEL || 'info',
  database: process.env.DATABASE_URL,
  apiKey: process.env.API_KEY,
  baseUrl: process.env.MCP_URL
}

const server = createMCPServer('configured-server', {
  version: '1.0.0',
  description: `Server running in ${config.environment} mode`
})

// Use config throughout your server
server.listen(config.port)

Configuration File

Load configuration from JSON or YAML:
// config.json
{
  "server": {
    "name": "my-mcp-server",
    "version": "1.0.0",
    "port": 3000
  },
  "features": {
    "enableInspector": true,
    "enableMetrics": true,
    "enableCaching": true
  },
  "security": {
    "corsOrigins": ["http://localhost:3000"],
    "rateLimit": {
      "windowMs": 900000,
      "max": 100
    }
  }
}

// Load in server
import config from './config.json'

const server = createMCPServer(config.server.name, {
  version: config.server.version
})

// Apply configuration
if (config.features.enableInspector) {
  // Inspector will auto-mount if available
}

server.listen(config.server.port)

Express Middleware Configuration

CORS Configuration

Configure Cross-Origin Resource Sharing:
import cors from 'cors'

// Custom CORS configuration
server.use(cors({
  origin: (origin, callback) => {
    const allowedOrigins = [
      'http://localhost:3000',
      'https://app.example.com'
    ]

    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'mcp-protocol-version']
}))

Rate Limiting

Implement rate limiting for API protection:
import rateLimit from 'express-rate-limit'

// General rate limiter
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP'
})

// Apply to all routes
server.use('/api/', limiter)

// Stricter limit for specific endpoints
const strictLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10
})

server.use('/api/expensive-operation', strictLimiter)

Authentication Middleware

Add authentication to your server:
// Basic API key authentication
function authenticateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key']

  if (!apiKey || apiKey !== process.env.API_KEY) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  next()
}

// Apply to specific routes
server.use('/api/admin', authenticateApiKey)

// JWT authentication
import jwt from 'jsonwebtoken'

function authenticateJWT(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1]

  if (!token) {
    return res.status(401).json({ error: 'No token provided' })
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    req.user = decoded
    next()
  } catch (error) {
    return res.status(403).json({ error: 'Invalid token' })
  }
}

// Protected routes
server.get('/api/protected', authenticateJWT, (req, res) => {
  res.json({ user: req.user })
})

Logging Configuration

Set up comprehensive logging:
import winston from 'winston'
import morgan from 'morgan'

// Configure Winston logger
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.Console({
      format: winston.format.simple()
    }),
    new winston.transports.File({
      filename: 'error.log',
      level: 'error'
    }),
    new winston.transports.File({
      filename: 'combined.log'
    })
  ]
})

// HTTP request logging with Morgan
const morganFormat = ':method :url :status :response-time ms - :res[content-length]'

server.use(morgan(morganFormat, {
  stream: {
    write: (message) => logger.info(message.trim())
  }
}))

// Use logger in tools
server.tool({
  name: 'example_tool',
  cb: async (params) => {
    logger.info('Tool executed', { tool: 'example_tool', params })

    try {
      // Tool logic
      const result = await performOperation(params)
      logger.debug('Tool result', { result })

      return { content: [{ type: 'text', text: result }] }
    } catch (error) {
      logger.error('Tool error', { error: error.message, stack: error.stack })
      throw error
    }
  }
})

Security Configuration

DNS Rebinding Protection

The server supports DNS rebinding protection through the allowedOrigins configuration option. This is important for security when exposing MCP servers over HTTP. Development vs Production Behavior:
  • Development mode (NODE_ENV !== “production”):
    • If allowedOrigins is not set: All origins are allowed (DNS rebinding protection disabled)
    • This enables direct browser connections from any origin for easier development
    • Perfect for local development with tools like the MCP Inspector
  • Production mode (NODE_ENV === “production”):
    • If allowedOrigins is not set: DNS rebinding protection is disabled (not recommended)
    • If set to empty array: DNS rebinding protection is disabled
    • If set with origins: DNS rebinding protection is enabled with those specific origins
    • Always explicitly set allowed origins in production
Example Configuration:
import { createMCPServer } from 'mcp-use/server'

// Development: No configuration needed (allows all origins)
const devServer = createMCPServer('dev-server', {
  version: '1.0.0'
})
// Works with direct browser connections, inspector, etc.

// Production: Explicitly set allowed origins
const prodServer = createMCPServer('prod-server', {
  version: '1.0.0',
  allowedOrigins: [
    'https://myapp.com',
    'https://app.myapp.com',
    'https://admin.myapp.com'
  ]
})
// Only allows connections from these specific origins

// Environment-based configuration
const server = createMCPServer('my-server', {
  version: '1.0.0',
  allowedOrigins: process.env.NODE_ENV === 'production'
    ? process.env.ALLOWED_ORIGINS?.split(',') || []
    : undefined // Development: allow all origins
})
Security Best Practices:
  1. Always set allowedOrigins in production - Never leave it undefined in production environments
  2. Use environment variables - Store allowed origins in environment variables, not in code
  3. Be specific - Only include origins that actually need access
  4. Include protocol - Always specify the full origin including protocol (http/https)
  5. Test in production mode - Test your server with NODE_ENV=production to ensure proper origin validation
Note: The server exposes MCP endpoints at both /mcp and /sse for compatibility. Both endpoints respect the same allowedOrigins configuration.

Session Management

The server manages client sessions to maintain state across multiple requests. Sessions are stored in memory and can be configured with idle timeouts and reconnection behavior. Session Configuration Options:
  • sessionIdleTimeoutMs: Controls how long a session remains active when idle (default: 5 minutes)
  • autoCreateSessionOnInvalidId: Controls behavior when a client sends a request with an invalid or expired session ID
Session Reconnection Behavior: By default, the server automatically creates a new session when it receives a request with an invalid or expired session ID. This enables seamless reconnection after server restarts and provides compatibility with clients that don’t properly handle session expiration. According to the MCP protocol specification, clients MUST start a new session by sending a new InitializeRequest when they receive HTTP 404 in response to a request containing an MCP-Session-Id. However, some clients (like ChatGPT) don’t properly implement this behavior and fail to reconnect.
import { createMCPServer } from 'mcp-use/server'

// Default behavior (compatible with ChatGPT and other non-compliant clients)
// Automatically creates new session when invalid session ID is detected
const compatibleServer = createMCPServer('my-server', {
  version: '1.0.0',
  autoCreateSessionOnInvalidId: true  // Default: true
})

// Use strict MCP spec behavior (requires compliant clients)
// Returns 404 for invalid session IDs, requires explicit initialize
const specCompliantServer = createMCPServer('my-server', {
  version: '1.0.0',
  autoCreateSessionOnInvalidId: false  // Strict MCP spec compliance
})
When to Use Each Option:
  • autoCreateSessionOnInvalidId: true (default):
    • Enables seamless reconnection after server restarts
    • Compatible with non-compliant clients like ChatGPT
    • Clients can continue using old session IDs (server auto-creates new session)
    • Recommended for most use cases, especially when using ChatGPT or other OpenAI clients
  • autoCreateSessionOnInvalidId: false:
    • Follows MCP protocol specification strictly
    • Clients must explicitly send initialize request after server restart
    • More predictable behavior for protocol-compliant clients
    • Use only if you’re certain all clients properly handle 404 errors by reinitializing
Example: Handling Server Restarts
// Server with auto-reconnection enabled
const server = createMCPServer('my-server', {
  version: '1.0.0',
  sessionIdleTimeoutMs: 300000,  // 5 minutes
  autoCreateSessionOnInvalidId: true  // Auto-create on invalid session
})

// When server restarts:
// 1. Client sends request with old session ID
// 2. Server detects invalid session ID
// 3. Server automatically creates new session
// 4. Request proceeds normally
// 5. Client receives new session ID in response headers
Best Practices:
  1. Use default behavior (true) in most cases - Provides compatibility with ChatGPT and other common clients
  2. Set to false only for compliant clients - Only disable if you’re certain all clients properly reinitialize on 404
  3. Monitor session creation - Log warnings when auto-creating sessions to detect connection issues
  4. Set appropriate timeouts - Configure sessionIdleTimeoutMs based on your application’s needs
  5. Reference the MCP spec - Understand that clients should reinitialize on 404 per the MCP protocol specification

Input Validation

Validate and sanitize all inputs:
import validator from 'validator'
import DOMPurify from 'isomorphic-dompurify'

server.tool({
  name: 'process_input',
  inputs: [
    { name: 'email', type: 'string', required: true },
    { name: 'url', type: 'string', required: true },
    { name: 'html', type: 'string', required: false }
  ],
  cb: async ({ email, url, html }) => {
    // Validate email
    if (!validator.isEmail(email)) {
      return {
        content: [{
          type: 'text',
          text: 'Invalid email address'
        }]
      }
    }

    // Validate URL
    if (!validator.isURL(url, { require_protocol: true })) {
      return {
        content: [{
          type: 'text',
          text: 'Invalid URL'
        }]
      }
    }

    // Sanitize HTML if provided
    const cleanHtml = html ? DOMPurify.sanitize(html) : ''

    // Process validated inputs
    return {
      content: [{
        type: 'text',
        text: 'Inputs validated and processed'
      }]
    }
  }
})

Secure Headers

Add security headers:
import helmet from 'helmet'

// Basic security headers
server.use(helmet())

// Custom CSP for widgets
server.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net'],
    styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'"],
    fontSrc: ["'self'", 'https://fonts.gstatic.com'],
    frameSrc: ["'self'"],
    frameAncestors: ["'none'"]
  }
}))

Performance Configuration

Caching

Implement caching for expensive operations:
import NodeCache from 'node-cache'

const cache = new NodeCache({
  stdTTL: 600,      // 10 minutes default TTL
  checkperiod: 120, // Check for expired keys every 2 minutes
  useClones: false  // Don't clone objects (better performance)
})

// Cache middleware
function cacheMiddleware(key: string, ttl?: number) {
  return (req, res, next) => {
    const cacheKey = `${key}:${JSON.stringify(req.params)}:${JSON.stringify(req.query)}`
    const cached = cache.get(cacheKey)

    if (cached) {
      return res.json(cached)
    }

    // Store original json method
    const originalJson = res.json.bind(res)

    // Override json method to cache response
    res.json = (data) => {
      cache.set(cacheKey, data, ttl)
      return originalJson(data)
    }

    next()
  }
}

// Use cache middleware
server.get('/api/expensive',
  cacheMiddleware('expensive-operation', 300),
  async (req, res) => {
    const result = await performExpensiveOperation()
    res.json(result)
  }
)

Database Connection Pooling

Configure database connections efficiently:
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,                    // Maximum pool size
  idleTimeoutMillis: 30000,   // Close idle clients after 30s
  connectionTimeoutMillis: 2000, // Timeout after 2s
})

// Monitor pool health
pool.on('error', (err, client) => {
  console.error('Unexpected error on idle client', err)
})

// Graceful shutdown
process.on('SIGTERM', async () => {
  await pool.end()
  process.exit(0)
})

Deployment Configuration

Docker Configuration

Create a Dockerfile for containerization:
# Dockerfile
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY tsconfig.json ./

# Install dependencies
RUN npm ci --only=production

# Copy source code
COPY src ./src
COPY dist ./dist

# Build TypeScript
RUN npm run build

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"

# Run server
CMD ["node", "dist/index.js"]
Docker Compose configuration:
# docker-compose.yml
version: '3.8'

services:
  mcp-server:
    build: .
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://user:pass@db:5432/mydb
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

PM2 Configuration

Use PM2 for process management:
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'mcp-server',
    script: './dist/index.js',
    instances: 'max',  // Use all CPU cores
    exec_mode: 'cluster',
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'production',
      PORT: 3000
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_file: './logs/combined.log',
    time: true
  }]
}
Start with PM2:
pm2 start ecosystem.config.js
pm2 save
pm2 startup

Kubernetes Configuration

Deploy to Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mcp-server
  template:
    metadata:
      labels:
        app: mcp-server
    spec:
      containers:
      - name: mcp-server
        image: your-registry/mcp-server:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: mcp-secrets
              key: database-url
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: mcp-server
spec:
  selector:
    app: mcp-server
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

Monitoring and Metrics

Health Check Endpoint

Implement comprehensive health checks:
server.get('/health', async (req, res) => {
  const health = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    service: 'mcp-server',
    version: '1.0.0',
    checks: {}
  }

  // Check database connection
  try {
    await pool.query('SELECT 1')
    health.checks.database = 'healthy'
  } catch (error) {
    health.checks.database = 'unhealthy'
    health.status = 'degraded'
  }

  // Check external API
  try {
    const response = await fetch('https://api.example.com/health')
    health.checks.externalApi = response.ok ? 'healthy' : 'unhealthy'
  } catch (error) {
    health.checks.externalApi = 'unhealthy'
    health.status = 'degraded'
  }

  // Return appropriate status code
  const statusCode = health.status === 'healthy' ? 200 : 503
  res.status(statusCode).json(health)
})

Metrics Collection

Collect and expose metrics:
import promClient from 'prom-client'

// Create a Registry
const register = new promClient.Registry()

// Add default metrics
promClient.collectDefaultMetrics({ register })

// Custom metrics
const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status'],
  buckets: [0.1, 0.5, 1, 2, 5]
})
register.registerMetric(httpRequestDuration)

// Middleware to track metrics
server.use((req, res, next) => {
  const start = Date.now()

  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000
    httpRequestDuration
      .labels(req.method, req.route?.path || 'unknown', res.statusCode.toString())
      .observe(duration)
  })

  next()
})

// Metrics endpoint
server.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType)
  const metrics = await register.metrics()
  res.end(metrics)
})

Client Configuration

MCP Client Setup

Configure MCP clients to connect to your server:
{
  "mcpServers": {
    "my-server": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer your-token",
        "X-API-Key": "your-api-key"
      },
      "timeout": 30000,
      "retryAttempts": 3
    }
  }
}

Best Practices

  1. Use Environment Variables: Never hardcode secrets
  2. Implement Health Checks: Monitor service health
  3. Add Logging: Log all important operations
  4. Rate Limiting: Protect against abuse
  5. Input Validation: Always validate user input
  6. Error Handling: Graceful error recovery
  7. Caching: Cache expensive operations
  8. Security Headers: Use Helmet.js
  9. Process Management: Use PM2 or similar
  10. Monitoring: Implement metrics and alerts

Next Steps