Configuration ReferenceΒΆ
This guide provides comprehensive configuration options for MCP Gateway, including database setup, environment variables, and deployment-specific settings.
π Database ConfigurationΒΆ
MCP Gateway supports multiple database backends with full feature parity across all supported systems.
Supported DatabasesΒΆ
| Database | Support Level | Connection String Example | Notes |
|---|---|---|---|
| SQLite | β Full | sqlite:///./mcp.db | Default, file-based |
| PostgreSQL | β Full | postgresql+psycopg://postgres:changeme@localhost:5432/mcp | Recommended for production |
| MariaDB | β Full | mysql+pymysql://mysql:changeme@localhost:3306/mcp | 36+ tables, MariaDB 10.6+ |
| MySQL | β Full | mysql+pymysql://admin:changeme@localhost:3306/mcp | Alternative MySQL variant |
PostgreSQL System DependenciesΒΆ
Required: libpq Development Headers
The PostgreSQL adapter (psycopg[c]) requires the libpq development headers to compile. Install them before running pip install .[postgres]:
After installing the system dependencies, install the Python package:
MariaDB/MySQL Setup DetailsΒΆ
MariaDB & MySQL Full Support
MariaDB and MySQL are fully supported alongside SQLite and PostgreSQL:
- 36+ database tables work perfectly with MariaDB 10.6+ and MySQL 8.0+
- All VARCHAR length issues have been resolved for MariaDB/MySQL compatibility
- Complete feature parity with SQLite and PostgreSQL
- Supports all MCP Gateway features including federation, caching, and A2A agents
Connection String FormatΒΆ
Local MariaDB/MySQL InstallationΒΆ
# Install MariaDB server
sudo apt update && sudo apt install mariadb-server
# Secure installation (optional)
sudo mariadb-secure-installation
# Create database and user
sudo mariadb -e "CREATE DATABASE mcp;"
sudo mariadb -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
sudo mariadb -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
sudo mariadb -e "FLUSH PRIVILEGES;"
# Install MySQL server
sudo apt update && sudo apt install mysql-server
# Secure installation (optional)
sudo mysql_secure_installation
# Create database and user
sudo mysql -e "CREATE DATABASE mcp;"
sudo mysql -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
sudo mysql -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"
# Install MariaDB server
sudo dnf install mariadb-server
sudo systemctl start mariadb
sudo systemctl enable mariadb
# Create database and user
sudo mariadb -e "CREATE DATABASE mcp;"
sudo mariadb -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
sudo mariadb -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
sudo mariadb -e "FLUSH PRIVILEGES;"
# Install MySQL server
sudo dnf install mysql-server # or: sudo yum install mysql-server
sudo systemctl start mysqld
sudo systemctl enable mysqld
# Create database and user
sudo mysql -e "CREATE DATABASE mcp;"
sudo mysql -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
sudo mysql -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"
# Install MariaDB
brew install mariadb
brew services start mariadb
# Create database and user
mariadb -u root -e "CREATE DATABASE mcp;"
mariadb -u root -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
mariadb -u root -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
mariadb -u root -e "FLUSH PRIVILEGES;"
# Install MySQL
brew install mysql
brew services start mysql
# Create database and user
mysql -u root -e "CREATE DATABASE mcp;"
mysql -u root -e "CREATE USER 'mysql'@'localhost' IDENTIFIED BY 'changeme';"
mysql -u root -e "GRANT ALL PRIVILEGES ON mcp.* TO 'mysql'@'localhost';"
mysql -u root -e "FLUSH PRIVILEGES;"
Docker MariaDB/MySQL SetupΒΆ
# Start MariaDB container (recommended)
docker run -d --name mariadb-mcp \
-e MYSQL_ROOT_PASSWORD=mysecretpassword \
-e MYSQL_DATABASE=mcp \
-e MYSQL_USER=mysql \
-e MYSQL_PASSWORD=changeme \
-p 3306:3306 \
registry.redhat.io/rhel9/mariadb-106:12.0.2-ubi10
# Or start MySQL container
docker run -d --name mysql-mcp \
-e MYSQL_ROOT_PASSWORD=mysecretpassword \
-e MYSQL_DATABASE=mcp \
-e MYSQL_USER=mysql \
-e MYSQL_PASSWORD=changeme \
-p 3306:3306 \
mysql:8
# Connection string for MCP Gateway (same for both)
DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp
π§ Core Environment VariablesΒΆ
Database SettingsΒΆ
# Database connection (choose one)
DATABASE_URL=sqlite:///./mcp.db # SQLite (default)
DATABASE_URL=mysql+pymysql://mysql:changeme@localhost:3306/mcp # MariaDB/MySQL
DATABASE_URL=postgresql+psycopg://postgres:changeme@localhost:5432/mcp # PostgreSQL
# Connection pool settings (optional)
DB_POOL_SIZE=200
DB_MAX_OVERFLOW=5
DB_POOL_TIMEOUT=60
DB_POOL_RECYCLE=3600
DB_MAX_RETRIES=5
DB_RETRY_INTERVAL_MS=2000
# psycopg3 auto-prepared statements (PostgreSQL only)
# Queries executed N+ times are prepared server-side for performance
DB_PREPARE_THRESHOLD=5
Server ConfigurationΒΆ
# Network binding & runtime
HOST=0.0.0.0
PORT=4444
ENVIRONMENT=development
APP_DOMAIN=localhost
APP_ROOT_PATH=
# HTTP Server selection (for containers)
HTTP_SERVER=gunicorn # Options: gunicorn (default), granian
Gunicorn Production Server (Default)ΒΆ
The production server uses Gunicorn with UVicorn workers by default. Configure via environment variables or .env file:
# Worker Configuration
GUNICORN_WORKERS=auto # Number of workers ("auto" = 2*CPU+1, capped at 16)
GUNICORN_TIMEOUT=600 # Worker timeout in seconds (increase for long requests)
GUNICORN_MAX_REQUESTS=100000 # Requests per worker before restart (prevents memory leaks)
GUNICORN_MAX_REQUESTS_JITTER=100 # Random jitter to prevent thundering herd
# Performance Options
GUNICORN_PRELOAD_APP=true # Preload app before forking (saves memory, runs migrations once)
GUNICORN_DEV_MODE=false # Enable hot reload (not for production!)
DISABLE_ACCESS_LOG=true # Disable access logs for performance (default: true)
# TLS/SSL Configuration
SSL=false # Enable TLS/SSL
CERT_FILE=certs/cert.pem # Path to SSL certificate
KEY_FILE=certs/key.pem # Path to SSL private key
KEY_FILE_PASSWORD= # Passphrase for encrypted private key
# Process Management
FORCE_START=false # Bypass lock file check
Starting the Production Server:
# Basic startup
./run-gunicorn.sh
# With TLS
SSL=true ./run-gunicorn.sh
# With custom workers
GUNICORN_WORKERS=8 ./run-gunicorn.sh
# Use fixed worker count instead of auto-detection
GUNICORN_WORKERS=4 ./run-gunicorn.sh
# High-performance mode (disable access logs)
DISABLE_ACCESS_LOG=true ./run-gunicorn.sh
Worker Count Recommendations
- CPU-bound workloads: 2-4 Γ CPU cores
- I/O-bound workloads: 4-12 Γ CPU cores
- Memory-constrained: Start with 2 and monitor
- Auto mode: Uses formula
min(2*CPU+1, 16)
Granian Production Server (Alternative)ΒΆ
Granian is a Rust-based HTTP server available as an alternative to Gunicorn. It offers native HTTP/2 support and lower memory usage.
# Worker Configuration
GRANIAN_WORKERS=auto # Number of workers (auto = CPU cores, max 16)
GRANIAN_RUNTIME_MODE=auto # Runtime mode: auto, mt (multi-threaded), st (single-threaded)
GRANIAN_RUNTIME_THREADS=1 # Runtime threads per worker
GRANIAN_BLOCKING_THREADS=1 # Blocking threads per worker
# Performance Options
GRANIAN_HTTP=auto # HTTP version: auto, 1, 2
GRANIAN_LOOP=uvloop # Event loop: uvloop, asyncio, rloop
GRANIAN_BACKLOG=2048 # Connection backlog
GRANIAN_BACKPRESSURE=512 # Max concurrent requests per worker
GRANIAN_RESPAWN_FAILED=true # Auto-restart failed workers
GRANIAN_DEV_MODE=false # Enable hot reload
DISABLE_ACCESS_LOG=true # Disable access logs for performance
# TLS/SSL (same as Gunicorn)
SSL=false
CERT_FILE=certs/cert.pem
KEY_FILE=certs/key.pem
Starting with Granian:
# Local development
make serve-granian
# With HTTP/2 + TLS
make serve-granian-http2
# Container with Granian
docker run -e HTTP_SERVER=granian mcpgateway/mcpgateway
When to use Granian
- Native HTTP/2 without reverse proxy
- Lower memory usage (~40MB vs ~80MB per worker)
- Simpler deployment for smaller instances
See ADR-0025 for detailed comparison.
Authentication & SecurityΒΆ
# JWT Algorithm Configuration
JWT_ALGORITHM=HS256 # HMAC: HS256, HS384, HS512 | RSA: RS256, RS384, RS512 | ECDSA: ES256, ES384, ES512
# Symmetric (HMAC) JWT Configuration - Default
JWT_SECRET_KEY=your-secret-key-here # Required for HMAC algorithms (HS256, HS384, HS512)
# Asymmetric (RSA/ECDSA) JWT Configuration - Enterprise
JWT_PUBLIC_KEY_PATH=jwt/public.pem # Required for asymmetric algorithms (RS*/ES*)
JWT_PRIVATE_KEY_PATH=jwt/private.pem # Required for asymmetric algorithms (RS*/ES*)
# JWT Claims & Validation
JWT_AUDIENCE=mcpgateway-api
JWT_ISSUER=mcpgateway
JWT_AUDIENCE_VERIFICATION=true # Set to false for Dynamic Client Registration
REQUIRE_TOKEN_EXPIRATION=true
# Basic Auth (Admin UI)
BASIC_AUTH_USER=admin
BASIC_AUTH_PASSWORD=changeme
# Email-based Auth
EMAIL_AUTH_ENABLED=true
PLATFORM_ADMIN_EMAIL=admin@example.com
PLATFORM_ADMIN_PASSWORD=changeme
# Security Features
AUTH_REQUIRED=true
SECURITY_HEADERS_ENABLED=true
CORS_ENABLED=true
CORS_ALLOW_CREDENTIALS=true
ALLOWED_ORIGINS="https://admin.example.com,https://api.example.com"
AUTH_ENCRYPTION_SECRET=$(openssl rand -hex 32)
Feature FlagsΒΆ
# Core Features
MCPGATEWAY_UI_ENABLED=true
MCPGATEWAY_ADMIN_API_ENABLED=true
MCPGATEWAY_UI_AIRGAPPED=false # Use local CDN assets for airgapped deployments
MCPGATEWAY_BULK_IMPORT_ENABLED=true
MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200
# A2A (Agent-to-Agent) Features
MCPGATEWAY_A2A_ENABLED=true
MCPGATEWAY_A2A_MAX_AGENTS=100
MCPGATEWAY_A2A_DEFAULT_TIMEOUT=30
MCPGATEWAY_A2A_MAX_RETRIES=3
MCPGATEWAY_A2A_METRICS_ENABLED=true
# Federation & Discovery
FEDERATION_ENABLED=true
FEDERATION_DISCOVERY=true
FEDERATION_PEERS=["https://gateway-1.internal", "https://gateway-2.internal"]
Airgapped DeploymentsΒΆ
For environments without internet access, the Admin UI can be configured to use local CDN assets instead of external CDNs.
Airgapped Mode Features
When MCPGATEWAY_UI_AIRGAPPED=true:
- All CSS and JavaScript libraries are loaded from local files
- No external CDN connections required (Tailwind, HTMX, CodeMirror, Alpine.js, Chart.js)
- Assets are automatically downloaded during container build
- Total asset size: ~932KB
- Full UI functionality maintained
Container Build Required
Airgapped mode requires building with Containerfile.lite which automatically downloads all CDN assets during the build process. The assets are not included in the Git repository.
Container Build Example:
docker build -f Containerfile.lite -t mcpgateway:airgapped .
docker run -e MCPGATEWAY_UI_AIRGAPPED=true -p 4444:4444 mcpgateway:airgapped
Caching ConfigurationΒΆ
# Cache Backend
CACHE_TYPE=redis # Options: memory, redis, database, none
REDIS_URL=redis://localhost:6379/0
CACHE_PREFIX=mcpgateway
# Cache TTL (seconds)
SESSION_TTL=3600
MESSAGE_TTL=600
RESOURCE_CACHE_TTL=1800
# Redis Connection Pool (performance-tuned defaults)
REDIS_MAX_CONNECTIONS=50 # Pool size per worker
REDIS_SOCKET_TIMEOUT=2.0 # Read/write timeout (seconds)
REDIS_SOCKET_CONNECT_TIMEOUT=2.0 # Connection timeout (seconds)
REDIS_RETRY_ON_TIMEOUT=true # Retry commands on timeout
REDIS_HEALTH_CHECK_INTERVAL=30 # Health check interval (seconds, 0=disabled)
REDIS_DECODE_RESPONSES=true # Return strings instead of bytes
# Redis Parser (ADR-026 - performance optimization)
REDIS_PARSER=auto # auto, hiredis, python (auto uses hiredis if available)
# Redis Leader Election (multi-node deployments)
REDIS_LEADER_TTL=15 # Leader TTL (seconds)
REDIS_LEADER_KEY=gateway_service_leader
REDIS_LEADER_HEARTBEAT_INTERVAL=5 # Heartbeat interval (seconds)
# Authentication Cache (ADR-028 - reduces DB queries per auth from 3-4 to 0-1)
AUTH_CACHE_ENABLED=true # Enable auth data caching (user, team, revocation)
AUTH_CACHE_USER_TTL=60 # User data cache TTL in seconds (10-300)
AUTH_CACHE_REVOCATION_TTL=30 # Token revocation cache TTL (5-120, security-critical)
AUTH_CACHE_TEAM_TTL=60 # Team membership cache TTL in seconds (10-300)
AUTH_CACHE_BATCH_QUERIES=true # Batch auth DB queries into single call
Authentication CacheΒΆ
When AUTH_CACHE_ENABLED=true (default), authentication data is cached to reduce database queries:
- User data: Cached for
AUTH_CACHE_USER_TTLseconds (default: 60) - Team memberships: Cached for
AUTH_CACHE_TEAM_TTLseconds (default: 60) - User roles in teams: Cached for
AUTH_CACHE_ROLE_TTLseconds (default: 60) - User teams list: Cached for
AUTH_CACHE_TEAMS_TTLseconds (default: 60) whenAUTH_CACHE_TEAMS_ENABLED=true - Token revocations: Cached for
AUTH_CACHE_REVOCATION_TTLseconds (default: 30)
The cache uses Redis when available (CACHE_TYPE=redis) and falls back to in-memory caching.
When AUTH_CACHE_BATCH_QUERIES=true (default), the 3 separate authentication database queries are batched into a single query, reducing thread pool contention and connection overhead.
Performance Note: The role cache (AUTH_CACHE_ROLE_TTL) caches get_user_role_in_team() which is called 11+ times per team operation. The teams list cache (AUTH_CACHE_TEAMS_TTL) caches get_user_teams() which is called 20+ times per request for authorization checks. Together, these can reduce "idle in transaction" connections by 50-70% under high load.
Security Note: Keep AUTH_CACHE_REVOCATION_TTL short (30s default) to limit the window where revoked tokens may still work.
See ADR-028 for implementation details.
Registry CacheΒΆ
# Registry Cache (ADR-029 - caches list endpoints for tools, prompts, resources, etc.)
REGISTRY_CACHE_ENABLED=true # Enable registry list caching
REGISTRY_CACHE_TOOLS_TTL=20 # Tools list cache TTL in seconds (5-300)
REGISTRY_CACHE_PROMPTS_TTL=15 # Prompts list cache TTL in seconds (5-300)
REGISTRY_CACHE_RESOURCES_TTL=15 # Resources list cache TTL in seconds (5-300)
REGISTRY_CACHE_AGENTS_TTL=20 # Agents list cache TTL in seconds (5-300)
REGISTRY_CACHE_SERVERS_TTL=20 # Servers list cache TTL in seconds (5-300)
REGISTRY_CACHE_GATEWAYS_TTL=20 # Gateways list cache TTL in seconds (5-300)
REGISTRY_CACHE_CATALOG_TTL=300 # Catalog servers cache TTL in seconds (60-600)
When REGISTRY_CACHE_ENABLED=true (default), the first page of registry list results is cached:
- Tools: Cached for
REGISTRY_CACHE_TOOLS_TTLseconds (default: 20) - Prompts: Cached for
REGISTRY_CACHE_PROMPTS_TTLseconds (default: 15) - Resources: Cached for
REGISTRY_CACHE_RESOURCES_TTLseconds (default: 15) - Agents: Cached for
REGISTRY_CACHE_AGENTS_TTLseconds (default: 20) - Servers: Cached for
REGISTRY_CACHE_SERVERS_TTLseconds (default: 20) - Gateways: Cached for
REGISTRY_CACHE_GATEWAYS_TTLseconds (default: 20) - Catalog: Cached for
REGISTRY_CACHE_CATALOG_TTLseconds (default: 300, longer since external catalog changes infrequently)
Cache is automatically invalidated when items are created, updated, or deleted.
Admin Stats CacheΒΆ
# Admin Stats Cache (ADR-029 - caches admin dashboard statistics)
ADMIN_STATS_CACHE_ENABLED=true # Enable admin stats caching
ADMIN_STATS_CACHE_SYSTEM_TTL=60 # System stats cache TTL in seconds (10-300)
ADMIN_STATS_CACHE_OBSERVABILITY_TTL=30 # Observability stats TTL (10-120)
When ADMIN_STATS_CACHE_ENABLED=true (default), admin dashboard statistics are cached:
- System stats: Cached for
ADMIN_STATS_CACHE_SYSTEM_TTLseconds (default: 60) - Observability: Cached for
ADMIN_STATS_CACHE_OBSERVABILITY_TTLseconds (default: 30)
See ADR-029 for implementation details.
Logging SettingsΒΆ
# Log Level
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
# Log Destinations
LOG_TO_FILE=false
LOG_ROTATION_ENABLED=false
LOG_FILE=mcpgateway.log
LOG_FOLDER=logs
# Structured Logging
LOG_FORMAT=json # json, plain
# Database Log Persistence (disabled by default for performance)
STRUCTURED_LOGGING_DATABASE_ENABLED=false
# Audit Trail Logging (disabled by default for performance)
AUDIT_TRAIL_ENABLED=false
# Security Event Logging (disabled by default for performance)
SECURITY_LOGGING_ENABLED=false
SECURITY_LOGGING_LEVEL=failures_only # all, failures_only, high_severity
Audit Trail LoggingΒΆ
When AUDIT_TRAIL_ENABLED=true, all CRUD operations (create, read, update, delete) on resources are logged to the audit_trails database table. This provides:
- Compliance logging for SOC2, HIPAA, and other regulatory requirements
- Data access tracking - who accessed what resources and when
- Change history - before/after values for updates and deletes
- Admin UI Audit Log Viewer - browse and filter audit entries
Warning: Enabling audit trails causes a database write on every API request, which can significantly impact performance. During load testing, this can generate millions of rows. Only enable for production compliance requirements.
Structured Log Database PersistenceΒΆ
When STRUCTURED_LOGGING_DATABASE_ENABLED=true, logs are persisted to the database enabling:
- Log Search API (
/api/logs/search) - Search logs by level, component, user, time range - Request Tracing (
/api/logs/trace/{correlation_id}) - Trace all logs for a request - Performance Metrics - Aggregated p50/p95/p99 latencies and error rates
- Admin UI Log Viewer - Browse and filter logs in the web interface
When disabled (default), logs only go to console/file. This improves performance by avoiding synchronous database writes on each log entry. Use this setting if you have an external log aggregator (ELK, Datadog, Splunk, etc.).
Development & DebugΒΆ
# Development Mode
ENVIRONMENT=development # development, staging, production
DEV_MODE=true
RELOAD=true
DEBUG=true
# Observability
OTEL_ENABLE_OBSERVABILITY=true
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
LLM Settings (Internal API)ΒΆ
MCP Gateway can act as a unified LLM provider with an OpenAI-compatible API. Configure multiple external LLM providers through the Admin UI and expose them through a single proxy endpoint.
# LLM API Configuration
LLM_API_PREFIX=/v1 # API prefix for internal LLM endpoints
LLM_REQUEST_TIMEOUT=120 # Request timeout for LLM API calls (seconds)
LLM_STREAMING_ENABLED=true # Enable streaming responses
LLM_HEALTH_CHECK_INTERVAL=300 # Provider health check interval (seconds)
# Gateway Provider Settings (for LLM Chat with provider=gateway)
GATEWAY_MODEL=gpt-4o # Default model to use
GATEWAY_BASE_URL= # Base URL (defaults to internal API)
GATEWAY_TEMPERATURE=0.7 # Sampling temperature
Provider Configuration
LLM providers (OpenAI, Azure OpenAI, Anthropic, Ollama, Google, Mistral, Cohere, AWS Bedrock, Groq, etc.) are configured through the Admin UI under LLM Settings > Providers. The settings above control the gateway's internal LLM proxy behavior.
OpenAI-Compatible API Endpoints:
# List available models
curl -H "Authorization: Bearer $TOKEN" http://localhost:4444/v1/models
# Chat completion
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}' \
http://localhost:4444/v1/chat/completions
Admin UI Features:
- Providers: Add, edit, enable/disable, and delete LLM providers
- Models: View, test, and manage models from configured providers
- Health Checks: Monitor provider health with automatic status checks
- Model Discovery: Fetch available models from providers and sync to database
π JWT Configuration ExamplesΒΆ
MCP Gateway supports both symmetric (HMAC) and asymmetric (RSA/ECDSA) JWT algorithms for different deployment scenarios.
HMAC (Symmetric) - Simple DeploymentsΒΆ
Best for single-service deployments where you control both token creation and verification.
# Standard HMAC configuration
JWT_ALGORITHM=HS256
JWT_SECRET_KEY=your-256-bit-secret-key-here
JWT_AUDIENCE=mcpgateway-api
JWT_ISSUER=mcpgateway
JWT_AUDIENCE_VERIFICATION=true
RSA (Asymmetric) - Enterprise DeploymentsΒΆ
Ideal for distributed systems, microservices, and enterprise environments.
# RSA configuration
JWT_ALGORITHM=RS256
JWT_PUBLIC_KEY_PATH=certs/jwt/public.pem # Path to RSA public key
JWT_PRIVATE_KEY_PATH=certs/jwt/private.pem # Path to RSA private key
JWT_AUDIENCE=mcpgateway-api
JWT_ISSUER=mcpgateway
JWT_AUDIENCE_VERIFICATION=true
Generate RSA KeysΒΆ
# Option 1: Use Makefile (Recommended)
make certs-jwt # Generates certs/jwt/{private,public}.pem with proper permissions
# Option 2: Manual generation
mkdir -p certs/jwt
openssl genrsa -out certs/jwt/private.pem 4096
openssl rsa -in certs/jwt/private.pem -pubout -out certs/jwt/public.pem
chmod 600 certs/jwt/private.pem
chmod 644 certs/jwt/public.pem
ECDSA (Asymmetric) - High PerformanceΒΆ
Modern elliptic curve cryptography for performance-sensitive deployments.
# ECDSA configuration
JWT_ALGORITHM=ES256
JWT_PUBLIC_KEY_PATH=certs/jwt/ec_public.pem
JWT_PRIVATE_KEY_PATH=certs/jwt/ec_private.pem
JWT_AUDIENCE=mcpgateway-api
JWT_ISSUER=mcpgateway
JWT_AUDIENCE_VERIFICATION=true
Generate ECDSA KeysΒΆ
# Option 1: Use Makefile (Recommended)
make certs-jwt-ecdsa # Generates certs/jwt/{ec_private,ec_public}.pem with proper permissions
# Option 2: Manual generation
mkdir -p certs/jwt
openssl ecparam -genkey -name prime256v1 -noout -out certs/jwt/ec_private.pem
openssl ec -in certs/jwt/ec_private.pem -pubout -out certs/jwt/ec_public.pem
chmod 600 certs/jwt/ec_private.pem
chmod 644 certs/jwt/ec_public.pem
Dynamic Client Registration (DCR)ΒΆ
For scenarios where JWT audience varies by client:
JWT_ALGORITHM=RS256
JWT_PUBLIC_KEY_PATH=certs/jwt/public.pem
JWT_PRIVATE_KEY_PATH=certs/jwt/private.pem
JWT_AUDIENCE_VERIFICATION=false # Disable audience validation for DCR
JWT_ISSUER=your-identity-provider
Security ConsiderationsΒΆ
- Key Storage: Store private keys securely, never commit to version control
- Permissions: Set restrictive file permissions (600) on private keys
- Key Rotation: Implement regular key rotation procedures
- Path Security: Use absolute paths or secure relative paths for key files
- Algorithm Choice:
- Use RS256 for broad compatibility
- Use ES256 for better performance and smaller signatures
- Use HS256 only for simple, single-service deployments
π³ Container ConfigurationΒΆ
Docker Environment FileΒΆ
Create a .env file for Docker deployments:
# .env file for Docker
HOST=0.0.0.0
PORT=4444
DATABASE_URL=mysql+pymysql://mysql:changeme@mysql:3306/mcp
REDIS_URL=redis://redis:6379/0
JWT_SECRET_KEY=my-secret-key
BASIC_AUTH_USER=admin
BASIC_AUTH_PASSWORD=changeme
MCPGATEWAY_UI_ENABLED=true
MCPGATEWAY_ADMIN_API_ENABLED=true
Docker Compose with MySQLΒΆ
version: "3.9"
services:
gateway:
image: ghcr.io/ibm/mcp-context-forge:latest
ports:
- "4444:4444"
environment:
- DATABASE_URL=mysql+pymysql://mysql:changeme@mysql:3306/mcp
- REDIS_URL=redis://redis:6379/0
- JWT_SECRET_KEY=my-secret-key
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
mysql:
image: mysql:8
environment:
- MYSQL_ROOT_PASSWORD=mysecretpassword
- MYSQL_DATABASE=mcp
- MYSQL_USER=mysql
- MYSQL_PASSWORD=changeme
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 5
redis:
image: redis:7
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
βΈοΈ Kubernetes ConfigurationΒΆ
ConfigMap ExampleΒΆ
apiVersion: v1
kind: ConfigMap
metadata:
name: mcpgateway-config
data:
DATABASE_URL: "mysql+pymysql://mysql:changeme@mysql-service:3306/mcp"
REDIS_URL: "redis://redis-service:6379/0"
JWT_SECRET_KEY: "your-secret-key"
BASIC_AUTH_USER: "admin"
BASIC_AUTH_PASSWORD: "changeme"
MCPGATEWAY_UI_ENABLED: "true"
MCPGATEWAY_ADMIN_API_ENABLED: "true"
LOG_LEVEL: "INFO"
MySQL Service ExampleΒΆ
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8
env:
- name: MYSQL_ROOT_PASSWORD
value: "mysecretpassword"
- name: MYSQL_DATABASE
value: "mcp"
- name: MYSQL_USER
value: "mysql"
- name: MYSQL_PASSWORD
value: "changeme"
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
π§ Advanced ConfigurationΒΆ
Performance TuningΒΆ
# Database connection pool
DB_POOL_SIZE=200
DB_MAX_OVERFLOW=5
DB_POOL_TIMEOUT=60
DB_POOL_RECYCLE=3600
# Tool execution
TOOL_TIMEOUT=120
MAX_TOOL_RETRIES=5
TOOL_CONCURRENT_LIMIT=10
Execution Metrics RecordingΒΆ
Control whether execution metrics are recorded to the database:
DB_METRICS_RECORDING_ENABLED=true # Record execution metrics (default)
DB_METRICS_RECORDING_ENABLED=false # Disable execution metrics database writes
What are execution metrics?
Each MCP operation (tool call, resource read, prompt get, etc.) records one database row with:
- Entity ID (tool_id, resource_id, etc.)
- Timestamp
- Response time (seconds)
- Success/failure status
- Error message (if failed)
- Interaction type (A2A metrics only)
When disabled:
- No new rows written to
ToolMetric,ResourceMetric,PromptMetric,ServerMetric,A2AAgentMetrictables - Existing metrics remain queryable until cleanup removes them
- Cleanup and rollup services still run (they process existing data)
- Admin UI metrics pages show no new data
Use cases for disabling:
- External observability platforms handle all metrics (ELK, Datadog, Splunk, Grafana)
- Minimal database footprint deployments
- High-throughput environments where per-operation DB writes are costly
Related settings (separate systems):
| Setting | What it controls |
|---|---|
METRICS_AGGREGATION_ENABLED | Log aggregation into PerformanceMetric table |
ENABLE_METRICS | Prometheus /metrics endpoint |
OBSERVABILITY_METRICS_ENABLED | Internal observability system |
To fully minimize metrics database writes, disable both:
DB_METRICS_RECORDING_ENABLED=false # Disable execution metrics
METRICS_AGGREGATION_ENABLED=false # Disable log aggregation
Metrics Cleanup and RollupΒΆ
Automatic Metrics Management
MCP Gateway automatically manages metrics data to prevent unbounded table growth while preserving historical analytics through hourly rollups.
Understanding Raw vs Rollup MetricsΒΆ
Raw metrics store individual execution events (timestamp, response time, success/failure, error message). Hourly rollups aggregate these into summary statistics (counts, averages, p50/p95/p99 percentiles).
| Use Case | Raw Metrics Needed? |
|---|---|
| Dashboard charts (latency percentiles) | No - rollups have p50/p95/p99 |
| Error rate monitoring | No - rollups have success/failure counts |
| Debugging specific failures | Yes - need exact error messages |
| Identifying slowest requests | Yes - need individual rows |
External Observability IntegrationΒΆ
If you use external observability platforms (ELK Stack, Datadog, Splunk, Grafana/Loki, CloudWatch, OpenTelemetry), raw metrics in the gateway database are typically redundant. Your external platform handles:
- Detailed request logs and traces
- Error message search and filtering
- Individual request debugging
- Compliance audit trails
With external observability, the gateway's hourly rollups provide efficient aggregated analytics, while raw metrics can be deleted quickly (1 hour default).
Configuration ReferenceΒΆ
Cleanup Settings:
METRICS_CLEANUP_ENABLED=true # Enable automatic cleanup (default: true)
METRICS_CLEANUP_INTERVAL_HOURS=1 # Hours between cleanup runs (default: 1)
METRICS_RETENTION_DAYS=7 # Fallback retention when rollup disabled (default: 7)
METRICS_CLEANUP_BATCH_SIZE=10000 # Batch size for deletion (default: 10000)
Rollup Settings:
METRICS_ROLLUP_ENABLED=true # Enable hourly rollup (default: true)
METRICS_ROLLUP_INTERVAL_HOURS=1 # Hours between rollup runs (default: 1)
METRICS_ROLLUP_RETENTION_DAYS=365 # Rollup data retention (default: 365)
METRICS_ROLLUP_LATE_DATA_HOURS=1 # Hours to re-process for late data (default: 1)
Raw Metrics Deletion (when rollups exist):
METRICS_DELETE_RAW_AFTER_ROLLUP=true # Delete raw after rollup (default: true)
METRICS_DELETE_RAW_AFTER_ROLLUP_HOURS=1 # Hours before deletion (default: 1)
Configuration ExamplesΒΆ
Default (recommended for most deployments): Raw metrics deleted after 1 hour, hourly rollups retained for 1 year.
Without external observability (need debugging from raw data):
Disable raw deletion (compliance/audit requirements):
METRICS_DELETE_RAW_AFTER_ROLLUP=false # Keep all raw metrics
METRICS_RETENTION_DAYS=90 # Delete raw after 90 days via cleanup
Admin API Endpoints:
| Endpoint | Method | Description |
|---|---|---|
/api/metrics/cleanup | POST | Trigger manual cleanup |
/api/metrics/rollup | POST | Trigger manual rollup |
/api/metrics/stats | GET | Get cleanup/rollup statistics |
/api/metrics/config | GET | Get current configuration |
Deletion behavior: - Deleted tools/resources/prompts/servers are removed from Top Performers by default, but historical rollups remain for reporting. - To permanently erase metrics for a deleted entity, use the Admin UI delete prompt and choose Purge metrics, or call the delete endpoints with ?purge_metrics=true. - Purge deletes use batched deletes sized by METRICS_CLEANUP_BATCH_SIZE to reduce long table locks on large datasets.
See ADR-030: Metrics Cleanup and Rollup for architecture details.
Security HardeningΒΆ
# Enable all security features
SECURITY_HEADERS_ENABLED=true
CORS_ALLOW_CREDENTIALS=false
AUTH_REQUIRED=true
REQUIRE_TOKEN_EXPIRATION=true
TOKEN_EXPIRY=60
OpenTelemetry ObservabilityΒΆ
# OpenTelemetry (Phoenix, Jaeger, etc.)
OTEL_ENABLE_OBSERVABILITY=true
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=http://phoenix:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_SERVICE_NAME=mcp-gateway
Internal Observability SystemΒΆ
MCP Gateway includes a built-in observability system that stores traces and metrics in the database, providing performance analytics and error tracking through the Admin UI.
# Enable internal observability (database-backed tracing)
OBSERVABILITY_ENABLED=false
# Automatically trace HTTP requests
OBSERVABILITY_TRACE_HTTP_REQUESTS=true
# Trace retention (days)
OBSERVABILITY_TRACE_RETENTION_DAYS=7
# Maximum traces to retain (prevents unbounded growth)
OBSERVABILITY_MAX_TRACES=100000
# Trace sampling rate (0.0-1.0)
# 1.0 = trace everything, 0.1 = trace 10% of requests
OBSERVABILITY_SAMPLE_RATE=1.0
# Paths to exclude from tracing (comma-separated regex patterns)
OBSERVABILITY_EXCLUDE_PATHS=/health,/healthz,/ready,/metrics,/static/.*
# Enable metrics collection
OBSERVABILITY_METRICS_ENABLED=true
# Enable event logging within spans
OBSERVABILITY_EVENTS_ENABLED=true
See the Internal Observability Guide for detailed usage instructions including Admin UI dashboards, performance metrics, and trace analysis.