# =============================================================================
# ContextForge Configuration Example
# =============================================================================
# NOTE ON SOURCES
# Values below match Pydantic Settings defaults unless noted.
# Most env vars below are Pydantic Settings (mcpgateway/config.py).
# Some are runtime/launcher envs (shell scripts, entrypoint) or direct env reads
# in modules (not part of Settings). Those sections are labeled explicitly.
# =============================================================================
# REQUIRED SECRETS - Application will NOT start without these
# =============================================================================
#
# ⚠️ SECURITY WARNING: JWT_SECRET_KEY and AUTH_ENCRYPTION_SECRET are REQUIRED
# in EVERY environment — including local development.
# The application will refuse to start if either value is a placeholder
# (__REPLACE_ME__), a known-weak default, shorter than 32 characters,
# or has low entropy. Both conditions are enforced in every environment.
#
# Quickstart (fresh checkout):
# 1. make install-dev # installs Python deps — does NOT touch .env
# 2. make init-secrets # generates .env.secrets (prompts before overwrite)
# 3. Copy values from .env.secrets into this file, OR:
# make init-secrets-patch-env # patches secrets directly into .env (no copy needed)
#
# Docker Compose / CI (non-interactive):
# python3 -m mcpgateway.scripts.init_secrets --patch-env .env
# # or: python3 -m mcpgateway.scripts.init_secrets --force && \
# # grep -E '^(JWT_SECRET_KEY|AUTH_ENCRYPTION_SECRET|BASIC_AUTH_PASSWORD)=' .env.secrets >> .env
#
# What each path writes:
# make init-secrets / --stdout / --force (writes .env.secrets):
# - JWT_SECRET_KEY (REQUIRED — all environments)
# - AUTH_ENCRYPTION_SECRET (REQUIRED — all environments)
# - BASIC_AUTH_PASSWORD (for UI/API Basic Auth)
# - PLATFORM_ADMIN_PASSWORD (for bootstrap admin account)
#
# make setup / make init-secrets-patch-env / --patch-env (in-place patch):
# - JWT_SECRET_KEY (patched when placeholder or weak)
# - AUTH_ENCRYPTION_SECRET (patched when placeholder or weak)
# - BASIC_AUTH_PASSWORD (patched when "changeme" or placeholder; 18 bytes → 24 chars)
# NOTE: PLATFORM_ADMIN_PASSWORD is NOT patched by this path — set it manually
# or via helm/k8s secrets before production deployment.
#
# =============================================================================
# JWT secret used to sign tokens (REQUIRED — all environments, including development)
# Generate with: python3 -m mcpgateway.scripts.init_secrets
JWT_SECRET_KEY=__REPLACE_ME__run_init-secrets_before_starting
# Passphrase used to encrypt stored auth secrets (REQUIRED — all environments, including development)
# Generate with: python3 -m mcpgateway.scripts.init_secrets
AUTH_ENCRYPTION_SECRET=__REPLACE_ME__run_init-secrets_before_starting
# =============================================================================
# Environment Mode
# =============================================================================
# Controls CORS, cookie security, and operational defaults — but NOT secret
# enforcement. Placeholder/weak secrets are rejected in every environment.
# - development: Relaxed CORS (localhost:3000/8080), insecure cookies, dev info
# - staging: Production-like CORS and cookie defaults, staging domain
# - production: Strict CORS (APP_DOMAIN only), secure cookies, no debug info
# Default: development (if not set)
ENVIRONMENT=development
# =============================================================================
# Admin UI HTTP Basic Auth credentials
# =============================================================================
# BASIC_AUTH_PASSWORD is patched to a strong value by make setup / make init-secrets-patch-env.
# PLATFORM_ADMIN_PASSWORD must be set manually — it is not patched by the automated path.
BASIC_AUTH_USER=admin
BASIC_AUTH_PASSWORD=changeme # pragma: allowlist secret
# -----------------------------------------------------------------------------
# CSRF Protection Configuration
# -----------------------------------------------------------------------------
# Cross-Site Request Forgery protection for state-changing operations
# Enable CSRF protection (default: true)
CSRF_ENABLED=true
# Secret used to sign CSRF tokens. If left unset, the application reuses
# JWT_SECRET_KEY for this purpose. Set it explicitly so that the two keys can
# be rotated independently.
CSRF_SECRET_KEY=
# HTTP header name for CSRF token
CSRF_TOKEN_NAME=X-CSRF-Token
# Cookie name for CSRF token
CSRF_COOKIE_NAME=mcpgateway_csrf_token
# CSRF token expiration time in seconds (default: 3600 = 1 hour)
CSRF_TOKEN_EXPIRY=3600
# Set Secure flag on CSRF cookie (HTTPS only, default: true)
CSRF_COOKIE_SECURE=true
# SameSite attribute for CSRF cookie (Strict, Lax, or None)
CSRF_COOKIE_SAMESITE=Strict
# Set HttpOnly flag on CSRF cookie (false allows JavaScript to read for API calls)
CSRF_COOKIE_HTTPONLY=false
# Validate Referer header for CSRF protection (default: true)
CSRF_CHECK_REFERER=true
# Rotate CSRF token on user login for enhanced security (default: true)
CSRF_ROTATE_ON_LOGIN=true
# Additional trusted origins for CSRF validation (JSON array)
CSRF_TRUSTED_ORIGINS=["http://localhost:4444","http://localhost:8080"]
# Paths exempt from CSRF protection (JSON array)
# WARNING: Setting this env var REPLACES the config.py default entirely — it does not append.
# Leave unset to use the secure default maintained in mcpgateway/config.py (csrf_exempt_paths).
# Only set explicitly if you need a fully custom list (you must include every path you want exempt).
#CSRF_EXEMPT_PATHS=["/health","/auth/login","/auth/logout","/auth/email/login","/auth/email/register","/auth/email/forgot-password","/auth/email/reset-password","/admin","/admin/login","/admin/forgot-password","/admin/reset-password","/oauth/fetch-tools","/docs","/redoc","/openapi.json","/metrics","/mcp/","/sse","/message","/rpc","/api/metrics/","/toolops/","/tokens","/teams/","/llmchat/","/api/logs/","/_internal/mcp/"]
# Bootstrap admin credentials (email auth)
# PRODUCTION: Change these values
PLATFORM_ADMIN_EMAIL=admin@example.com
#PLATFORM_ADMIN_PASSWORD="< replace me >"
DEFAULT_USER_PASSWORD=UserP@ssw0rd!2026Secure
# Set to false in development to enable the platform admin bootstrap path.
# Production default is true (requires admin user seeded in the database).
# Uncomment in development if you rely on the platform-admin email match
# instead of seeding the user via init-secrets.
# (full docs → Authentication section)
#REQUIRE_USER_IN_DB=false
# =============================================================================
# Security Defaults (secure by default)
# =============================================================================
# These settings are enabled by default for security. Only disable for backward
# compatibility with legacy tokens that lack these claims.
# Require JTI (JWT ID) claim in all tokens for revocation support
# Tokens without JTI cannot be revoked before expiration
REQUIRE_JTI=true
# Require expiration (exp) claim in all tokens
# Tokens without expiration never expire and pose a security risk
REQUIRE_TOKEN_EXPIRATION=true
# Disable public user self-registration (admin must create accounts)
PUBLIC_REGISTRATION_ENABLED=false
# Allow entities to have public visibility (set false to block in team scope)
ALLOW_PUBLIC_VISIBILITY=true
# Basic Auth is DISABLED by default for security - use JWT tokens instead
# Only enable for backwards compatibility with legacy clients
# API_ALLOW_BASIC_AUTH=false
# -----------------------------------------------------------------------------
# SSRF Protection (Server-Side Request Forgery)
# -----------------------------------------------------------------------------
# Prevents the gateway from being used to access internal resources or cloud
# metadata services. Enabled by default with safe settings for dev/internal use.
# Master switch for SSRF protection (default: true)
# SSRF_PROTECTION_ENABLED=true
# Allow localhost/loopback addresses (127.0.0.0/8, ::1)
# Default: false (strict)
# SSRF_ALLOW_LOCALHOST=false
# Allow RFC 1918 private network addresses (10.x, 172.16-31.x, 192.168.x)
# Default: false (strict). Use SSRF_ALLOWED_NETWORKS for explicit exceptions.
# SSRF_ALLOW_PRIVATE_NETWORKS=false
# Optional CIDR allowlist when SSRF_ALLOW_PRIVATE_NETWORKS=false
# Only private IPs in these ranges are allowed.
# SSRF_ALLOWED_NETWORKS=["10.20.0.0/16","192.168.50.0/24"]
# Fail closed on DNS resolution errors (default: true)
# URLs that cannot be resolved are rejected
# SSRF_DNS_FAIL_CLOSED=true
# Gateway Test Endpoint Security
#
# The /admin/gateways/test endpoint allows testing gateway connectivity. To prevent
# using this endpoint as an open proxy (ICA_ContextForgeICACF-14), it enforces an
# allowlist of approved hosts.
#
# GATEWAY_TEST_ALLOW_REGISTERED_ONLY: When true, only allows testing URLs that match
# registered gateway base URLs in the database. When false, uses GATEWAY_TEST_ALLOWED_HOSTS
# patterns. Default: true (most secure).
# GATEWAY_TEST_ALLOW_REGISTERED_ONLY=true
#
# GATEWAY_TEST_ALLOWED_HOSTS: List of allowed host patterns for /admin/gateways/test
# when GATEWAY_TEST_ALLOW_REGISTERED_ONLY=false. Supports exact hostnames (example.com)
# and wildcards (*.example.com). Empty list = reject all when registered-only mode is off.
# Example: GATEWAY_TEST_ALLOWED_HOSTS=["api.example.com","*.partner.com"]
# GATEWAY_TEST_ALLOWED_HOSTS=[]
#
# GATEWAY_TEST_DNS_TIMEOUT: Timeout in seconds for DNS resolution during gateway test
# validation. Prevents slow/malicious DNS servers from stalling the handler.
# Default: 5.0 seconds (range: 0.1 to 30.0)
# GATEWAY_TEST_DNS_TIMEOUT=5.0
#
# NOTE: Private IPs (RFC 1918), loopback addresses (127.0.0.0/8), and link-local
# addresses (169.254.0.0/16) are ALWAYS blocked regardless of allowlist configuration.
################################################################################
# UAID Cross-Gateway Routing Security
################################################################################
# Domain allowlist for UAID cross-gateway routing (REQUIRED for production)
# Empty list = DENY all cross-gateway routing (fail-closed, secure default)
# Example: UAID_ALLOWED_DOMAINS=["gateway1.example.com", "gateway2.example.com"]
UAID_ALLOWED_DOMAINS=[]
# DANGEROUS: Allow UAID routing to ANY domain (dev-only)
# ⚠️ WARNING: Setting this to true bypasses domain allowlist validation
# ⚠️ NEVER use in production - creates SSRF vulnerability
# Only enable for development/testing environments
UAID_ALLOW_ALL_DOMAINS=false
# Forward bearer tokens in cross-gateway UAID calls for RBAC enforcement
# When enabled, user authentication context is preserved across gateway hops
# Requires both gateways to trust the same JWT issuer (shared JWT_SECRET_KEY or federated SSO)
# Disable only if you have a different cross-gateway auth mechanism
UAID_FORWARD_AUTH=true
# Startup behavior when UAID allowlist not configured (strict mode)
# When true: Gateway startup FAILS if A2A enabled but UAID_ALLOWED_DOMAINS empty
# When false (default): Logs ERROR but allows startup (backward compatible)
# Recommended: true for production, false for dev/staging
# UAID_REQUIRE_ALLOWLIST_ON_STARTUP=false
# DoS Protection: Maximum UAID length (default 2048, matches database column limit)
# Prevents DoS attacks via excessively long UAID parsing
# Valid range: 512-2048 (cannot exceed database schema limit)
# UAID_MAX_LENGTH=2048
# Networks to block (JSON array of CIDR ranges) - ALWAYS blocked regardless of above
# Default blocks cloud metadata endpoints. Add more for stricter security.
# SSRF_BLOCKED_NETWORKS=["169.254.169.254/32","169.254.169.123/32","fd00::1/128","169.254.0.0/16","fe80::/10"]
# Hostnames to block (JSON array) - case-insensitive matching
# SSRF_BLOCKED_HOSTS=["metadata.google.internal","metadata.internal"]
# Example: STRICT mode (external endpoints only, no internal access)
# SSRF_PROTECTION_ENABLED=true
# SSRF_ALLOW_LOCALHOST=false
# SSRF_ALLOW_PRIVATE_NETWORKS=false
# SSRF_ALLOWED_NETWORKS=[]
# SSRF_BLOCKED_NETWORKS=["169.254.169.254/32","169.254.169.123/32","fd00::1/128","169.254.0.0/16","fe80::/10","100.64.0.0/10"]
# The 100.64.0.0/10 range is Carrier-Grade NAT (CGNAT) which some cloud providers use
# -----------------------------------------------------------------------------
# Content Security - Size Limits and MIME Type Restrictions (US-2)
# -----------------------------------------------------------------------------
# Maximum content sizes (in bytes) to prevent DoS attacks via large uploads
# Maximum size for resource content (default: 102400 = 100KB)
# Resources exceeding this limit will be rejected with 413 Payload Too Large
# CONTENT_MAX_RESOURCE_SIZE=102400
# Maximum size for prompt templates (default: 10240 = 10KB)
# Prompts exceeding this limit will be rejected with 413 Payload Too Large
# CONTENT_MAX_PROMPT_SIZE=10240
# Allowed MIME types for resources (JSON array or comma-separated list)
# In strict mode, only MIME types explicitly listed here are accepted.
# Vendor types (application/x-*, text/x-*) and suffix types (+json, +xml) must be
# explicitly added to this list if needed - they are NOT automatically allowed.
# Default: text/plain,text/markdown,text/html,text/csv,application/json,application/xml,application/pdf,...
# Both formats are accepted:
# CONTENT_ALLOWED_RESOURCE_MIMETYPES=["text/plain","text/markdown","application/json"]
# CONTENT_ALLOWED_RESOURCE_MIMETYPES=text/plain,text/markdown,application/json
# Enable strict MIME type validation for resources (default: true)
# Set to false to log violations without blocking (useful for testing/migration)
# CONTENT_STRICT_MIME_VALIDATION=true
# -----------------------------------------------------------------------------
# Content Security - Template Validation (US-4)
# -----------------------------------------------------------------------------
# Enable validation of prompt templates for safe syntax (default: true)
# Validates Jinja2 syntax and blocks dangerous patterns like __import__, eval, exec
# Set to false to disable template security validation (not recommended)
# CONTENT_VALIDATE_PROMPT_TEMPLATES=true
# Dangerous patterns to block in templates (JSON array)
# Blocks Python injection attempts like {{__import__('os')}}
# Default blocks: __import__, eval, exec, __builtins__, and dunder methods
# CONTENT_BLOCKED_TEMPLATE_PATTERNS=["__import__","eval\\s*\\(","exec\\s*\\(","__.*__"]
# Example: Strict mode (block all dunder methods and common injection vectors)
# CONTENT_BLOCKED_TEMPLATE_PATTERNS=["__import__","__builtins__","__globals__","__locals__","__class__","__base__","__subclasses__","eval\\s*\\(","exec\\s*\\(","compile\\s*\\(","open\\s*\\(","file\\s*\\(","input\\s*\\(","__\\w+__"]
# -----------------------------------------------------------------------------
# Content Security - Runtime Pattern Detection (US-3)
# -----------------------------------------------------------------------------
# Enable malicious pattern detection in resources and prompts (XSS, SQLi, etc.)
# Default: true
# CONTENT_PATTERN_DETECTION_ENABLED=true
# Validation mode: strict (warn+block), moderate (same as strict), lenient (warn only)
# Default: strict
# CONTENT_PATTERN_VALIDATION_MODE=strict
# Additional regex patterns to block (JSON array, appended to built-ins)
# Default: [] (built-in patterns only)
# CONTENT_BLOCKED_PATTERNS=[]
# Enable caching of clean (non-malicious) pattern validation results
# Default: true — disable to reduce memory use at the cost of scan latency
# CONTENT_PATTERN_CACHE_ENABLED=true
# Maximum clean-result cache entries (0 to disable)
# Default: 1000
# CONTENT_PATTERN_MAX_CACHE_SIZE=1000
# Maximum bytes of content scanned per request (hard ReDoS defense)
# Content exceeding this limit is rejected. Default: 200000 (200KB)
# CONTENT_PATTERN_MAX_SCAN_SIZE=200000
# Per-pattern regex timeout in seconds (soft timeout via thread join)
# Default: 1.0
# CONTENT_PATTERN_REGEX_TIMEOUT=1.0
# =============================================================================
# Project defaults (batteries-included overrides)
# =============================================================================
# These values intentionally differ from config.py defaults to provide a working
# local/dev setup out of the box. Comment out anything you do not want to override.
# Bind to all interfaces for local containers and remote access
HOST=0.0.0.0
# Local origin used for CORS/cookies in development examples
APP_DOMAIN=http://localhost:8080
# Trusted React/UI origin and optional path prefix for links in invitation and password emails.
# When unset, password recovery uses legacy /admin routes; invitations use APP_DOMAIN plus APP_ROOT_PATH.
# UI_BASE_URL=http://localhost:3000
# Enable Admin UI and Admin API for local development
MCPGATEWAY_UI_ENABLED=true
MCPGATEWAY_ADMIN_API_ENABLED=true
# Enable MCP Apps support (disabled by default for secure rollout)
# When enabled, gateway advertises io.modelcontextprotocol/ui capability
# SECURITY WARNING: MCP Apps can render UI returned by upstream MCP servers.
# UI-originated links, form actions, and JavaScript navigation may open or post
# directly from the user's browser instead of going through MCP tool calls/RBAC.
# Only enable for trusted servers or with strict UI CSP, sandboxing, external
# navigation allowlists/brokered opens, and audit logging.
MCPGATEWAY_MCP_APPS_ENABLED=false
MCPGATEWAY_MCP_APPS_SESSION_TTL=900
# Local/dev SSRF compatibility: allow local services and fail open on DNS lookups.
# Keep strict values (false/false/true) in production environments.
SSRF_ALLOW_LOCALHOST=true
SSRF_ALLOW_PRIVATE_NETWORKS=true
SSRF_DNS_FAIL_CLOSED=false
# Disable WebSocket relay and reverse-proxy transports by default.
# Enable only when those transports are explicitly required.
MCPGATEWAY_WS_RELAY_ENABLED=false
MCPGATEWAY_REVERSE_PROXY_ENABLED=false
# Relax cookie security for local HTTP development
SECURE_COOKIES=false
# Enable experimental IO validation for visibility
EXPERIMENTAL_VALIDATE_IO=true
# DEPRECATED as of 2026-06-11; sunsets on 2026-07-07. Prefer endpoint-level Pydantic validation and protocol-specific validation.
VALIDATION_MIDDLEWARE_ENABLED=false
# Query-parameter validation patterns (override only when you need stricter or broader allow-lists)
# VALIDATION_CURSOR_PATTERN=^[a-zA-Z0-9_=+/-]+$
# VALIDATION_TAGS_FILTER_PATTERN=^[a-zA-Z0-9_,+ .-]*$
# VALIDATION_GATEWAY_ID_LIST_PATTERN=^[a-zA-Z0-9_,-]*$
# VALIDATION_RENDER_MODE_PATTERN=^[a-zA-Z_-]+$
# VALIDATION_VISIBILITY_PATTERN=^(private|team|public)$
# VALIDATION_USER_IDENTIFIER_PATTERN=^[a-zA-Z0-9._%+@-]+$
# VALIDATION_HTTP_METHOD_PATTERN=^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)$
# VALIDATION_EXPORT_FORMAT_PATTERN=^(json|csv|ndjson)$
# VALIDATION_ERROR_CODE_PATTERN=^[a-zA-Z0-9_]+$
# VALIDATION_TRACE_STATUS_PATTERN=^(ok|error)$
# VALIDATION_TOOLOPS_MODE_PATTERN=^(generate|query|status)$
# VALIDATION_HYPHEN_IDENTIFIER_PATTERN=^[a-zA-Z0-9_-]+$
# VALIDATION_TEAM_ID_PATTERN=^[a-zA-Z0-9_-]+$
# VALIDATION_SCOPE_ID_PATTERN=^[a-zA-Z0-9_-]+$
# VALIDATION_GATEWAY_ID_PATTERN=^[a-zA-Z0-9_-]+$
# VALIDATION_TRACE_ID_PATTERN=^[a-zA-Z0-9_-]+$
# VALIDATION_RESOURCE_NAME_PATTERN=^[a-zA-Z0-9_. /-]+$
# VALIDATION_RELATIONSHIP_PATTERN=^(owner|member|public)$
# VALIDATION_ENTITY_TYPE_PATTERN=^(tools|resources|prompts|servers)$
# VALIDATION_TIME_RANGE_PATTERN=^(1h|6h|12h|24h|7d|30d)$
# VALIDATION_STATUS_FILTER_PATTERN=^(all|ok|error)$
# VALIDATION_PERIOD_TYPE_PATTERN=^(hourly|daily)$
# VALIDATION_AGGREGATION_PATTERN=^(5m|24h)$
# VALIDATION_ENTITY_TYPES_PATTERN=^[a-zA-Z,]*$
# Tag length validation (applies to all resources: tools, prompts, resources, servers, gateways)
# Minimum tag length (default: 2, range: 1-10)
# VALIDATION_MIN_TAG_LENGTH=2
# Maximum tag length (default: 100, range: 10-255)
# Supports system-generated tags, hashes, and namespaced identifiers
# Example: VALIDATION_MAX_TAG_LENGTH=150 for very long descriptive tags
# VALIDATION_MAX_TAG_LENGTH=100
# Permission audit logging (RBAC checks) - disabled by default for performance
PERMISSION_AUDIT_ENABLED=false
# Add SQL injection pattern on top of the default dangerous patterns
DANGEROUS_PATTERNS=["[;&|`$(){}\\[\\]<>]", "\\.\\.[\\\\/]", "[\\x00-\\x1f\\x7f-\\x9f]", "(?i)(drop|delete|insert|update|select)\\s+(table|from|into|where)"]
# Loosen password complexity for local bootstrap (production should re-enable)
PASSWORD_REQUIRE_UPPERCASE=false
PASSWORD_REQUIRE_LOWERCASE=false
PASSWORD_REQUIRE_SPECIAL=false
# Longer UI tool test timeout for slower dev environments
MCPGATEWAY_UI_TOOL_TEST_TIMEOUT=120000
# Slow down health polling and extend config cache for local dev
HEALTH_CHECK_INTERVAL=300
GLOBAL_CONFIG_CACHE_TTL=300
# Log to file by default for local debugging
LOG_FILE=mcpgateway.log
LOG_FOLDER=logs
# Local OTEL collector endpoint
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# -----------------------------------------------------------------------------
# Experimental DATAPLANE
# -----------------------------------------------------------------------------
# Send db config to redis to be consumed by experimental Dataplane
DATAPLANE_PUBLISHER=false
DATAPLANE_PUBLISHER_INTERVAL_SECONDS=60
# =============================================================================
# Hot toggles (commented quick switches)
# =============================================================================
# These are frequently changed flags. If a key is already set in the
# Project defaults block above, change it there instead of uncommenting here.
# Feature flags / UX (full docs → Plugin Framework Configuration section)
# MCPGATEWAY_UI_ENABLED=false
# MCPGATEWAY_ADMIN_API_ENABLED=false
# PLUGINS_ENABLED=false
# MCPGATEWAY_CATALOG_ENABLED=true
# LLMCHAT_ENABLED=true
# MCPGATEWAY_STDIO_TRANSPORT_ENABLED=false
# PLUGINS_CAN_OVERRIDE_RBAC=false
# PLUGINS_CAN_OVERRIDE_AUTH_HEADERS=false
# Direct proxy mode
# MCPGATEWAY_DIRECT_PROXY_ENABLED=false
# MCPGATEWAY_DIRECT_PROXY_TIMEOUT=30
# Observability / metrics (full docs → Observability Settings + OpenTelemetry sections)
# OBSERVABILITY_ENABLED=false
# OTEL_ENABLE_OBSERVABILITY=false
# CPEX_CONTROL_TELEMETRY_ENABLED=false
# ENABLE_METRICS=true
# DB_METRICS_RECORDING_ENABLED=true
# METRICS_AGGREGATION_AUTO_START=false
# Logging / audit (full docs → respective named sections below)
# STRUCTURED_LOGGING_DATABASE_ENABLED=false
# AUDIT_TRAIL_ENABLED=false
# PERMISSION_AUDIT_ENABLED=false
# SECURITY_LOGGING_ENABLED=false
# SIEM_EXPORT_ENABLED=false
# SIEM_EXPORT_EVENT_SOURCES=["auth","security","audit"]
# SIEM_DESTINATIONS=[]
# TOKEN_USAGE_LOGGING_ENABLED=true
# TOKEN_LAST_USED_UPDATE_INTERVAL_MINUTES=5
# Security / auth (full docs → Authentication section)
# AUTH_REQUIRED=true
# MCP_CLIENT_AUTH_ENABLED=true
# HTTP header name for JWT authentication (default: Authorization)
# Use alternative header (e.g., X-MCP-Gateway-Auth) to avoid collision with
# downstream server authentication. When using custom header, the standard
# Authorization header is preserved and passed through to backend servers.
# AUTH_HEADER_NAME=Authorization
# AUTH_HEADER_NAME=X-MCP-Gateway-Auth
# TRUST_PROXY_AUTH=false
# TRUST_PROXY_AUTH_DANGEROUSLY=false # DANGER: Only set true behind a strictly trusted auth proxy
# ALLOW_UNAUTHENTICATED_ADMIN=false # DANGER: Only for local dev — grants admin to unauthenticated requests
# SECURITY_HEADERS_ENABLED=true
# CORS_ALLOW_CREDENTIALS=true
# SECURE_COOKIES=true
# REQUIRE_USER_IN_DB=false
# ANYIO_CANCEL_DELIVERY_PATCH_ENABLED=false
# Rust MCP (simple) - DEPRECATED as of 2026-06-11; sunsets on 2026-07-07.
# Prefer RUST_MCP_MODE=off and the default Python MCP transport.
# See https://ibm.github.io/mcp-context-forge/deprecations/
# RUST_MCP_BUILD=false # build the Rust MCP runtime into Containerfile.lite images
# RUST_MCP_MODE=off # off | shadow | edge | full
# RUST_MCP_LOG=warn # default Rust sidecar log filter for the simple mode flow
#
# RUST_MCP_MODE=shadow -> Rust sidecar enabled, but public /mcp stays on Python for safe fallback
# RUST_MCP_MODE=edge -> direct public /mcp on Rust with managed UDS sidecar defaults
# RUST_MCP_MODE=full -> edge + Rust session/event-store/resume/live-stream/affinity cores
#
# Runtime override (in-memory only; restart re-reads RUST_MCP_MODE / RUST_A2A_MODE):
# When the boot mode is edge an authorized caller (admin.system_config) can
# flip shadow <-> edge live via:
# (off, shadow, and full boot modes are not flippable — off has no Rust
# sidecar; shadow did not opt into session-auth-reuse / delegate-enabled
# so an edge override cannot safely route traffic; full would require
# live migration of Rust-owned session/event-store cores.)
# curl -X PATCH .../admin/runtime/mcp-mode -d '{"mode": "shadow"}'
# curl -X PATCH .../admin/runtime/a2a-mode -d '{"mode": "edge"}'
# If REDIS_URL is configured the override propagates to all pods; otherwise the
# override is local to the pod that received the PATCH (visible via /health
# under mcp_runtime.cluster_propagation).
#
# Advanced Rust MCP overrides - DEPRECATED with the Rust MCP runtime sidecar.
# RUST_MCP_SESSION_AUTH_REUSE=false # advanced override for the fast direct public Rust session-auth path; prefer RUST_MCP_MODE presets above
# EXPERIMENTAL_RUST_MCP_RUNTIME_ENABLED=
# EXPERIMENTAL_RUST_MCP_RUNTIME_URL=http://127.0.0.1:8787
# EXPERIMENTAL_RUST_MCP_RUNTIME_UDS=/tmp/contextforge-mcp-rust.sock
# EXPERIMENTAL_RUST_MCP_RUNTIME_TIMEOUT_SECONDS=30
# EXPERIMENTAL_RUST_MCP_SESSION_CORE_ENABLED= # enable Rust-owned MCP session metadata/lifecycle increment
# EXPERIMENTAL_RUST_MCP_EVENT_STORE_ENABLED= # enable Rust-owned resumable event-store backend
# EXPERIMENTAL_RUST_MCP_RESUME_CORE_ENABLED= # enable Rust-owned public GET /mcp replay/resume path
# EXPERIMENTAL_RUST_MCP_LIVE_STREAM_CORE_ENABLED= # enable Rust-owned public GET /mcp live SSE path
# EXPERIMENTAL_RUST_MCP_AFFINITY_CORE_ENABLED= # enable Rust-owned session-affinity forwarding path
# EXPERIMENTAL_RUST_MCP_SESSION_AUTH_REUSE_ENABLED= # enable Rust-owned session-bound auth-context reuse
# EXPERIMENTAL_RUST_MCP_RUNTIME_MANAGED= # launcher env, not a Pydantic setting
# ENABLE_RUST_MCP_RMCP_BUILD= # container build arg override for rmcp-enabled Rust MCP binary
# MCP_RUST_USE_RMCP_UPSTREAM_CLIENT= # runtime override for official rust-sdk upstream tools/call client
# MCP_RUST_LISTEN_HTTP=127.0.0.1:8787 # runtime env for bundled Rust sidecar
# MCP_RUST_LISTEN_UDS=/tmp/contextforge-mcp-rust.sock
# MCP_RUST_SESSION_CORE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_SESSION_CORE_ENABLED
# MCP_RUST_SESSION_TTL_SECONDS=3600
# MCP_RUST_EVENT_STORE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_EVENT_STORE_ENABLED
# MCP_RUST_RESUME_CORE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_RESUME_CORE_ENABLED
# MCP_RUST_LIVE_STREAM_CORE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_LIVE_STREAM_CORE_ENABLED
# MCP_RUST_AFFINITY_CORE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_AFFINITY_CORE_ENABLED
# MCP_RUST_SESSION_AUTH_REUSE_ENABLED= # explicit sidecar env; defaults from EXPERIMENTAL_RUST_MCP_SESSION_AUTH_REUSE_ENABLED
# MCP_RUST_SESSION_AUTH_REUSE_TTL_SECONDS=30
# MCP_RUST_EVENT_STORE_MAX_EVENTS_PER_STREAM=100
# MCP_RUST_EVENT_STORE_TTL_SECONDS=3600
# MCP_RUST_EVENT_STORE_POLL_INTERVAL_MS=250
# MCP_RUST_LOG= # advanced runtime log override for the bundled Rust sidecar
# MCP_RUST_BACKEND_RPC_URL=http://127.0.0.1:4444/_internal/mcp/rpc
# MCP_RUST_REDIS_URL=redis://redis:6379/0
# MCP_RUST_CACHE_PREFIX=mcpgw:
# MCP_RUST_DATABASE_URL=postgresql://postgres:mysecretpassword@pgbouncer:6432/mcp
# MCP_RUST_DB_POOL_MAX_SIZE=20
# MCP_RUST_MAX_REQUEST_BODY_SIZE_BYTES=10485760 # max request body (bytes) enforced by the Rust ingress (default 10MB). Only applies when RUST_MCP_MODE=edge|full
# Rust A2A runtime controls are also DEPRECATED as of 2026-06-11; sunsets on 2026-07-07.
# Prefer RUST_A2A_MODE=off and the default Python A2A invocation path.
# =============================================================================
# Performance Tuning (quick reference)
# =============================================================================
# Use this section to tune throughput, latency, and cache behavior.
# If a key is already set in the Project defaults block, change it there instead.
# Detailed explanations for each setting appear later in this file.
# -----------------------------------------------------------------------------
# Cache TTLs (seconds)
# -----------------------------------------------------------------------------
# AUTH / registry / admin caches
# AUTH_CACHE_USER_TTL=60
# AUTH_CACHE_REVOCATION_TTL=30
# AUTH_CACHE_TEAM_TTL=60
# AUTH_CACHE_ROLE_TTL=60
# AUTH_CACHE_TEAMS_TTL=60
# REGISTRY_CACHE_TOOLS_TTL=20
# REGISTRY_CACHE_PROMPTS_TTL=15
# REGISTRY_CACHE_RESOURCES_TTL=15
# REGISTRY_CACHE_AGENTS_TTL=20
# REGISTRY_CACHE_SERVERS_TTL=20
# REGISTRY_CACHE_GATEWAYS_TTL=20
# REGISTRY_CACHE_CATALOG_TTL=300
# ADMIN_STATS_CACHE_SYSTEM_TTL=60
# ADMIN_STATS_CACHE_OBSERVABILITY_TTL=30
# ADMIN_STATS_CACHE_TAGS_TTL=120
# ADMIN_STATS_CACHE_PLUGINS_TTL=120
# ADMIN_STATS_CACHE_PERFORMANCE_TTL=60
# TEAM_MEMBER_COUNT_CACHE_TTL=300
# METRICS_CACHE_TTL_SECONDS=60
# Tool + pagination caches
# TOOL_LOOKUP_CACHE_TTL_SECONDS=60
# TOOL_LOOKUP_CACHE_NEGATIVE_TTL_SECONDS=10
# PAGINATION_COUNT_CACHE_TTL=300
# LLM chat caches
# LLMCHAT_SESSION_TTL=300
# LLMCHAT_SESSION_LOCK_TTL=30
# LLMCHAT_CHAT_HISTORY_TTL=3600
# Catalog / DCR / performance caches
# MCPGATEWAY_CATALOG_CACHE_TTL=3600
# DCR_METADATA_CACHE_TTL=3600
# MCPGATEWAY_PERFORMANCE_NET_CONNECTIONS_CACHE_TTL=15
# Static resource caching
# RESOURCE_CACHE_TTL=3600
# Redis leader election
# REDIS_LEADER_TTL=15
# REDIS_LEADER_HEARTBEAT_INTERVAL=5
# -----------------------------------------------------------------------------
# Pooling, concurrency, and limits
# -----------------------------------------------------------------------------
# Database connection pool (SQLAlchemy)
# DB_POOL_CLASS=auto
# DB_POOL_PRE_PING=auto
# DB_POOL_SIZE=200
# DB_MAX_OVERFLOW=10
# DB_POOL_TIMEOUT=30
# DB_POOL_RECYCLE=3600
# Redis connection pool
# REDIS_MAX_CONNECTIONS=50
# REDIS_SOCKET_TIMEOUT=2.0
# REDIS_SOCKET_CONNECT_TIMEOUT=2.0
# REDIS_HEALTH_CHECK_INTERVAL=30
# Timeout for individual Redis operations (seconds). Prevents API hangs during Redis connectivity issues.
# Circuit breaker opens after REDIS_CIRCUIT_FAILURE_THRESHOLD consecutive failures
# (timeouts or connection errors) and cools down for REDIS_CIRCUIT_OPEN_DURATION seconds
# before allowing a single probe. A successful probe closes the circuit.
# REDIS_OPERATION_TIMEOUT=0.5
# REDIS_CIRCUIT_FAILURE_THRESHOLD=3
# REDIS_CIRCUIT_OPEN_DURATION=30.0
# Redis TLS (disabled by default for local dev; enable in production)
# To use TLS, change REDIS_URL to use the rediss:// scheme and set REDIS_SSL=true.
# Example production URL: REDIS_URL=rediss://redis:6380/0
# REDIS_SSL=false
# REDIS_SSL_CA_CERTS=/certs/ca.crt # CA bundle to verify the Redis server certificate
# REDIS_SSL_CERTFILE=/certs/client.crt # Client certificate for mutual TLS (mTLS)
# REDIS_SSL_KEYFILE=/certs/client.key # Client private key for mutual TLS (mTLS)
# REDIS_SSL_CHECK_HOSTNAME=true # Set true only when Redis presents a valid CA-signed cert
# HTTPX shared client pool
# HTTPX_MAX_CONNECTIONS=200
# HTTPX_MAX_KEEPALIVE_CONNECTIONS=100
# HTTPX_KEEPALIVE_EXPIRY=30.0
# HTTPX_POOL_TIMEOUT=10.0
# Tool and federation limits
# TOOL_TIMEOUT=60
# MAX_TOOL_RETRIES=3
# TOOL_RATE_LIMIT=100
# TOOL_CONCURRENT_LIMIT=10
# FEDERATION_TIMEOUT=120
# Health checks
# HEALTH_CHECK_INTERVAL=60
# HEALTH_CHECK_TIMEOUT=30
# UNHEALTHY_THRESHOLD=3
# GATEWAY_VALIDATION_TIMEOUT=5
# MAX_CONCURRENT_HEALTH_CHECKS=10
# -----------------------------------------------------------------------------
# Timeouts, polling, and backoff
# -----------------------------------------------------------------------------
# Session registry polling (cache_type=database)
# POLL_INTERVAL=1.0
# MAX_INTERVAL=5.0
# BACKOFF_FACTOR=1.5
# DB startup resilience
# DB_MAX_RETRIES=30
# DB_RETRY_INTERVAL_MS=2000
# DB_MAX_BACKOFF_SECONDS=30
# Redis startup resilience
# REDIS_MAX_RETRIES=30
# REDIS_RETRY_INTERVAL_MS=2000
# REDIS_MAX_BACKOFF_SECONDS=30
# -----------------------------------------------------------------------------
# Retention and cleanup windows
# -----------------------------------------------------------------------------
# METRICS_RETENTION_DAYS=7
# METRICS_CLEANUP_INTERVAL_HOURS=1
# METRICS_ROLLUP_ENABLED=true
# METRICS_ROLLUP_INTERVAL_HOURS=1
# METRICS_ROLLUP_RETENTION_DAYS=365
# METRICS_ROLLUP_LATE_DATA_HOURS=1
# METRICS_DELETE_RAW_AFTER_ROLLUP=true
# METRICS_DELETE_RAW_AFTER_ROLLUP_HOURS=1
# MCPGATEWAY_PERFORMANCE_RETENTION_HOURS=24
# MCPGATEWAY_PERFORMANCE_RETENTION_DAYS=90
# OBSERVABILITY_TRACE_RETENTION_DAYS=7
# LOG_RETENTION_DAYS=30
# HTTPX timeouts
# HTTPX_CONNECT_TIMEOUT=5.0
# HTTPX_READ_TIMEOUT=120.0
# HTTPX_WRITE_TIMEOUT=30.0
# HTTPX_ADMIN_READ_TIMEOUT=30.0
# SSE_TASK_GROUP_CLEANUP_TIMEOUT=5.0
# ANYIO_CANCEL_DELIVERY_PATCH_ENABLED=false
# ANYIO_CANCEL_DELIVERY_MAX_ITERATIONS=100
# -----------------------------------------------------------------------------
# Middleware overhead and compression
# -----------------------------------------------------------------------------
# COMPRESSION_ENABLED=true
# COMPRESSION_GZIP_LEVEL=6
# COMPRESSION_BROTLI_QUALITY=4
# COMPRESSION_ZSTD_LEVEL=3
# COMPRESSION_MINIMUM_SIZE=500
# CLIENT_DISCONNECT_MIDDLEWARE_ENABLED=true
# VALIDATION_MIDDLEWARE_ENABLED=false
# CORRELATION_ID_ENABLED=true
# TEMPLATES_AUTO_RELOAD=false
# STRUCTURED_LOGGING_DATABASE_ENABLED=false # full docs → Structured Log Database Persistence section
# AUDIT_TRAIL_ENABLED=false # full docs → Audit Trail Logging section
# SECURITY_LOGGING_ENABLED=false # full docs → Security Event Logging section
# =============================================================================
# Basic Server Configuration
# =============================================================================
# Application name displayed in UI and logs
# APP_NAME=ContextForge
# Host interface to bind to (127.0.0.1 = localhost only)
# Project defaults block sets HOST=0.0.0.0 for local containers
# HOST=127.0.0.1
# Port number for the HTTP server
# PORT=4444
# Runtime environment - affects CORS, cookies, and security defaults
# Options: development, staging, production
# - development: Relaxed CORS (localhost:3000/8080), debug info, insecure cookies
# - staging: Production-like CORS and cookie defaults, but use staging domains
# - production: Strict CORS (APP_DOMAIN only), secure cookies, no debug info
# ENVIRONMENT=development
# Domain name for CORS origins and cookie settings (use your actual domain in production)
# Project defaults block sets APP_DOMAIN=http://localhost for local dev
# APP_DOMAIN=http://localhost:4444
# FastAPI root_path for reverse proxy deployments (empty = serve from root "/")
# Used when gateway is behind a proxy with path prefix (e.g., "/api/v1")
# See FastAPI docs: https://fastapi.tiangolo.com/advanced/behind-a-proxy/
# APP_ROOT_PATH=
# Client mode for gateway-as-client usage
# Options: true, false (default)
# CLIENT_MODE=false
# Override templates/static directories (absolute paths)
# Leave unset to use package defaults
# TEMPLATES_DIR=/absolute/path/to/templates
# STATIC_DIR=/absolute/path/to/static
# Enable HTTP Basic Auth for OpenAPI docs endpoints (/docs, /redoc)
# Options: true, false (default: false)
# When true: Allows accessing docs with BASIC_AUTH_USER/BASIC_AUTH_PASSWORD
# When false: Only JWT Bearer token authentication is accepted
# DOCS_ALLOW_BASIC_AUTH=false # canonical entry — remove stub in project-defaults if needed
# Database Configuration
# SQLite (default) - good for development and small deployments
# macOS note: If you see "sqlite3.OperationalError: disk I/O error" on macOS when running
# `make serve`, move the DB to a safe APFS path (avoid iCloud/Dropbox/OneDrive/Google Drive,
# network shares, or external exFAT) and use an absolute path, for example:
# DATABASE_URL=sqlite:////Users/$USER/Library/Application Support/mcpgateway/mcp.db
# DATABASE_URL=sqlite:///./mcp.db
# Skip alembic upgrade head on startup (migrations managed externally via init container or CI).
# Set to 'true' when using docker-compose (the migration service handles schema before gateway starts).
# Set to 'false' for standalone 'docker run' or direct 'make serve' deployments.
MCPGATEWAY_SKIP_MIGRATIONS=false
# PostgreSQL - recommended for production deployments
# Uses psycopg3 driver (psycopg[binary])
# IMPORTANT: Use postgresql+psycopg:// (not postgresql://) for psycopg3
# DATABASE_URL=postgresql+psycopg://postgres:mysecretpassword@localhost:5432/mcp
# Database Connection Pool Configuration
# ============================================================================
# IMPORTANT: Pool size depends on your database connection strategy:
#
# WITH PgBouncer (recommended for PostgreSQL, default in docker-compose):
# - Use SMALL pool (10-20) since PgBouncer handles connection pooling
# - docker-compose.yml sets DB_POOL_SIZE=15 by default
# - Do NOT override here unless you know what you're doing
# - Formula: (replicas × workers × pool) should be < PgBouncer MAX_CLIENT_CONN
#
# WITHOUT PgBouncer (direct PostgreSQL or SQLite):
# - Use LARGER pool based on: (replicas × workers × pool) < max_connections
# - Uncomment and set DB_POOL_SIZE=50-200 depending on workload
#
# Uncomment for SQLite or direct PostgreSQL without PgBouncer
# DB_POOL_SIZE=200
# ============================================================================
# Additional connections beyond pool_size for burst handling (default: 10)
# DB_MAX_OVERFLOW=10
# Seconds to wait for connection before timeout (default: 30)
# DB_POOL_TIMEOUT=30
# Seconds before recreating connection to prevent stale connections (default: 3600)
# DB_POOL_RECYCLE=3600
# Database driver identifier (advanced; used by SQLAlchemy engine selection)
# DB_DRIVER=postgresql+psycopg
# Connection pool class selection
# Options: auto (default), null, queue
# DB_POOL_CLASS=auto
# Connection pool pre-ping behavior
# Options: auto (default), true, false
# DB_POOL_PRE_PING=auto
# Database Startup Resilience (exponential backoff with jitter)
# Retry progression: 2s → 4s → 8s → 16s → 30s (capped), ±25% jitter
# 30 retries ≈ 5 minutes total wait before worker gives up
# DB_MAX_RETRIES=30
# Base retry interval in milliseconds (doubles each attempt)
# DB_RETRY_INTERVAL_MS=2000
# Maximum backoff cap in seconds (jitter ±25% applied after cap)
# DB_MAX_BACKOFF_SECONDS=30
# psycopg3: Number of query executions before auto-preparing server-side (default: 5)
# Set to 0 to disable, 1 to prepare immediately. Higher values reduce memory usage.
# DB_PREPARE_THRESHOLD=5
# SQLite Configuration
# SQLite busy timeout (milliseconds) - maximum time SQLite will block while waiting
# to acquire a database lock before returning SQLITE_BUSY. Limits lock-wait latency
# and prevents prolonged thread blocking under write contention (default: 5000ms)
# DB_SQLITE_BUSY_TIMEOUT=5000
# Database Performance Optimization
# Use database-native percentile functions for observability performance metrics
# When true: PostgreSQL uses native percentile_cont (5-10x faster for large datasets)
# When false: Falls back to Python-based percentile calculations (works with all databases)
# Recommended: true for PostgreSQL production deployments, auto-detected for SQLite
# USE_POSTGRESDB_PERCENTILES=true
# The number of rows fetched from the database at a time when streaming results,
# to limit memory usage and avoid loading all rows into RAM at once.
# YIELD_BATCH_SIZE=1000
# Cache Backend Configuration
# Options: database (default), memory (in-process), redis (distributed)
# - database: Uses SQLite/PostgreSQL for persistence (good for single-node)
# - memory: Fast in-process caching (lost on restart, not shared between workers)
# - redis: Distributed caching for multi-node deployments
# REQUIRED for multi-worker session affinity (MCPGATEWAY_SESSION_AFFINITY_ENABLED=true)
# CACHE_TYPE=database
# Session Registry Database Polling (Adaptive Backoff)
# When CACHE_TYPE=database, sessions use polling to check for messages.
# Adaptive backoff reduces database load by ~90% during idle periods while
# maintaining responsiveness when messages arrive.
#
# How it works:
# - Starts polling at POLL_INTERVAL (1.0s default)
# - When no messages found, interval increases by BACKOFF_FACTOR (1.5x)
# - Backs off until reaching MAX_INTERVAL (5.0s cap)
# - Immediately resets to POLL_INTERVAL when a message arrives
#
# Example progression: 1.0s → 1.5s → 2.25s → 3.375s → 5.0s (capped)
# =============================================================================
# Tuning guide:
# - Lower POLL_INTERVAL (0.1-0.5s) for real-time applications needing <1s latency
# - Higher MAX_INTERVAL (10-30s) for batch workloads to minimize DB queries
# - Higher BACKOFF_FACTOR (2.0) for faster backoff, lower (1.2) for gradual
# POLL_INTERVAL=1.0
# MAX_INTERVAL=5.0
# BACKOFF_FACTOR=1.5
# Redis connection URL (only used when CACHE_TYPE=redis)
# Format: redis://[username:password@]host:port/database
# Example: redis://localhost:6379/0 (local), redis://redis:6379/0 (container)
# REDIS_URL=redis://localhost:6379/0
# Cache key prefix for Redis (used to namespace keys in shared Redis instances)
# Default: "mcpgw:"
# CACHE_PREFIX=mcpgw:
# Session time-to-live in seconds (how long sessions remain valid)
# Default: 3600 (1 hour)
# SESSION_TTL=3600
# Message time-to-live in seconds (how long messages are retained)
# Default: 600 (10 minutes)
# MESSAGE_TTL=600
# Redis Startup Resilience (exponential backoff with jitter)
# Same behavior as DB retries: 2s → 4s → 8s → 16s → 30s (capped), ±25% jitter
# 30 retries ≈ 5 minutes total wait before worker gives up
# REDIS_MAX_RETRIES=30
# Base retry interval in milliseconds (doubles each attempt)
# REDIS_RETRY_INTERVAL_MS=2000
# Maximum backoff cap in seconds (jitter ±25% applied after cap)
# REDIS_MAX_BACKOFF_SECONDS=30
# =============================================================================
# Redis Connection Pool - Performance Tuned
# =============================================================================
# Connection pool size per worker process
# Formula: (concurrent_requests / workers) * 1.5
# Default 50 handles ~500 concurrent requests with 10 workers
# REDIS_MAX_CONNECTIONS=50
# Socket read/write timeout (seconds)
# Keep low for fast failure detection; Redis ops typically <100ms
# REDIS_SOCKET_TIMEOUT=2.0
# Connection establishment timeout (seconds)
# Keep low to avoid blocking event loop on network issues
# REDIS_SOCKET_CONNECT_TIMEOUT=2.0
# Retry commands that timeout (recommended: true)
# REDIS_RETRY_ON_TIMEOUT=true
# Connection health check interval (seconds, 0=disabled)
# Prevents stale connections in pool
# REDIS_HEALTH_CHECK_INTERVAL=30
# Return strings instead of bytes (recommended: true)
# REDIS_DECODE_RESPONSES=true
# =============================================================================
# Redis Parser Configuration (Performance - ADR-026)
# =============================================================================
# Redis protocol parser selection
# Options:
# - auto (default): Use hiredis C parser if available, fallback to pure-Python
# - hiredis: Require hiredis C parser (fails if not installed)
# - python: Force pure-Python parser (useful for debugging)
#
# Performance benchmarks (hiredis vs pure-Python):
# - Simple SET/GET: ~1.1x faster
# - LRANGE (10 items): ~2.7x faster
# - LRANGE (100 items): ~10x faster
# - LRANGE (999 items): ~83x faster
#
# Recommendation: Leave as "auto" - hiredis is installed by default with redis[hiredis]
# REDIS_PARSER=auto
# =============================================================================
# Redis Leader Election - Multi-Node Deployments
# =============================================================================
# Leader TTL in seconds (time before failover if leader dies)
# Lower = faster failover, but more sensitive to network blips
# Recommended: 15s for production, 5s for development
# REDIS_LEADER_TTL=15
# Leader heartbeat interval (seconds)
# Must be < leader_ttl/2 to prevent false failovers
# Rule: heartbeat_interval <= leader_ttl / 3
# REDIS_LEADER_HEARTBEAT_INTERVAL=5
# Leader key name in Redis
# REDIS_LEADER_KEY=gateway_service_leader
# Override path for the primary-worker election lock file
# (mcpgateway/utils/primary_worker.py). Lets a side-effecting non-hook plugin run
# its work on one worker per host. Defaults to a port-scoped file in the system
# temp dir when unset; point it at a gateway-owned directory on hostile hosts.
# PRIMARY_WORKER_LOCK_PATH=
# Primary-worker election backend:
# - filelock (default): one primary per host
# - redis: one primary across all instances sharing the same Redis
# PRIMARY_WORKER_ELECTION_BACKEND=filelock
# PRIMARY_WORKER_REDIS_KEY=mcpgw:primary_worker
# PRIMARY_WORKER_LEASE_TTL=15
# PRIMARY_WORKER_HEARTBEAT_INTERVAL=5
# When the redis backend can't reach Redis: fail_closed (no primary) or
# filelock_fallback (per-host). fail_closed preserves the global guarantee.
# PRIMARY_WORKER_REDIS_UNAVAILABLE_POLICY=fail_closed
# =============================================================================
# =============================================================================
# Rate Limiter Redis Configuration
# =============================================================================
# Optional: Dedicated Redis instance for rate limiting middleware to prevent
# contention with main Redis (cache, sessions, federation).
# When unset, rate limiting uses REDIS_URL (backward compatible).
#
# IMPORTANT: Rate limiter Redis operates independently of CACHE_TYPE setting.
# Optional Redis URL for rate limiting middleware
# Must start with redis:// or rediss://
# RATELIMITER_REDIS_URL=redis://localhost:6380/0
# Connection pool size for rate limiter Redis (default: 50)
# RATELIMITER_REDIS_MAX_CONNECTIONS=50
# Socket read/write timeout for rate limiter Redis (default: 2.0 seconds)
# RATELIMITER_REDIS_SOCKET_TIMEOUT=2.0
# Connection timeout for rate limiter Redis (default: 2.0 seconds)
# RATELIMITER_REDIS_SOCKET_CONNECT_TIMEOUT=2.0
# Redis TLS (disabled by default for local dev; enable in production)
# To use TLS, change RATELIMITER_REDIS_URL to use the rediss:// scheme and set RATELIMITER_REDIS_SSL=true.
# Example production URL: REDIS_URL=rediss://redis:6380/0
# RATELIMITER_REDIS_SSL=false
# RATELIMITER_REDIS_SSL_CA_CERTS=/certs/ca.crt # CA bundle to verify the Redis server certificate
# RATELIMITER_REDIS_SSL_CERTFILE=/certs/client.crt # Client certificate for mutual TLS (mTLS)
# RATELIMITER_REDIS_SSL_KEYFILE=/certs/client.key # Client private key for mutual TLS (mTLS)
# RATELIMITER_REDIS_SSL_CHECK_HOSTNAME=true # Set true only when Redis presents a valid CA-signed cert
# Protocol Settings
# =============================================================================
# MCP protocol version supported by this gateway
# PROTOCOL_VERSION=2025-06-18
# =============================================================================
# Authentication
# =============================================================================
# Admin UI HTTP Basic Auth credentials
# Used for: Admin UI login, /docs endpoint (if DOCS_ALLOW_BASIC_AUTH=true)
# PRODUCTION: Change these to strong, unique values!
# BASIC_AUTH_USER=admin
# BASIC_AUTH_PASSWORD=changeme
# Global authentication requirement
# Options: true (default), false
# When true: All endpoints require authentication (Basic or JWT)
# When false: Endpoints are publicly accessible (NOT RECOMMENDED)
# AUTH_REQUIRED=true
# MCP endpoint authentication requirement
# Options: true, false
# Default when unset: follows AUTH_REQUIRED
# - AUTH_REQUIRED=true -> MCP auth required
# - AUTH_REQUIRED=false -> public-only /mcp access allowed
# Set to false explicitly to allow unauthenticated public-only MCP access.
# MCP_REQUIRE_AUTH=true
# JWT Algorithm Selection
# Supported algorithms:
# HMAC (Symmetric): HS256, HS384, HS512 - Simple deployments, shared secret
# RSA (Asymmetric): RS256, RS384, RS512 - Enterprise, distributed systems
# ECDSA (Asymmetric): ES256, ES384, ES512 - High performance, modern crypto
# JWT_ALGORITHM=HS256
# === HMAC (Symmetric) Configuration ===
# Secret used to sign JWTs (required for HMAC algorithms: HS256, HS384, HS512)
# REQUIRED in all environments — see "REQUIRED SECRETS" section at the top of this file.
# Use a strong, random secret (minimum 32 characters). Generate with:
# python3 -m mcpgateway.scripts.init_secrets
# JWT_SECRET_KEY=
# === RSA/ECDSA (Asymmetric) Configuration - Recommended for Production ===
# Public and private key paths (required for asymmetric algorithms: RS*, ES*)
# Generate RSA keys with: make certs-jwt
# (creates certs/jwt/private.pem and certs/jwt/public.pem with proper permissions)
# Generate ECDSA keys with: make certs-jwt-ecdsa
# (creates certs/jwt/ec_private.pem and certs/jwt/ec_public.pem with proper permissions)
# Generate both SSL and JWT keys: make certs-all
#JWT_PUBLIC_KEY_PATH=certs/jwt/public.pem
#JWT_PRIVATE_KEY_PATH=certs/jwt/private.pem
# JWT Claims Configuration
# PRODUCTION: Set these to your service-specific values
# JWT_AUDIENCE=mcpgateway-api
# JWT_ISSUER=mcpgateway
# Cross-environment token isolation (GHSA-vgf8-3685-66j9)
# Use a DISTINCT JWT_SECRET_KEY per environment. Do not copy .env across DEV/STAGING/PROD.
# Also set a distinct ENVIRONMENT value per deployment (development/staging/production) --
# DERIVE_KEY_PER_ENVIRONMENT isolation requires distinct ENVIRONMENT values.
EMBED_ENVIRONMENT_IN_TOKENS=true
VALIDATE_TOKEN_ENVIRONMENT=true
# Optional: derive a per-environment signing key from JWT_SECRET_KEY (HS* only).
# Enabling this re-keys tokens (treat as a key rotation); same-environment federation
# peers must share the same JWT_SECRET_KEY and ENVIRONMENT. Not applicable to RS*/ES*.
# DERIVE_KEY_PER_ENVIRONMENT=false
# JWT Validation Options
# Set to false for Dynamic Client Registration (DCR) scenarios where audience varies
# JWT_AUDIENCE_VERIFICATION=true
# Set to false for custom auth flows where issuer varies or is not present
# JWT_ISSUER_VERIFICATION=true
# Expiry time for generated JWT tokens (in minutes; e.g. 7 days)
# TOKEN_EXPIRY=10080
# Session lifecycle (POST /auth/refresh + GET /auth/validate)
# Absolute maximum session lifetime in minutes, enforced at refresh (0 disables the cap)
# SESSION_MAX_LIFETIME=480
# Maximum POST /auth/refresh requests per minute per client dimension (IP/user/team)
# SESSION_REFRESH_RATE_LIMIT=10
# Client-behavior hints surfaced via GET /auth/validate (seconds)
# SESSION_WARNING_TIME=60
# SESSION_REFRESH_BUFFER=300
# SESSION_ACTIVITY_TRACKING=true
# SECURITY: Require expiration claim in all tokens (default: true)
# Set to false only for backward compatibility with legacy tokens
# REQUIRE_TOKEN_EXPIRATION=true
# SECURITY: Require JTI (JWT ID) claim for token revocation support (default: true)
# Set to false only for backward compatibility with legacy tokens
# REQUIRE_JTI=true
# Require all authenticated users to exist in the database
# When true, disables the platform admin bootstrap mechanism
# WARNING: Enabling this on a fresh deployment will lock you out!
# REQUIRE_USER_IN_DB=false
# =============================================================================
# Security Validation & Sanitization
# =============================================================================
# Enable experimental input validation and output sanitization
# This implements gateway-level security controls to protect against:
# - Path traversal attacks (../../../etc/passwd)
# - Command injection (file.jpg; rm -rf /)
# - SQL injection ('; DROP TABLE users; --)
# - XSS attacks ()
# - Control character injection (\x1b[31m)
#
# Roll-out phases:
# Phase 0: EXPERIMENTAL_VALIDATE_IO=false (disabled, default)
# Phase 1: EXPERIMENTAL_VALIDATE_IO=true, VALIDATION_STRICT=false (log-only)
# Phase 2: EXPERIMENTAL_VALIDATE_IO=true, VALIDATION_STRICT=true (enforce in staging)
# Phase 3: Production deployment with all features enabled
# Project defaults block enables EXPERIMENTAL_VALIDATE_IO for local dev
# EXPERIMENTAL_VALIDATE_IO=false
# Deprecated validation middleware for all requests
# Keep disabled unless an existing deployment depends on it
# Options: true, false (default)
# VALIDATION_MIDDLEWARE_ENABLED=false # active value set in project-defaults block
# Strict validation mode
# Options:
# - true: Reject requests with validation failures (422 status)
# - false: Log warnings but allow requests (log-only mode)
# Recommended: false for dev/staging, true for production
#
# Note: this also controls the forbidden-pattern check on tool descriptions
# during registration (shell metacharacters such as "> ", "< ", "|", ";").
# Set to false if your MCP server tools have Markdown-formatted descriptions
# that contain these characters (e.g. "> blockquote", "< input", "cmd | grep").
# For more targeted control, see TOOL_DESCRIPTION_FORBIDDEN_PATTERNS below.
# VALIDATION_STRICT=true
# Tool description forbidden pattern validation
# Master switch to enable/disable forbidden pattern checks on tool descriptions.
# When disabled, no pattern checks are performed regardless of VALIDATION_STRICT.
# Options: true (default), false
# TOOL_DESCRIPTION_FORBIDDEN_PATTERNS_ENABLED=true
# Override the list of substrings blocked in tool descriptions.
# Accepts a JSON array. Default: ["&&", ";", "||", "$(", "|", "> ", "< "]
# Set to a custom list to allow specific patterns while still blocking others.
# Example: TOOL_DESCRIPTION_FORBIDDEN_PATTERNS=["&&", "$("]
# TOOL_DESCRIPTION_FORBIDDEN_PATTERNS=["&&", ";", "||", "$(", "|", "> ", "< "]
# Strict JSON Schema validation for tools and prompts
# Options:
# - true: Reject invalid JSON schemas during registration (strict spec compliance)
# - false: Log warnings only (backward compatibility for legacy tools)
# JSON_SCHEMA_VALIDATION_STRICT=true
# Sanitize output to remove control characters
# Removes ANSI escape sequences and C0/C1 control characters from responses
# Preserves newlines (\n) and tabs (\t)
# Options: true (default), false
# SANITIZE_OUTPUT=true
# MCP Apps session cleanup (AppBridge)
# Enable automatic cleanup of expired AppBridge sessions
# MCPGATEWAY_MCP_APPS_SESSION_CLEANUP_ENABLED=true
# Seconds between cleanup runs (60–86400)
# MCPGATEWAY_MCP_APPS_SESSION_CLEANUP_INTERVAL_SECONDS=300
# Max expired sessions deleted per cleanup batch
# MCPGATEWAY_MCP_APPS_SESSION_CLEANUP_BATCH_SIZE=1000
# Allowed root paths for resource access
# Restricts file system access to specific directories
# Format: JSON array or comma-separated list
# Examples:
# - JSON: ["/srv/data", "/var/app/uploads"]
# - CSV: /srv/data,/var/app/uploads
# - Empty: [] (no restrictions, not recommended)
# PRODUCTION: Always configure this to limit resource access
# ALLOWED_ROOTS=[]
# Maximum allowed path depth
# Prevents deeply nested path attacks
# Default: 10 levels
# MAX_PATH_DEPTH=10
# Maximum parameter length (characters)
# Prevents buffer overflow and DoS attacks
# Default: 10000 characters
# MAX_PARAM_LENGTH=10000
# CWE-400: Limits for user-supplied meta_data forwarded to upstream MCP servers.
# Keeps arbitrarily large dicts from amplifying into downstream network/DB load.
# Maximum number of top-level keys in meta_data (default: 16)
# META_MAX_KEYS=16
# Maximum nesting depth in meta_data (default: 2)
# META_MAX_DEPTH=2
# Maximum JSON-encoded byte size of meta_data (default: 4096)
# META_MAX_BYTES=4096
# Regex patterns for dangerous input (JSON array)
# Used to detect and block malicious input patterns
# Default patterns:
# 1. Shell metacharacters: [;&|`$(){}\[\]<>]
# 2. Path traversal: \.\.[/\\]
# 3. Control characters: [\x00-\x1f\x7f-\x9f]
# Format: JSON array of regex patterns
# Project defaults block adds an SQL injection pattern on top of defaults
# DANGEROUS_PATTERNS=["[;&|`$(){}\\[\\]<>]", "\\.\\.[\\\\/]", "[\\x00-\\x1f\\x7f-\\x9f]"]
# =============================================================================
# Email-Based Authentication
# =============================================================================
# Enable email-based authentication system
# EMAIL_AUTH_ENABLED=true
# Public registration control
# When false (default), only admins can create user accounts via /admin/users
# When true, anyone can self-register via /auth/email/register
# SECURITY: Keep this false in production unless you explicitly need public sign-up
# PUBLIC_REGISTRATION_ENABLED=false
# Admin login-lockout protection
# When true (default), active admin accounts can bypass login lockout
# Admin self-demotion and last-active-admin protection are always enforced independently
# PROTECT_ALL_ADMINS=true
# Platform admin user (bootstrap from environment)
# PRODUCTION: Change these to your actual admin credentials!
# PLATFORM_ADMIN_EMAIL=admin@example.com
# PLATFORM_ADMIN_PASSWORD=changeme
# PLATFORM_ADMIN_FULL_NAME=Platform Administrator
# Default password for newly created users (bootstrap only)
# DEFAULT_USER_PASSWORD=changeme
# Argon2id Password Hashing Configuration
# Time cost (iterations) - higher = more secure but slower
# ARGON2ID_TIME_COST=3
# Memory cost (KB) - higher = more secure but uses more RAM
# ARGON2ID_MEMORY_COST=65536
# Parallelism (threads) - typically 1 for web apps
# ARGON2ID_PARALLELISM=1
# Password Policy Configuration
# PASSWORD_MIN_LENGTH=8
# Minimum password length for regular user accounts (OWASP recommendation: 12)
# PASSWORD_MIN_LENGTH_USER=12
# Minimum password length for admin/platform_admin accounts (user baseline + 10 chars for added complexity)
# PASSWORD_MIN_LENGTH_PRIVILEGED=22
# Minimum password length for API/service accounts (64-128 bit entropy recommendation)
# PASSWORD_MIN_LENGTH_SERVICE=20
# Project defaults block relaxes these for local bootstrap
# PASSWORD_REQUIRE_UPPERCASE=true
# PASSWORD_REQUIRE_LOWERCASE=true
# PASSWORD_REQUIRE_NUMBERS=false
# PASSWORD_REQUIRE_SPECIAL=true
# Password Change Enforcement
# Master switch for all password change enforcement checks
# PASSWORD_CHANGE_ENFORCEMENT_ENABLED=true
# Force admin to change password after bootstrap
# ADMIN_REQUIRE_PASSWORD_CHANGE_ON_BOOTSTRAP=true
# Rate Limiting (for local dev/testing, disable to prevent account lockouts)
# Enable Redis-backed rate limiting middleware
# RATE_LIMITING_ENABLED=true
# Enable Redis-backed rate limiting (fallback to in-memory if Redis unavailable)
# RATE_LIMITING_REDIS_ENABLED=true
# Enable temporary lockout after excessive violations
# RATE_LIMIT_LOCKOUT_ENABLED=true
# Violations before account lockout triggers
# RATE_LIMIT_LOCKOUT_THRESHOLD=5
# Duration of lockout in minutes
# RATE_LIMIT_LOCKOUT_DURATION_MINUTES=15
# Per-tier RPM and burst limits (CRITICAL/HIGH/MEDIUM/LOW)
# CRITICAL: Auth endpoints (login, register, password reset)
# RATE_LIMIT_CRITICAL_RPM=10
# RATE_LIMIT_CRITICAL_BURST=0
# HIGH: Token management, admin, OAuth
# RATE_LIMIT_HIGH_RPM=30
# RATE_LIMIT_HIGH_BURST=0
# MEDIUM: MCP, tools, LLM chat
# RATE_LIMIT_MEDIUM_RPM=100
# RATE_LIMIT_MEDIUM_BURST=20
# LOW: Health checks, metrics, static content
# RATE_LIMIT_LOW_RPM=500
# RATE_LIMIT_LOW_BURST=100
# Detect default password during login and mark user for change
# DETECT_DEFAULT_PASSWORD_ON_LOGIN=true
# Require password change when using default password
# REQUIRE_PASSWORD_CHANGE_FOR_DEFAULT_PASSWORD=true
# Enable password complexity validation for new/changed passwords
# PASSWORD_POLICY_ENABLED=true
# Prevent reusing the current password when changing
# PASSWORD_PREVENT_REUSE=true
# Number of previous passwords that cannot be reused
# PASSWORD_HISTORY_COUNT=5
# Password maximum age in days before expiry forces a change
# PASSWORD_MAX_AGE_DAYS=90
# Maximum length for password error messages in URL redirects (prevents browser URL overflow)
# PASSWORD_ERROR_MESSAGE_MAX_LENGTH=200
# Account Security Configuration
# Maximum failed login attempts before account lockout (recommended: 5)
# Production deployments should use the default value (5) or lower to prevent brute-force attacks
# Security Note: Per ICACF-16, accounts are locked after 5 failed attempts within 60 minutes
# MAX_FAILED_LOGIN_ATTEMPTS=5
# Account lockout duration in minutes (recommended: 60)
# Locks account for this duration after MAX_FAILED_LOGIN_ATTEMPTS is exceeded
# Security Note: Per ICACF-16, 60-minute lockout prevents brute-force attacks
# ACCOUNT_LOCKOUT_DURATION_MINUTES=60
# Send lockout notification emails when an account is locked
# ACCOUNT_LOCKOUT_NOTIFICATION_ENABLED=true
# Minimum response time for failed login attempts (milliseconds)
# Helps reduce timing-based account enumeration
# FAILED_LOGIN_MIN_RESPONSE_MS=250
# Self-Service Password Reset
# Enable forgot-password and reset-password workflows
# Set to false to disable public self-service reset UI/API endpoints.
# PASSWORD_RESET_ENABLED=true
# Password reset token validity (minutes)
# PASSWORD_RESET_TOKEN_EXPIRY_MINUTES=60
# Max password reset requests allowed per email in each window
# PASSWORD_RESET_RATE_LIMIT=5
# Rate limit window length (minutes)
# PASSWORD_RESET_RATE_WINDOW_MINUTES=15
# Invalidate active sessions after successful password reset
# PASSWORD_RESET_INVALIDATE_SESSIONS=true
# Minimum response time for forgot-password requests (milliseconds)
# Helps reduce timing-based account enumeration
# PASSWORD_RESET_MIN_RESPONSE_MS=250
# SMTP Email Delivery (for password reset + lockout notifications)
# Enable SMTP delivery
# SMTP_ENABLED=false
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USER=noreply@example.com
# SMTP_PASSWORD=changeme
# SMTP_FROM_EMAIL=noreply@example.com
# SMTP_FROM_NAME=ContextForge
# Use STARTTLS
# SMTP_USE_TLS=true
# Use implicit SSL/TLS (set true for port 465)
# SMTP_USE_SSL=false
# SMTP_TIMEOUT_SECONDS=15
# MCP Client Authentication
# Controls JWT authentication for /mcp endpoints
# MCP_CLIENT_AUTH_ENABLED=true
# TRUST_PROXY_AUTH=false
# PROXY_USER_HEADER=X-Authenticated-User
# SECURITY NOTE: MCP Access Control Dependencies
# Full MCP access control (visibility + team scoping + membership validation) requires:
# 1. MCP_CLIENT_AUTH_ENABLED=true (JWT auth extracts user identity and teams)
# 2. Valid Bearer token with teams claim for team-scoped access
# When MCP_CLIENT_AUTH_ENABLED=false:
# - Access control relies on MCP_REQUIRE_AUTH + tool/resource visibility only
# - Team membership validation is skipped (no JWT to extract teams from)
# - Use TRUST_PROXY_AUTH=true with a reverse proxy for user identification
# Used to derive an AES encryption key for secure auth storage
# Must be at least 32 characters with high entropy — startup fails otherwise.
# Generate with: make init-secrets-patch-env (or python3 -m mcpgateway.scripts.init_secrets)
# AUTH_ENCRYPTION_SECRET=
# Identity Propagation - forward end-user identity to upstream MCP servers
# IDENTITY_PROPAGATION_ENABLED=false
# IDENTITY_PROPAGATION_MODE=both # headers, meta, or both
# IDENTITY_PROPAGATION_HEADERS_PREFIX=X-Forwarded-User
# IDENTITY_SENSITIVE_ATTRIBUTES=["password_hash","internal_id","ssn"]
# IDENTITY_SIGN_CLAIMS=false
# IDENTITY_CLAIMS_SECRET= # uses JWT_SECRET_KEY if unset
# OAuth Configuration
# OAUTH_REQUEST_TIMEOUT=30
# OAUTH_MAX_RETRIES=3
# OAuth Security Settings
# When MCP servers require OAuth authorization code flow,
# tokens are stored per-user to prevent cross-user token access.
# Users must individually authorize each OAuth-protected gateway.
# Enforce audience mismatches as blocking even when the expected resource was
# auto-derived (i.e. no explicit `resource` configured on the gateway and no
# learned aud persisted yet from a prior IdP token for the current user).
# Default (false) keeps the auto-derived audience check advisory so brand-new
# gateways still forward tokens to the upstream MCP server for validation.
# Set to true in strict environments where the gateway must reject
# cross-resource tokens itself rather than relying on the upstream server
# to validate `aud`. See docs/docs/manage/oauth-resource-configuration.md.
# OAUTH_REQUIRE_CONFIGURED_RESOURCE=false
# =============================================================================
# OAuth Token Storage Backend
# =============================================================================
# Pluggable token storage backend for OAuth access/refresh tokens
# Options: 'database' (default), 'vault' (HashiCorp Vault)
# - database: Store tokens in the primary database (SQLite/PostgreSQL)
# - vault: Store tokens in HashiCorp Vault KV v2 (recommended for enterprise)
# OAUTH_TOKEN_BACKEND=database
# ─────────────────────────────────────────────────────────────────────────────
# Vault Configuration (only used when OAUTH_TOKEN_BACKEND=vault)
# ─────────────────────────────────────────────────────────────────────────────
# Vault server URL
# Example: https://vault.example.com:8200
# VAULT_ADDR=http://127.0.0.1:8200
# Vault authentication token (required when using Vault backend)
# Phase 1: Static token (root or long-lived token)
# Phase 2: AppRole authentication (future enhancement)
# Generate with: vault token create -policy=contextforge-oauth
# VAULT_TOKEN=
# Vault namespace (Vault Enterprise only; leave empty for Community Edition)
# Example: engineering/team1
# VAULT_NAMESPACE=
# KV v2 mount path where secrets are stored
# Must be a KV v2 secrets engine (not KV v1)
# Verify with: vault secrets list | grep "kv"
# VAULT_KV_MOUNT=secret
# Path prefix within the KV mount for organizing OAuth tokens
# Full Vault path structure: {mount}/data/{prefix}/{team_id}/{server_id}/{email}
# Example: secret/data/contextforge/oauth/team-123/server-456/user@example.com
# VAULT_KV_PATH_PREFIX=contextforge/oauth
# TLS certificate verification (set false for local dev only)
# PRODUCTION: Always keep true to prevent MITM attacks
# VAULT_TLS_VERIFY=true
# ─────────────────────────────────────────────────────────────────────────────
# Vault Token Cache (optional, Vault backend only)
# ─────────────────────────────────────────────────────────────────────────────
# Enable in-memory caching of OAuth tokens to reduce Vault API calls
# When enabled: Reduces read latency from ~25ms (Vault) to ~0.5ms (cache hit)
# Trade-off: Tokens may be stale within TTL window if rotated externally
# VAULT_TOKEN_CACHE_ENABLED=false
# Cache TTL in seconds (how long tokens remain cached)
# Shorter = more fresh but higher Vault load; Longer = lower load but potentially stale
# Recommended: 300 (5 minutes) for balance between freshness and performance
# VAULT_TOKEN_CACHE_TTL=300
# Maximum number of cached token entries before LRU eviction
# Each entry ≈ 1 KB (access + refresh token) → 10,000 entries ≈ 10 MB
# Tune based on: (active users × OAuth gateways per user)
# VAULT_TOKEN_CACHE_MAX_SIZE=10000
# =============================================================================
# OAuth Dynamic Client Registration (DCR) and PKCE
# =============================================================================
# Enable Dynamic Client Registration (RFC 7591)
# When enabled, ContextForge can automatically register as an OAuth client with Authorization Servers
# that support DCR, eliminating the need for manual client credential configuration.
# DCR_ENABLED=true
# Auto-register when gateway has issuer but no client_id
# When true, gateway automatically registers with the Authorization Server when configured
# with an issuer URL but no client credentials.
# DCR_AUTO_REGISTER_ON_MISSING_CREDENTIALS=true
# Default scopes to request during DCR
# JSON array of OAuth scopes to request when auto-registering
# DCR_DEFAULT_SCOPES=["mcp:read"]
# Optional allowlist of issuer URLs for DCR (empty = allow any)
# JSON array of trusted Authorization Server issuer URLs
# Example: ["https://auth.example.com", "https://auth2.example.com"]
# Empty array [] allows DCR with any issuer (not recommended for production)
# DCR_ALLOWED_ISSUERS=[]
# Token endpoint authentication method for DCR
# Options: client_secret_basic (default), client_secret_post, none
# - client_secret_basic: Send credentials via HTTP Basic Auth header
# - client_secret_post: Send credentials in POST body
# - none: Public client (no client secret, PKCE-only)
# DCR_TOKEN_ENDPOINT_AUTH_METHOD=client_secret_basic
# AS metadata cache TTL in seconds (RFC 8414 discovery)
# How long to cache Authorization Server metadata after discovery
# DCR_METADATA_CACHE_TTL=3600
# Template for client_name in DCR requests
# {gateway_name} will be replaced with the actual gateway name
# DCR_CLIENT_NAME_TEMPLATE=ContextForge ({gateway_name})
# Request refresh_token even when AS metadata omits grant_types_supported
# Default: false (strict mode - only request refresh_token if AS explicitly advertises support)
# Set to true for AS servers that support refresh tokens but don't advertise it in metadata
# DCR_REQUEST_REFRESH_TOKEN_WHEN_UNSUPPORTED=false
# Enable OAuth AS metadata discovery (RFC 8414)
# When enabled, gateway automatically discovers Authorization Server endpoints
# from the issuer URL using well-known metadata endpoints
# OAUTH_DISCOVERY_ENABLED=true
# Preferred PKCE code challenge method
# Options: S256 (SHA-256, recommended), plain (not recommended)
# PKCE (Proof Key for Code Exchange) is always enabled for Authorization Code flows
# OAUTH_PREFERRED_CODE_CHALLENGE_METHOD=S256
# ==============================================================================
# SSO (Single Sign-On) Configuration
# ==============================================================================
# Master SSO switch - enable Single Sign-On authentication
# Options: true, false (default)
# When true: Enables SSO login options alongside local auth
# SSO_ENABLED=false
# GitHub OAuth Configuration
# Options: true, false (default)
# Requires: GitHub OAuth App (Settings > Developer settings > OAuth Apps)
# SSO_GITHUB_ENABLED=false
# SSO_GITHUB_CLIENT_ID=your-github-client-id
# SSO_GITHUB_CLIENT_SECRET=your-github-client-secret
# Google OAuth Configuration
# SSO_GOOGLE_ENABLED=false
# SSO_GOOGLE_CLIENT_ID=your-google-client-id.googleusercontent.com
# SSO_GOOGLE_CLIENT_SECRET=your-google-client-secret
# IBM Security Verify OIDC Configuration
# SSO_IBM_VERIFY_ENABLED=false
# SSO_IBM_VERIFY_CLIENT_ID=your-ibm-verify-client-id
# SSO_IBM_VERIFY_CLIENT_SECRET=your-ibm-verify-client-secret
# SSO_IBM_VERIFY_ISSUER=https://your-tenant.verify.ibm.com/oidc/endpoint/default
# Okta OIDC Configuration
# SSO_OKTA_ENABLED=false
# SSO_OKTA_CLIENT_ID=your-okta-client-id
# SSO_OKTA_CLIENT_SECRET=your-okta-client-secret
# SSO_OKTA_ISSUER=https://your-okta-domain.okta.com
# SSO_OKTA_SCOPE=openid profile email groups
# OKTA_GROUP_MAPPING={"Engineering": "team-uuid-1", "Admins": "team-uuid-2"}
# Keycloak OIDC Configuration (with auto-discovery)
# SSO_KEYCLOAK_ENABLED=false
# SSO_KEYCLOAK_BASE_URL=https://keycloak.example.com
# Optional: browser-facing Keycloak URL when gateway uses an internal base URL (e.g., Docker DNS)
# SSO_KEYCLOAK_PUBLIC_BASE_URL=https://login.example.com
# SSO_KEYCLOAK_REALM=master
# SSO_KEYCLOAK_CLIENT_ID=mcp-gateway
# SSO_KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret
# SSO_KEYCLOAK_MAP_REALM_ROLES=true
# SSO_KEYCLOAK_MAP_CLIENT_ROLES=false
# SSO_KEYCLOAK_USERNAME_CLAIM=preferred_username
# SSO_KEYCLOAK_EMAIL_CLAIM=email
# SSO_KEYCLOAK_GROUPS_CLAIM=groups
# Optional: map Keycloak realm roles/groups to Gateway RBAC roles
# Example: {"gateway-admin":"platform_admin","gateway-developer":"developer","gateway-viewer":"viewer"}
# SSO_KEYCLOAK_ROLE_MAPPINGS={}
# Optional: fallback role when no mapping matches
# SSO_KEYCLOAK_DEFAULT_ROLE=
# If true, map team-scoped roles to the user's personal team automatically
# SSO_KEYCLOAK_RESOLVE_TEAM_SCOPE_TO_PERSONAL_TEAM=false
# Microsoft Entra ID (Azure AD) OIDC Configuration
# See docs/docs/manage/sso-microsoft-entra-id-tutorial.md for detailed setup instructions
# SSO_ENTRA_ENABLED=false
# SSO_ENTRA_CLIENT_ID=your-entra-application-client-id
# SSO_ENTRA_CLIENT_SECRET=your-entra-client-secret-value
# SSO_ENTRA_TENANT_ID=your-entra-tenant-id
# ADFS Configuration
# SSO_ADFS_ENABLED=false
# SSO_ADFS_DISPLAY_NAME=your-value
# SSO_ADFS_CLIENT_ID=your-adfs-client-id
# SSO_ADFS_CLIENT_SECRET=your-adfs-client-secret # pragma: allowlist secret
# SSO_ADFS_AUTHORIZATION_URL=https://adfs.ds.example.net/adfs/oauth2/authorize
# SSO_ADFS_TOKEN_URL=https://adfs.ds.example.net/adfs/oauth2/token
# SSO_ADFS_ISSUER=https://adfs.ds.example.net/adfs
# SSO_ADFS_SCOPE=openid profile email
# Fallback: Default email domain for ADFS when UPN is plain username (e.g., converts 'user123' to 'user123@company.com')
# SSO_ADFS_DEFAULT_EMAIL_DOMAIN=company.com
# ─────────────────────────────────────────────────────────────────────────────
# EntraID Role Mapping Configuration
# ─────────────────────────────────────────────────────────────────────────────
# IMPORTANT: Configure group claims in Azure Portal > App Registration > Token Configuration
# Add "groups" claim to ID tokens for Security Groups, or use App Roles for semantic names.
#
# JWT claim containing groups (default: "groups" for Security Groups, use "roles" for App Roles)
# SSO_ENTRA_GROUPS_CLAIM=groups
#
# Admin Groups - members get platform_admin role and is_admin=true (full platform access)
# Accepts Object IDs (GUIDs) or App Role names. Case-insensitive matching.
# Example with Object IDs: ["a1b2c3d4-1234-5678-90ab-cdef12345678"]
# Example with App Roles: ["Admin", "PlatformAdmin"]
# SSO_ENTRA_ADMIN_GROUPS=[]
#
# Role Mappings - map EntraID groups/roles to ContextForge RBAC roles
# Available roles: platform_admin (global), team_admin, developer, viewer (team scope)
# Format: JSON object {"group-id-or-name": "role-name"}
# Example with Object IDs:
# SSO_ENTRA_ROLE_MAPPINGS={"e5f6g7h8-1234-5678-90ab-cdef12345678":"developer","i9j0k1l2-1234-5678-90ab-cdef12345678":"team_admin"}
# Example with App Roles (recommended - more readable):
# SSO_ENTRA_ROLE_MAPPINGS={"Developer":"developer","TeamAdmin":"team_admin","Viewer":"viewer"}
# SSO_ENTRA_ROLE_MAPPINGS={}
#
# Default role for users without any group mappings (default: None = no automatic role)
# Set to "viewer" to give all EntraID users read-only access, or leave empty for explicit mapping only
# SSO_ENTRA_DEFAULT_ROLE=
#
# Synchronize role assignments on each login (default: true)
# When true: roles are updated based on current group membership (recommended for security)
# When false: roles are only assigned on first login (user creation)
# SSO_ENTRA_SYNC_ROLES_ON_LOGIN=true
#
# Group overage fallback (users with >200 Entra groups)
# When enabled, ContextForge calls Microsoft Graph /v1.0/me/getMemberObjects
# to resolve full group membership during SSO login.
# SSO_ENTRA_GRAPH_API_ENABLED=true
# SSO_ENTRA_GRAPH_API_TIMEOUT=10
# Maximum number of groups retained from Graph response (0 = no cap)
# SSO_ENTRA_GRAPH_API_MAX_GROUPS=0
# Generic OIDC Provider Configuration (Keycloak, Auth0, Authentik, etc.)
# SSO_GENERIC_ENABLED=false
# SSO_GENERIC_PROVIDER_ID=keycloak
# SSO_GENERIC_DISPLAY_NAME=Keycloak
# SSO_GENERIC_CLIENT_ID=your-oidc-client-id
# SSO_GENERIC_CLIENT_SECRET=your-oidc-client-secret
# SSO_GENERIC_AUTHORIZATION_URL=https://keycloak.company.com/auth/realms/master/protocol/openid-connect/auth
# SSO_GENERIC_TOKEN_URL=https://keycloak.company.com/auth/realms/master/protocol/openid-connect/token
# SSO_GENERIC_USERINFO_URL=https://keycloak.company.com/auth/realms/master/protocol/openid-connect/userinfo
# SSO_GENERIC_ISSUER=https://keycloak.company.com/auth/realms/master
# SSO_GENERIC_JWKS_URI=https://keycloak.company.com/auth/realms/master/protocol/openid-connect/certs
# SSO_GENERIC_SCOPE=openid profile email
# JWT claim that contains the user's groups (default: groups)
# SSO_GENERIC_GROUPS_CLAIM=groups
# Groups that grant platform_admin — checked at login (CSV or JSON list)
# SSO_GENERIC_ADMIN_GROUPS=["cf-platform-admin"]
# Map IdP group names to ContextForge roles (JSON object)
# SSO_GENERIC_ROLE_MAPPINGS={"cf-platform-admin":"platform_admin","cf-dev":"developer"}
# Default role assigned to users with no matching group mapping (omit to assign no role)
# SSO_GENERIC_DEFAULT_ROLE=platform_viewer
# SSO General Settings
# SSO_AUTO_CREATE_USERS=true
# JSON array of trusted email domains, e.g., ["example.com", "company.org"]
# SSO_TRUSTED_DOMAINS=[]
# Keep local admin authentication when SSO is enabled
# SSO_PRESERVE_ADMIN_AUTH=true
# Bootstrap Behavior: Auto-disable providers not in environment config
# When true: Providers configured in database but missing from SSO_*_ENABLED
# environment variables will be automatically disabled during bootstrap.
# This enforces environment config as the single source of truth.
# When false: Manually configured providers in database are preserved (default).
# IMPORTANT: Enabling this may be a breaking change for existing deployments
# with manually configured SSO providers.
# Default: false (backward compatible)
# SSO_AUTO_DISABLE_UNCONFIGURED_PROVIDERS=false
# SSO Issuers Configuration
# Optional JSON array of issuer URLs for SSO providers
# Example: ["https://idp1.example.com", "https://idp2.example.com"]
# Default: null (not set)
# SSO_ISSUERS=["https://idp.example.com"]
# SSO Admin Assignment Settings
# Email domains that automatically get admin privileges, e.g., ["yourcompany.com"]
# SSO_AUTO_ADMIN_DOMAINS=[]
# GitHub organizations whose members get admin privileges, e.g., ["your-org", "partner-org"]
# SSO_GITHUB_ADMIN_ORGS=[]
# Google Workspace domains that get admin privileges, e.g., ["company.com"]
# SSO_GOOGLE_ADMIN_DOMAINS=[]
# Require admin approval for new SSO registrations
# SSO_REQUIRE_ADMIN_APPROVAL=false
# Accept access tokens from trusted external SSO providers as API/MCP bearer creds.
# Each provider must also be opted in (SSOProvider.trusted_for_api_auth). Default off.
# SSO_API_TOKEN_AUTH_ENABLED=false
# Seconds to cache a provisioned external-IdP identity per token (avoids re-provisioning
# every M2M request). Shared across workers when CACHE_TYPE=redis. 0 disables.
# EXTERNAL_IDENTITY_CACHE_TTL=60
# =============================================================================
# Personal Teams Configuration
# =============================================================================
# Enable automatic personal team creation for new users
# AUTO_CREATE_PERSONAL_TEAMS=true
# Personal team naming prefix (optional; empty default derives slug from display name)
# PERSONAL_TEAM_PREFIX=personal
# Allow users to create organizational teams (admins can always create teams)
# ALLOW_TEAM_CREATION=true
# Allow users to request to join public teams
# ALLOW_TEAM_JOIN_REQUESTS=true
# Allow team owners to send invitations
# ALLOW_TEAM_INVITATIONS=true
# Default global role assigned to admin users
# DEFAULT_ADMIN_ROLE=platform_admin
# Default global role assigned to non-admin users
# DEFAULT_USER_ROLE=platform_viewer
# Default team role assigned to team owners (e.g. personal team creator)
# DEFAULT_TEAM_OWNER_ROLE=team_admin
# Default team role assigned to team members
# DEFAULT_TEAM_MEMBER_ROLE=viewer
# Team Limits
# MAX_TEAMS_PER_USER=50
# MAX_MEMBERS_PER_TEAM=100
# Hard ceiling on how many members can be seeded in a single POST /teams request
# (the `members` array). Validated at the request boundary before any write; the
# per-team MAX_MEMBERS_PER_TEAM limit still applies underneath.
# MAX_TEAM_MEMBER_SEEDS=500
# Team Invitation Settings
# INVITATION_EXPIRY_DAYS=7
# REQUIRE_EMAIL_VERIFICATION_FOR_INVITES=true
# =============================================================================
# Admin UI and API Toggles
# =============================================================================
# Enable the web-based Admin UI at /admin
# Options: true, false (default)
# PRODUCTION: Set to false for security unless needed
# Project defaults block enables this for local dev (MCPGATEWAY_UI_ENABLED=true there)
# MCPGATEWAY_UI_ENABLED=false
# Enable Admin REST API endpoints (/tools, /servers, /resources, etc.)
# Options: true, false (default)
# Required for: Admin UI functionality, programmatic management
# Project defaults block enables this for local dev (MCPGATEWAY_ADMIN_API_ENABLED=true there)
# MCPGATEWAY_ADMIN_API_ENABLED=false
# Use local CDN assets for airgapped deployments
# Options: true, false (default)
# When enabled, UI loads CSS/JS from local files instead of external CDNs
# Requires container build with downloaded assets (automatic in Containerfile.lite)
# MCPGATEWAY_UI_AIRGAPPED=false
# Embedded UI mode (hides logout + team selector by default)
# Options: true, false (default)
# MCPGATEWAY_UI_EMBEDDED=false
# Comma-separated list of UI sections to hide
# Valid values: overview, servers, gateways, tools, prompts, resources, roots, mcp-registry, metrics, plugins, export-import, logs, version-info, maintenance, teams, users, agents, tokens, settings
# Example: MCPGATEWAY_UI_HIDE_SECTIONS=prompts,resources,teams
# MCPGATEWAY_UI_HIDE_SECTIONS=
# Comma-separated list of header items to hide
# Valid values: logout, team_selector, user_identity, theme_toggle
# Example: MCPGATEWAY_UI_HIDE_HEADER_ITEMS=logout,team_selector
# MCPGATEWAY_UI_HIDE_HEADER_ITEMS=
# Admin-specific UI section/header hiding (separate from non-admin lists above)
# When unset, admins see all sections. Embedded mode defaults do NOT apply to admins.
# Same valid values as MCPGATEWAY_UI_HIDE_SECTIONS / MCPGATEWAY_UI_HIDE_HEADER_ITEMS
# Example: MCPGATEWAY_UI_HIDE_SECTIONS_ADMIN=maintenance
# MCPGATEWAY_UI_HIDE_SECTIONS_ADMIN=
# Example: MCPGATEWAY_UI_HIDE_HEADER_ITEMS_ADMIN=theme_toggle
# MCPGATEWAY_UI_HIDE_HEADER_ITEMS_ADMIN=
# Enable bulk import feature for mass tool/resource registration
# Options: true (default), false
# Allows importing multiple tools/resources in a single API call
# MCPGATEWAY_BULK_IMPORT_ENABLED=true
# Maximum number of tools allowed per bulk import request
# MCPGATEWAY_BULK_IMPORT_MAX_TOOLS=200
# Rate limiting for bulk import endpoint (requests per minute)
# MCPGATEWAY_BULK_IMPORT_RATE_LIMIT=10
# =============================================================================
# Tool Execution Cancellation
# =============================================================================
# Enable gateway-authoritative tool execution cancellation
# Options: true (default), false
# When enabled: Provides REST API endpoints for cancelling long-running tool executions
# - POST /cancellation/cancel - Cancel a running tool execution
# - GET /cancellation/status/{id} - Query tool execution status
# When disabled: Cancellation endpoints return 404, tool executions not tracked
# Features: Real asyncio task interruption, multi-worker coordination via Redis
# MCPGATEWAY_TOOL_CANCELLATION_ENABLED=true
# =============================================================================
# A2A (Agent-to-Agent) Configuration
# =============================================================================
# Enable A2A agent features (true/false)
# Allows registration and management of external AI agents
# MCPGATEWAY_A2A_ENABLED=true
# Maximum number of A2A agents allowed
# MCPGATEWAY_A2A_MAX_AGENTS=100
# Default timeout for A2A agent HTTP requests (seconds)
# MCPGATEWAY_A2A_DEFAULT_TIMEOUT=30
# Maximum retry attempts for failed A2A agent calls
# MCPGATEWAY_A2A_MAX_RETRIES=3
# Enable A2A agent metrics collection (true/false)
# MCPGATEWAY_A2A_METRICS_ENABLED=true
# =============================================================================
# MCP Server Catalog Configuration
# =============================================================================
# Enable MCP server catalog feature
# Allows defining a catalog of pre-configured MCP servers in a YAML file
# for easy discovery and management via the Admin UI
# Options: true (default), false
# MCPGATEWAY_CATALOG_ENABLED=true
# Path to the catalog configuration file
# YAML file containing MCP server definitions
# Default: mcp-catalog.yml
# MCPGATEWAY_CATALOG_FILE=mcp-catalog.yml
# Automatically health check catalog servers on startup and periodically
# Options: true (default), false
# MCPGATEWAY_CATALOG_AUTO_HEALTH_CHECK=true
# Catalog cache TTL in seconds
# How long to cache catalog data before refreshing
# Default: 3600 (1 hour)
# MCPGATEWAY_CATALOG_CACHE_TTL=3600
# Number of catalog servers to display per page
# Default: 100
# MCPGATEWAY_CATALOG_PAGE_SIZE=100
# =============================================================================
# Elicitation Support (MCP 2025-06-18)
# =============================================================================
# Enable elicitation passthrough - allows upstream MCP servers to request
# structured user input through connected clients (e.g., Claude Desktop)
# Per MCP spec 2025-06-18, elicitation enables interactive workflows where
# servers can dynamically gather information from users during operations
# MCPGATEWAY_ELICITATION_ENABLED=true
# Default timeout for user responses (seconds)
# How long to wait for users to respond to elicitation requests
# MCPGATEWAY_ELICITATION_TIMEOUT=60
# Maximum concurrent elicitation requests
# Prevents resource exhaustion from too many pending user input requests
# MCPGATEWAY_ELICITATION_MAX_CONCURRENT=100
# =============================================================================
# Header Passthrough Configuration
# =============================================================================
# SECURITY WARNING: Header passthrough is disabled by default for security.
# Only enable if you understand the security implications and have reviewed
# which headers should be passed through to backing MCP servers.
# ENABLE_HEADER_PASSTHROUGH=false
# Enable overwriting of base headers (advanced usage only)
# When disabled, passthrough headers cannot override gateway headers like Content-Type, Authorization
# ENABLE_OVERWRITE_BASE_HEADERS=false
# Default headers to pass through (when feature is enabled)
# JSON array format recommended: ["X-Tenant-Id", "X-Trace-Id"]
# Comma-separated also supported: X-Tenant-Id,X-Trace-Id
# NOTE: Authorization header removed from defaults for security
# DEFAULT_PASSTHROUGH_HEADERS=["X-Tenant-Id", "X-Trace-Id"]
# Passthrough headers source priority
# Controls where header configuration is read from:
# - "db": Database wins if configured, env as fallback (default, backward compatible)
# - "env": Environment variable always wins (ideal for Kubernetes/containerized deployments)
# - "merge": Union of both sources - env provides base, DB can add more headers
# PASSTHROUGH_HEADERS_SOURCE=db
# =============================================================================
# Security and CORS
# =============================================================================
# Skip SSL/TLS certificate verification for upstream requests
# Options: true, false (default)
# WARNING: Only use in development or with self-signed certificates!
# PRODUCTION: Must be false for security
# SKIP_SSL_VERIFY=false
# CORS allowed origins (JSON array of URLs)
# Controls which domains can make cross-origin requests to the gateway
# Format: JSON array starting with [ and ending with ]
# Example: ["http://localhost:3000", "https://app.example.com"]
# Use ["*"] to allow all origins (NOT RECOMMENDED)
# ALLOWED_ORIGINS='["http://localhost", "http://localhost:4444"]'
# Enable CORS (Cross-Origin Resource Sharing) handling
# Options: true (default), false
# Required for: Web browser clients, cross-domain API access
# CORS_ENABLED=true
# CORS allow credentials (true/false)
# CORS_ALLOW_CREDENTIALS=true
# Environment setting (development/production) - affects security defaults
# development: Auto-configures CORS for localhost:3000, localhost:8080, etc.
# production: Uses APP_DOMAIN for HTTPS origins, enforces secure cookies
# ENVIRONMENT is already defined in Basic Server Configuration section
# Domain configuration for production CORS origins
# In production, automatically creates origins: https://APP_DOMAIN, https://app.APP_DOMAIN, https://admin.APP_DOMAIN
# For production: set to your actual domain (e.g., mycompany.com)
# APP_DOMAIN is already defined in Basic Server Configuration section
# Security settings for cookies
# production: Automatically enables secure cookies regardless of this setting
# development: Set to false for HTTP development, true for HTTPS
# Project defaults block sets SECURE_COOKIES=false for local dev
# SECURE_COOKIES=true # active value set in project-defaults block
# Cookie SameSite attribute for CSRF protection
# strict: Maximum security, may break some OAuth flows
# lax: Good balance of security and compatibility (recommended)
# none: Requires Secure=true, allows cross-site usage
# COOKIE_SAMESITE=lax
# =============================================================================
# Query Parameter Authentication (INSECURE)
# =============================================================================
# WARNING: Query parameter authentication exposes API keys in URLs.
# API keys may appear in proxy logs, browser history, and server access logs.
# See CWE-598: Use of GET Request Method With Sensitive Query Strings.
# Only use when the upstream MCP server (e.g., Tavily) requires this method.
# Enable query parameter authentication for gateway peers
# Options: true, false (default)
# SECURITY: Disabled by default. Only enable with explicit allowlist.
# INSECURE_ALLOW_QUERYPARAM_AUTH=false
# Allowlist of hosts permitted to use query parameter authentication
# Format: JSON array of hostnames, e.g., ["mcp.tavily.com", "api.example.com"]
# Empty list [] allows any host when feature is enabled (NOT RECOMMENDED)
# PRODUCTION: Always configure an explicit allowlist
# INSECURE_QUERYPARAM_AUTH_ALLOWED_HOSTS=[]
# =============================================================================
# Security Headers Configuration
# =============================================================================
# Enable security headers middleware (true/false)
# SECURITY_HEADERS_ENABLED=true
# X-Frame-Options setting - Controls iframe embedding (also sets CSP frame-ancestors)
# DENY: Prevents all iframe embedding (recommended for security) → frame-ancestors 'none'
# SAMEORIGIN: Allows embedding from same domain only → frame-ancestors 'self'
# "" (empty string) / null / none: Removes iframe restrictions (no headers sent, allows embedding)
# ALLOW-FROM uri: Allows specific domain (deprecated, use CSP instead)
# ALLOW-ALL: Allows all embedding → frame-ancestors * file: http: https:
#
# Both X-Frame-Options header and CSP frame-ancestors directive are automatically synced.
# Modern browsers prioritize CSP frame-ancestors over X-Frame-Options.
# X_FRAME_OPTIONS=DENY
# Other security headers (true/false)
# X_CONTENT_TYPE_OPTIONS_ENABLED=true
# X_XSS_PROTECTION_ENABLED=true
# X_DOWNLOAD_OPTIONS_ENABLED=true
# HSTS (HTTP Strict Transport Security) settings
# HSTS_ENABLED=true
# HSTS max age in seconds (31536000 = 1 year)
# HSTS_MAX_AGE=31536000
# HSTS_INCLUDE_SUBDOMAINS=true
# Remove server identification headers (true/false)
# REMOVE_SERVER_HEADERS=true
# Enable HTTP Basic Auth for docs endpoints (in addition to Bearer token auth)
# Uses the same credentials as BASIC_AUTH_USER and BASIC_AUTH_PASSWORD
# DOCS_ALLOW_BASIC_AUTH is already defined in Basic Server Configuration section
# =============================================================================
# Response Compression Configuration
# =============================================================================
# Enable response compression (Brotli, Zstd, GZip)
# Options: true (default), false
# Reduces bandwidth by 30-70% for text-based responses (JSON, HTML, CSS, JS)
# Automatically negotiates compression algorithm based on client Accept-Encoding header
# Priority: Brotli (best compression) > Zstd (fast) > GZip (universal fallback)
# COMPRESSION_ENABLED=true
# Minimum response size in bytes to compress
# Responses smaller than this won't be compressed (compression overhead not worth it)
# Default: 500 bytes
# Set to 0 to compress all responses
# COMPRESSION_MINIMUM_SIZE=500
# GZip compression level (1-9)
# 1 = fastest compression, larger files
# 6 = balanced (recommended default)
# 9 = best compression, slower
# Default: 6
# COMPRESSION_GZIP_LEVEL=6
# Brotli compression quality (0-11)
# 0-3 = fast compression (lower quality)
# 4-9 = balanced compression (recommended)
# 10-11 = maximum compression (slower)
# Default: 4 (balanced)
# Note: Brotli offers 15-20% better compression than GZip at similar speeds
# COMPRESSION_BROTLI_QUALITY=4
# Zstd compression level (1-22)
# 1-3 = fast compression
# 4-9 = balanced compression
# 10+ = slower, maximum compression
# Default: 3 (fast)
# Note: Zstd is the fastest algorithm with good compression ratio
# COMPRESSION_ZSTD_LEVEL=3
# =============================================================================
# HTTPX Client Connection Pool Configuration
# =============================================================================
# Controls HTTP client settings for outbound requests (federation, health checks,
# A2A, SSO, MCP server connections, etc.). Most requests use a shared singleton
# client for ~20x better performance. SSE/streaming MCP connections use factory
# clients with the same settings for proper connection lifecycle management.
# Maximum total connections in the pool (default: 200, range: 10-1000)
# Formula: concurrent_outbound_requests × 1.5
# HTTPX_MAX_CONNECTIONS=200
# Maximum keepalive connections (default: 100, range: 1-500)
# Connections held open for reuse; typically 50% of max_connections
# HTTPX_MAX_KEEPALIVE_CONNECTIONS=100
# Keepalive connection expiry in seconds (default: 30.0, range: 5.0-300.0)
# How long idle connections stay in the pool before being closed
# HTTPX_KEEPALIVE_EXPIRY=30.0
# Connection timeout in seconds (default: 5.0, range: 1.0-60.0)
# Time to establish a new TCP connection (5s for LAN, increase for WAN)
# HTTPX_CONNECT_TIMEOUT=5.0
# Read timeout in seconds (default: 120.0, range: 1.0-600.0)
# Time to wait for response data after connection established
# Set high to accommodate slow MCP tool calls (60-90s+)
# HTTPX_READ_TIMEOUT=120.0
# Write timeout in seconds (default: 30.0, range: 1.0-600.0)
# Time to wait when sending request data
# HTTPX_WRITE_TIMEOUT=30.0
# Pool timeout in seconds (default: 10.0, range: 1.0-120.0)
# Time to wait for a connection from the pool (fail fast on exhaustion)
# HTTPX_POOL_TIMEOUT=10.0
# Enable HTTP/2 support (default: false)
# HTTP/2 provides multiplexing but may not be supported by all upstream servers
# HTTPX_HTTP2_ENABLED=false
# Admin operations read timeout in seconds (default: 30.0, range: 1.0-120.0)
# Shorter timeout for admin UI operations (model fetching, health checks)
# Use this to fail fast on admin pages instead of waiting for httpx_read_timeout
# HTTPX_ADMIN_READ_TIMEOUT=30.0
# =============================================================================
# Retry Config for HTTP Requests
# =============================================================================
# RETRY_MAX_ATTEMPTS=3
# seconds
# RETRY_BASE_DELAY=1.0
# seconds
# RETRY_MAX_DELAY=60.0
# fraction of delay
# RETRY_JITTER_MAX=0.5
# =============================================================================
# Logging
# =============================================================================
# Logging verbosity level
# Options: DEBUG, INFO, WARNING, ERROR (default), CRITICAL
# DEBUG: Detailed diagnostic info (verbose)
# INFO: General operational messages
# WARNING: Warning messages for potential issues
# ERROR: Error messages for failures (recommended for production)
# CRITICAL: Only critical failures
# PRODUCTION: Use ERROR to minimize I/O overhead and improve performance
# LOG_LEVEL=ERROR
# Log output format
# Options: json (default), text
# json: Structured JSON logs (good for log aggregation)
# text: Human-readable plain text
# LOG_FORMAT=json
# Enable file logging (in addition to console output)
# Options: true, false (default)
# LOG_TO_FILE=false
# Enable request payload logging for debugging
# Options: true, false (default)
# When enabled, logs HTTP request method, headers, query params, and body
# Sensitive data (passwords, tokens, etc.) is automatically masked
# LOG_REQUESTS=false
# File write mode when LOG_TO_FILE=true
# Options: a+ (append, default), w (overwrite on startup)
# LOG_FILEMODE=a+
# Project defaults block sets LOG_FILE and LOG_FOLDER for local debug logs
# LOG_FILE=mcpgateway.log
# LOG_FOLDER=logs
# LOG_ROTATION_ENABLED=false
# LOG_MAX_SIZE_MB=1
# LOG_BACKUP_COUNT=5
# LOG_BUFFER_SIZE_MB=1.0
# Masking value used for sensitive data in logs
# MASKED_AUTH_VALUE=*****
# Maximum request body size to log in detailed mode (bytes)
# Controls how much of the request body is parsed and logged when LOG_REQUESTS=true
# Separate from LOG_MAX_SIZE_MB which is for log file rotation
# Default: 16384 (16KB), Range: 1024-1048576 (1KB-1MB)
# LOG_DETAILED_MAX_BODY_SIZE=16384
# Path prefixes to skip from detailed request logging (JSON array or comma-separated)
# Use to exclude high-volume or low-value endpoints from logging overhead
# Examples: '[]' (empty), '["/metrics","/health"]', or "/metrics,/health"
# Default: [] (no additional endpoints skipped beyond built-in health checks)
# LOG_DETAILED_SKIP_ENDPOINTS=[]
# Sampling rate for detailed request logging (0.0-1.0)
# When LOG_REQUESTS=true, only log a fraction of requests to reduce overhead
# 1.0 = log all requests, 0.5 = log 50%, 0.1 = log 10%
# Default: 1.0 (log all requests when detailed logging is enabled)
# LOG_DETAILED_SAMPLE_RATE=1.0
# Enable user identity resolution via database lookup during request logging
# When false (default), only uses cached user identity from request context
# When true, falls back to DB lookup if no cached identity (adds overhead)
# Default: false (avoid implicit DB queries for better performance)
# LOG_RESOLVE_USER_IDENTITY=false
# ═══════════════════════════════════════════════════════════════════════════════
# Structured Log Database Persistence
# ═══════════════════════════════════════════════════════════════════════════════
# Persist structured logs to the database for search, tracing, and metrics.
# Options: true, false (default)
#
# When ENABLED, you get:
# - Log Search API (/api/logs/search) - search logs by level, component, user, time
# - Request Tracing (/api/logs/trace/{id}) - trace all logs for a correlation ID
# - Performance Metrics - aggregated p50/p95/p99 latencies, error rates
# - Admin UI log viewer with filtering and search
#
# When DISABLED:
# - Logs only go to console/file (no database writes)
# - Better performance (no DB I/O per log entry)
# - Log search/trace/metrics APIs return empty results
# - Use this if you have an external log aggregator (ELK, Datadog, etc.)
#
# PERFORMANCE NOTE: Each log entry triggers a synchronous database write.
# Disable this in high-throughput production environments or use external logging.
# STRUCTURED_LOGGING_ENABLED=true
# STRUCTURED_LOGGING_DATABASE_ENABLED=false
# STRUCTURED_LOGGING_EXTERNAL_ENABLED=false
# Log Search Configuration
# Maximum results per log search query
# LOG_SEARCH_MAX_RESULTS=1000
# Number of days to retain logs in the database
# LOG_RETENTION_DAYS=30
# External Log Integration Configuration
# Send logs to Elasticsearch
# ELASTICSEARCH_ENABLED=false
# ELASTICSEARCH_URL=
# ELASTICSEARCH_INDEX_PREFIX=mcpgateway-logs
# Send logs to syslog
# SYSLOG_ENABLED=false
# SYSLOG_HOST=
# SYSLOG_PORT=514
# Send logs to webhook endpoints
# WEBHOOK_LOGGING_ENABLED=false
# WEBHOOK_LOGGING_URLS=[]
# SIEM Export Configuration (security/audit event export pipeline)
# SIEM_EXPORT_ENABLED=false
# SIEM_EXPORT_BATCH_SIZE=100
# SIEM_EXPORT_FLUSH_INTERVAL_SECONDS=5
# SIEM_EXPORT_QUEUE_MAX_SIZE=10000
# SIEM_EXPORT_MAX_RETRIES=10
# SIEM_EXPORT_BACKOFF_MAX_SECONDS=60
# SIEM_EXPORT_BACKPRESSURE_POLICY=drop_oldest
# SIEM_EXPORT_EVENT_SOURCES=["auth","security","audit"]
# SIEM_EXPORT_STREAM_NAME=mcpgateway:siem:events
# SIEM_EXPORT_CONSUMER_GROUP=siem-exporters
# SIEM_EXPORT_URL_ALLOWLIST=[]
# SIEM_EXPORT_REDACT_FIELDS=["user_email","authorization","token","password","secret","api_key"]
# SIEM_DESTINATIONS=[]
# SIEM_DESTINATIONS_FILE=
# Correlation ID / Request Tracking
# Enable automatic correlation ID tracking for unified request tracing
# Options: true (default), false
# CORRELATION_ID_ENABLED=true
# HTTP header name for correlation ID (default: X-Correlation-ID)
# CORRELATION_ID_HEADER=X-Correlation-ID
# Preserve incoming correlation IDs from clients (default: true)
# CORRELATION_ID_PRESERVE=true
# Include correlation ID in HTTP response headers (default: true)
# CORRELATION_ID_RESPONSE_HEADER=true
# ═══════════════════════════════════════════════════════════════════════════════
# Database Query Logging (N+1 Detection)
# ═══════════════════════════════════════════════════════════════════════════════
# Enable database query logging to file for N+1 detection and performance analysis
# Use: make dev-query-log (starts server with logging enabled)
# Use: make query-log-analyze (analyze logs for N+1 patterns)
# DB_QUERY_LOG_ENABLED=false
# DB_QUERY_LOG_FILE=logs/db-queries.log
# DB_QUERY_LOG_JSON_FILE=logs/db-queries.jsonl
# DB_QUERY_LOG_FORMAT=both
# DB_QUERY_LOG_MIN_QUERIES=1
# DB_QUERY_LOG_INCLUDE_PARAMS=false
# DB_QUERY_LOG_DETECT_N1=true
# DB_QUERY_LOG_N1_THRESHOLD=3
# =============================================================================
# Metrics Aggregation Configuration
# =============================================================================
# Aggregates structured logs into performance metrics on a schedule.
# Requires STRUCTURED_LOGGING_DATABASE_ENABLED=true.
# METRICS_AGGREGATION_ENABLED=true
# Hours of structured logs to backfill into metrics on startup
# METRICS_AGGREGATION_BACKFILL_HOURS=6
# Time window for metrics aggregation (minutes)
# METRICS_AGGREGATION_WINDOW_MINUTES=5
# Seconds between aggregation runs (defaults to METRICS_AGGREGATION_WINDOW_MINUTES * 60).
# Raise to reduce DB pressure on multi-worker deployments.
# METRICS_AGGREGATION_INTERVAL_SECONDS=300
# Automatically start the aggregation loop on application startup
# METRICS_AGGREGATION_AUTO_START=false
# =============================================================================
# Execution Metrics Recording
# =============================================================================
# Controls whether tool/resource/prompt/server/A2A execution metrics are written
# to the database. Each MCP operation (tool call, resource read, etc.) creates
# one database row with: entity_id, timestamp, response_time (seconds), is_success, error_message.
#
# Disable if you rely entirely on external observability (ELK, Datadog, Splunk)
# to reduce database I/O overhead.
#
# Note: This does NOT affect:
# - Log aggregation (METRICS_AGGREGATION_ENABLED) - aggregates StructuredLogEntry into PerformanceMetric
# - Prometheus metrics (ENABLE_METRICS) - /metrics endpoint for Prometheus scraping
# - Observability metrics (OBSERVABILITY_METRICS_ENABLED) - internal observability system
#
# To disable log aggregation as well, also set METRICS_AGGREGATION_ENABLED=false
# DB_METRICS_RECORDING_ENABLED=true
# =============================================================================
# Metrics Buffer Configuration
# =============================================================================
# Batches tool/resource/prompt/server metric writes to reduce DB pressure under load
# Enable buffered metrics writes (default: true)
# When enabled, metrics are accumulated in memory and flushed periodically
# METRICS_BUFFER_ENABLED=true
# Seconds between automatic metrics buffer flushes (default: 60, range: 5-300)
# Lower values = more frequent writes, higher values = better batching
# METRICS_BUFFER_FLUSH_INTERVAL=60
# Maximum buffered metrics before forced flush (default: 1000, range: 100-10000)
# Prevents unbounded memory growth under very high load
# METRICS_BUFFER_MAX_SIZE=1000
# Metrics Cache Configuration
# =============================================================================
# Caches aggregate metrics queries to reduce database load under high traffic
# See GitHub Issue #1734 for performance optimization details
# Enable in-memory caching for aggregate metrics queries (default: true)
# When enabled, aggregate_metrics() results are cached to reduce database load
# METRICS_CACHE_ENABLED=true
# TTL for cached aggregate metrics in seconds (default: 60, range: 1-300)
# Lower values = fresher data, higher values = better performance
# Recommended: 60-300 seconds for high-traffic deployments (see Issue #1906)
# METRICS_CACHE_TTL_SECONDS=60
# Metrics Cleanup Configuration
# =============================================================================
# Automatically deletes old metrics data to prevent unbounded table growth
# Enable automatic cleanup of old metrics data (default: true)
# METRICS_CLEANUP_ENABLED=true
# Days to retain raw metrics when rollup is disabled (default: 7, range: 1-365)
# This is a fallback - when METRICS_DELETE_RAW_AFTER_ROLLUP=true, raw metrics
# are deleted based on METRICS_DELETE_RAW_AFTER_ROLLUP_HOURS instead.
# METRICS_RETENTION_DAYS=7
# Hours between automatic cleanup runs (default: 1, range: 1-168)
# METRICS_CLEANUP_INTERVAL_HOURS=1
# Batch size for metrics deletion (default: 10000, range: 100-100000)
# Larger batches are faster but may cause longer table locks
# METRICS_CLEANUP_BATCH_SIZE=10000
# Milliseconds to sleep between batch DELETEs (default: 50, range: 0-5000)
# Increase to reduce DB pressure on high-traffic deployments (0 = no sleep)
# METRICS_CLEANUP_BATCH_SLEEP_MS=50
# Metrics Rollup Configuration
# =============================================================================
# Aggregates raw metrics into hourly summaries for efficient historical queries
# Rollups preserve counts, averages, and percentiles (p50, p95, p99)
# Enable hourly metrics rollup for efficient historical queries (default: true)
# METRICS_ROLLUP_ENABLED=true
# Hours between rollup runs (default: 1, range: 1-24)
# METRICS_ROLLUP_INTERVAL_HOURS=1
# Days to retain hourly rollup data (default: 365, range: 30-3650)
# METRICS_ROLLUP_RETENTION_DAYS=365
# Hours to re-process on each rollup run to catch late-arriving data (default: 1, range: 1-48)
# Smaller = less CPU/IO overhead, larger = more tolerance for delayed metrics
# METRICS_ROLLUP_LATE_DATA_HOURS=1
# Delete raw metrics after hourly rollup exists (default: true)
# When true, raw metrics older than METRICS_DELETE_RAW_AFTER_ROLLUP_HOURS are
# deleted once hourly rollups exist. Rollups preserve all analytics.
#
# Set to false only if you need raw metrics indefinitely (e.g., exact error
# messages, individual request debugging without external observability).
#
# If using ELK, Datadog, Splunk, or similar platforms for debugging, keep this
# true - your external platform handles detailed logs and traces.
# METRICS_DELETE_RAW_AFTER_ROLLUP=true
# Hours to retain raw metrics when hourly rollup exists (default: 1, range: 1-8760)
# After this period, raw metrics are deleted but hourly rollups remain.
# Increase to 168 if you need raw data for debugging without external observability.
# METRICS_DELETE_RAW_AFTER_ROLLUP_HOURS=1
# Authentication Cache Configuration
# =============================================================================
# Caches authentication data (user, team, revocation) to reduce database queries
# Uses Redis when available, falls back to in-memory cache
# Applies to both Python MCP and Rust MCP because public MCP auth still runs in Python first
# Enable Redis/in-memory caching for authentication data (default: true)
# Significantly reduces database queries during authentication
# Disabling this also disables the shared auth cache benefit for RUST_MCP_MODE=edge/full
# AUTH_CACHE_ENABLED=true
# TTL in seconds for cached user data (default: 60, range: 10-300)
# Also affects MCP Streamable HTTP auth, including Rust-fronted MCP requests
# AUTH_CACHE_USER_TTL=60
# TTL in seconds for token revocation cache (default: 30, range: 5-120)
# Security-critical: keep short to limit exposure window for revoked tokens
# Also affects MCP auth on both Python and Rust runtime modes
# AUTH_CACHE_REVOCATION_TTL=30
# TTL in seconds for team membership cache (default: 60, range: 10-300)
# AUTH_CACHE_TEAM_TTL=60
# TTL in seconds for user role in team cache (default: 60, range: 10-300)
# Caches get_user_role_in_team() which is called 11+ times per team operation
# AUTH_CACHE_ROLE_TTL=60
# Enable caching for get_user_teams() (default: true)
# Set to false to disable teams list caching (useful for debugging)
# Also affects session-token MCP auth on Python and Rust modes
# AUTH_CACHE_TEAMS_ENABLED=true
# TTL in seconds for user teams list cache (default: 60, range: 10-300)
# Caches get_user_teams() which is called 20+ times per request for auth checks
# AUTH_CACHE_TEAMS_TTL=60
# Batch auth DB queries into single call (default: true)
# Reduces 3 separate queries to 1, improving performance under load
# Streamable HTTP MCP auth uses this too before falling back to per-query checks
# AUTH_CACHE_BATCH_QUERIES=true
# Registry Cache Configuration
# =============================================================================
# Caches registry list endpoints (tools, prompts, resources, agents, servers, gateways)
# Uses Redis when available, falls back to in-memory cache
# Reduces DB queries for frequently accessed list endpoints
# Enable registry caching (default: true)
# REGISTRY_CACHE_ENABLED=true
# TTL in seconds for tools list cache (default: 20, range: 5-300)
# REGISTRY_CACHE_TOOLS_TTL=20
# TTL in seconds for prompts list cache (default: 15, range: 5-300)
# REGISTRY_CACHE_PROMPTS_TTL=15
# TTL in seconds for resources list cache (default: 15, range: 5-300)
# REGISTRY_CACHE_RESOURCES_TTL=15
# TTL in seconds for A2A agents list cache (default: 20, range: 5-300)
# REGISTRY_CACHE_AGENTS_TTL=20
# TTL in seconds for servers list cache (default: 20, range: 5-300)
# REGISTRY_CACHE_SERVERS_TTL=20
# TTL in seconds for gateways list cache (default: 20, range: 5-300)
# REGISTRY_CACHE_GATEWAYS_TTL=20
# TTL in seconds for catalog servers list cache (default: 300, range: 60-600)
# Longer TTL since external catalog changes infrequently
# REGISTRY_CACHE_CATALOG_TTL=300
# Tool Lookup Cache Configuration
# =============================================================================
# Caches tool lookup by name in the invoke_tool hot path
# Uses in-memory L1 cache and optional Redis L2 cache when CACHE_TYPE=redis
# Enable tool lookup caching (default: true)
# TOOL_LOOKUP_CACHE_ENABLED=true
# TTL in seconds for tool lookup cache entries (default: 60, range: 5-600)
# TOOL_LOOKUP_CACHE_TTL_SECONDS=60
# TTL in seconds for negative cache entries (default: 10, range: 1-60)
# Used for missing/inactive/offline tool lookups
# TOOL_LOOKUP_CACHE_NEGATIVE_TTL_SECONDS=10
# Max entries for in-memory L1 tool cache (default: 10000, range: 100-1000000)
# TOOL_LOOKUP_CACHE_L1_MAXSIZE=10000
# Enable Redis L2 cache when CACHE_TYPE=redis (default: true)
# TOOL_LOOKUP_CACHE_L2_ENABLED=true
# Admin Stats Cache Configuration
# =============================================================================
# Caches admin dashboard statistics (entity counts, observability metrics)
# Reduces expensive aggregate queries under dashboard load
# Enable admin stats caching (default: true)
# ADMIN_STATS_CACHE_ENABLED=true
# TTL in seconds for system stats cache (default: 60, range: 10-300)
# ADMIN_STATS_CACHE_SYSTEM_TTL=60
# TTL in seconds for observability stats cache (default: 30, range: 10-120)
# ADMIN_STATS_CACHE_OBSERVABILITY_TTL=30
# TTL in seconds for tags listing cache (default: 120, range: 30-600)
# ADMIN_STATS_CACHE_TAGS_TTL=120
# TTL in seconds for plugin stats cache (default: 120, range: 30-600)
# ADMIN_STATS_CACHE_PLUGINS_TTL=120
# TTL in seconds for performance aggregates cache (default: 60, range: 15-300)
# ADMIN_STATS_CACHE_PERFORMANCE_TTL=60
# Team Member Count Cache
# Reduces N+1 queries in admin UI team listings
# Enable team member count caching (default: true)
# TEAM_MEMBER_COUNT_CACHE_ENABLED=true
# TTL in seconds for team member count cache (default: 300, range: 30-3600)
# TEAM_MEMBER_COUNT_CACHE_TTL=300
# Transport Protocol Configuration
# Options: all (default), sse, streamablehttp, http
# - all: Enable all transport protocols
# - sse: Server-Sent Events only
# - streamablehttp: Streaming HTTP only
# - http: Standard HTTP JSON-RPC only
# TRANSPORT_TYPE=all
# WebSocket keepalive ping interval in seconds
# Prevents connection timeout for idle WebSocket connections
# WEBSOCKET_PING_INTERVAL=30
# Enable legacy WebSocket JSON-RPC relay endpoint (/ws)
# SECURITY: Disabled by default. Enable only for clients that require /ws.
# MCPGATEWAY_WS_RELAY_ENABLED=false
# Enable reverse-proxy transport endpoints (/reverse-proxy/*)
# SECURITY: Disabled by default. Enable only when using mcpgateway.reverse_proxy.
# MCPGATEWAY_REVERSE_PROXY_ENABLED=false
# SSE client retry timeout in milliseconds
# Time client waits before reconnecting after SSE connection loss
# SSE_RETRY_TIMEOUT=5000
# Enable SSE keepalive events to prevent proxy/firewall timeouts
# Options: true (default), false
# SSE_KEEPALIVE_ENABLED=true
# SSE keepalive event interval in seconds
# How often to send keepalive events when SSE_KEEPALIVE_ENABLED=true
# SSE_KEEPALIVE_INTERVAL=30
# Streamable HTTP — MCP GET stream support
# Enable the GET /mcp stream endpoint for MCP Streamable HTTP transport
# Default: true
# MCP_GET_STREAM_ENABLED=true
# TTL in seconds for the per-session GET-stream listener claim
# Refreshed by heartbeat while the GET handler holds the connection
# Default: 30
# MCP_GET_STREAM_LISTENER_TTL_SECONDS=30
# Maximum bytes the body-peek helper will buffer per POST before falling through
# to the streaming receive path. NEVER rejects the request — only bounds peek memory.
# Default: 4194304 (4MB)
# MCP_BODY_PEEK_MAX_BYTES=4194304
# ─────────────────────────────────────────────────────────────────────────────
# SSE Connection Protection (CPU Spin Loop Mitigation - Layer 1)
# ─────────────────────────────────────────────────────────────────────────────
# These settings detect and close dead SSE connections before they trigger
# CPU spin loops in anyio's _deliver_cancellation method.
#
# Part of Issue #2360 mitigation. See: docs/docs/operations/cpu-spin-loop-mitigation.md
# Upstream issue: https://github.com/agronholm/anyio/issues/695
# SSE send timeout in seconds
# Timeout for ASGI send() calls - protects against sends that hang indefinitely
# when client connection is in a bad state. Does NOT affect MCP server response times.
# Set to 0 to disable. Default matches keepalive interval.
# SSE_SEND_TIMEOUT=30.0
# SSE rapid yield detection
# If more than SSE_RAPID_YIELD_MAX yields occur within SSE_RAPID_YIELD_WINDOW_MS,
# the connection is assumed dead and closed. Set SSE_RAPID_YIELD_MAX=0 to disable.
# SSE_RAPID_YIELD_WINDOW_MS=1000
# SSE_RAPID_YIELD_MAX=50
# Streaming HTTP Configuration
# Enable stateful sessions (stores session state server-side)
# Options: true, false (default)
# false: Stateless mode (better for scaling)
# true: Stateful mode (requires CACHE_TYPE=redis for multi-worker deployments)
# USE_STATEFUL_SESSIONS=false
# Multi-Worker Session Affinity (ADR-038)
# Routes client requests to the same worker for session continuity in multi-worker deployments
# Requires: CACHE_TYPE=redis, USE_STATEFUL_SESSIONS=true, Redis accessible at REDIS_URL
# IMPORTANT: Redis must be enabled (CACHE_TYPE=redis) for session affinity to work
# MCPGATEWAY_SESSION_AFFINITY_ENABLED=false
# Session ownership TTL in seconds (default: 300 = 5 minutes)
# How long a worker owns a session before it expires
# MCPGATEWAY_SESSION_AFFINITY_TTL=300
# Forwarded request timeout in seconds (default: 30)
# Timeout when forwarding requests between workers via Redis Pub/Sub
# MCPGATEWAY_POOL_RPC_FORWARD_TIMEOUT=30
# Enable JSON response format for streaming HTTP
# Options: true (default), false
# true: Return JSON responses, false: Return SSE stream
# JSON_RESPONSE_ENABLED=true
# Event store configuration for stateful sessions
# Ring buffer size per stream (default: 100)
# Controls how many events are kept in memory before oldest are evicted
# STREAMABLE_HTTP_MAX_EVENTS_PER_STREAM=100
# Stream TTL in seconds (default: 3600 = 1 hour)
# How long event streams are kept in Redis before automatic cleanup
# STREAMABLE_HTTP_EVENT_TTL=3600
# Federation Configuration
# Timeout for federation requests in seconds
# Default: 120 seconds (matches config.py)
# FEDERATION_TIMEOUT=120
# Resource Configuration
# RESOURCE_CACHE_SIZE=1000
# RESOURCE_CACHE_TTL=3600
# MAX_RESOURCE_SIZE=10485760
# Allowed MIME types for resources (JSON array)
# Controls which content types are allowed for resource handling
# Default includes common text, image, and data formats
# Example: ["text/plain", "text/markdown", "application/json", "image/png"]
# To add custom types: ["text/plain", "application/pdf", "video/mp4"]
# ALLOWED_MIME_TYPES=["text/plain", "text/markdown", "text/html", "application/json", "application/xml", "image/png", "image/jpeg", "image/gif"]
# Tool Configuration
# TOOL_TIMEOUT=60
# MAX_TOOL_RETRIES=3
# TOOL_RATE_LIMIT=100
# TOOL_CONCURRENT_LIMIT=10
# GATEWAY_TOOL_NAME_SEPARATOR=-
# Maximum length of response text returned for non-JSON REST API responses
# Longer responses are truncated to prevent exposing excessive sensitive data
# Default: 5000 characters, Range: 1000-100000
# REST_RESPONSE_TEXT_MAX_LENGTH=5000
# jq filter sandbox for tool jsonpath_filter execution.
# Filters run in a forked worker with a cleared environment and a time limit.
# Options: subprocess (default, safe), inprocess (unsafe, no environment scrub, no time limit)
# JQ_FILTER_EXECUTION=subprocess
# JQ_FILTER_TIMEOUT_SECONDS=2.0
# JQ_FILTER_WORKERS=2
# Prompt Configuration
# PROMPT_CACHE_SIZE=100
# MAX_PROMPT_SIZE=102400
# PROMPT_RENDER_TIMEOUT=10
# =============================================================================
# MCP Server Health Check Configuration
# =============================================================================
# Interval between health checks in seconds (default: 60)
# Project defaults block sets HEALTH_CHECK_INTERVAL=300 for local dev
# HEALTH_CHECK_INTERVAL=60
# Health check timeout in seconds (default: 30)
# HEALTH_CHECK_TIMEOUT=30
# Per-check timeout (seconds) to bound total time of one gateway health check (default: 30.0)
# GATEWAY_HEALTH_CHECK_TIMEOUT=30.0
# Consecutive failures before marking gateway offline (default: 3)
# UNHEALTHY_THRESHOLD=3
# Gateway URL validation timeout in seconds (default: 5)
# GATEWAY_VALIDATION_TIMEOUT=5
# Maximum redirects allowed during gateway validation (default: 5)
# GATEWAY_MAX_REDIRECTS=5
# Maximum concurrent health checks per worker (default: 10)
# MAX_CONCURRENT_HEALTH_CHECKS=10
# -----------------------------------------------------------------------------
# Async Gateway Lifecycle
# -----------------------------------------------------------------------------
# When enabled, gateway create/update/delete requests return 202 Accepted after
# persisting the gateway lifecycle state. Background workers then initialize or
# delete gateways from the database-backed pending/deleting queues.
#
# Disabled by default to preserve existing synchronous gateway behavior.
# GATEWAY_ASYNC_LIFECYCLE_ENABLED=false
# Worker polling interval in seconds for pending/deleting gateways (default: 5.0)
# GATEWAY_ASYNC_LIFECYCLE_POLL_INTERVAL=5.0
# Timeout in seconds for one async gateway initialization attempt (default: 30.0)
# GATEWAY_ASYNC_LIFECYCLE_ATTEMPT_TIMEOUT=30.0
# Lease TTL in seconds for database-backed lifecycle claims (default: 90.0)
# GATEWAY_ASYNC_LIFECYCLE_LEASE_SECONDS=90.0
# Bounded shutdown wait in seconds for lifecycle task cancellation (default: 5.0)
# GATEWAY_ASYNC_LIFECYCLE_SHUTDOWN_TIMEOUT=5.0
# -----------------------------------------------------------------------------
# Auto-Refresh / Polling (requires health checks above)
# -----------------------------------------------------------------------------
# Automatically re-fetch tools, prompts, and resources from upstream MCP
# servers during health-check cycles. When disabled (default), tool lists
# are only updated on manual refresh or gateway registration.
#
# AUTO_REFRESH_SERVERS=false
# GATEWAY_AUTO_REFRESH_INTERVAL=300 # interval in seconds (minimum: 60)
# -----------------------------------------------------------------------------
# Hot/Cold Server Classification (requires auto-refresh + Redis)
# -----------------------------------------------------------------------------
# Classifies upstream servers by MCP session pool usage:
# hot (top 20% by recent usage) → polled at 1x GATEWAY_AUTO_REFRESH_INTERVAL
# cold (remaining 80%) → polled at 3x GATEWAY_AUTO_REFRESH_INTERVAL
#
# Requires Redis for multi-worker leader election and state sharing.
# Falls back to local-only (always-poll) in single-worker mode (make dev).
# Poll intervals are auto-derived — no additional config needed.
#
# HOT_COLD_CLASSIFICATION_ENABLED=false
# File lock name for gateway service leader election
# Used to coordinate multiple gateway instances when running in cluster mode
# Default: "gateway_service_leader.lock"
# FILELOCK_NAME=gateway_service_leader.lock
# =============================================================================
# MCP Session Pool Configuration
# =============================================================================
# ─────────────────────────────────────────────────────────────────────────────
# Cleanup Timeouts (CPU Spin Loop Mitigation - Layer 2)
# ─────────────────────────────────────────────────────────────────────────────
# Limit how long cleanup waits for stuck tasks. Shorter timeouts = faster
# recovery from spin loops but may interrupt legitimate cleanup.
#
# Part of Issue #2360 mitigation. See: docs/docs/operations/cpu-spin-loop-mitigation.md
# Upstream issue: https://github.com/agronholm/anyio/issues/695
# Timeout for session/transport cleanup operations (seconds).
# Controls how long to wait for session.__aexit__() and transport.__aexit__()
# when closing sessions.
#
# IMPORTANT: Does NOT affect tool execution time - only cleanup of
# idle/released sessions. Tool execution uses TOOL_TIMEOUT instead.
#
# Timeout for SSE task group cleanup (seconds).
# Controls how long to wait for internal tasks to respond before forcing cleanup.
# Only affects cancelled connections, not normal SSE operation.
# Default: 5.0
# SSE_TASK_GROUP_CLEANUP_TIMEOUT=5.0
# =============================================================================
# EXPERIMENTAL: anyio Monkey-Patch (CPU Spin Loop Mitigation - Layer 3)
# =============================================================================
# Last resort workaround that patches anyio to limit _deliver_cancellation iterations.
# Use only if Layers 1-2 don't fully resolve the issue.
#
# Part of Issue #2360 mitigation. See: docs/docs/operations/cpu-spin-loop-mitigation.md
# Upstream issue: https://github.com/agronholm/anyio/issues/695
#
# WARNING: This is EXPERIMENTAL and may be removed when upstream fixes the issue.
# =============================================================================
# Trade-offs when enabled:
# - Prevents indefinite CPU spin (good)
# - May leave some tasks uncancelled (usually harmless)
# - Worker recycling (GUNICORN_MAX_REQUESTS) cleans up orphaned tasks
#
# Default: false
# ANYIO_CANCEL_DELIVERY_PATCH_ENABLED=false
# Maximum iterations for _deliver_cancellation before forcing termination.
# Only used when ANYIO_CANCEL_DELIVERY_PATCH_ENABLED=true.
# - Higher (100+) = more attempts to cancel, longer potential spin
# - Lower (50) = faster recovery, more orphaned tasks
# Default: 100
# ANYIO_CANCEL_DELIVERY_MAX_ITERATIONS=100
# =============================================================================
# Default Root Paths
# =============================================================================
# Default root URIs (JSON array). Values are validated by the root URI policy below.
# Compatibility note: roots are disabled by default until accepted metadata schemes
# are explicitly allowlisted. MCP filesystem roots require file:// opt-in plus prefixes.
# Example: ["https://example.com/project"]
# Default: []
# DEFAULT_ROOTS=[]
# Root URI metadata policy. ROOT_ALLOWED_SCHEMES accepts scheme names without ://.
# Supported non-file schemes: http, https, ws, wss.
# Default: []
# ROOT_ALLOWED_SCHEMES=[]
# Example:
# ROOT_ALLOWED_SCHEMES=["https","wss"]
# file:// roots are disabled by default and require explicit POSIX prefixes.
# Default: false
# ROOT_ALLOW_FILE_SCHEME=false
# Default: []
# ROOT_ALLOWED_FILE_PREFIXES=[]
# Example:
# ROOT_ALLOW_FILE_SCHEME=true
# ROOT_ALLOWED_FILE_PREFIXES=["/workspace","/srv/data"]
# =============================================================================
# OpenTelemetry Observability Configuration
# =============================================================================
# Enable distributed tracing and metrics collection
# Options: true (default), false
# OTEL_ENABLE_OBSERVABILITY=false
# Traces exporter backend
# Options: otlp (default), jaeger, zipkin, console, none
# - otlp: OpenTelemetry Protocol (works with many backends)
# - jaeger: Direct Jaeger integration
# - zipkin: Direct Zipkin integration
# - console: Print to stdout (debugging)
# - none: Disable tracing
# OTEL_TRACES_EXPORTER=otlp
# OTLP endpoint for traces and metrics
# Examples:
# - Phoenix: http://localhost:4317
# - Jaeger: http://localhost:4317
# - Tempo: http://localhost:4317
# Project defaults block sets OTEL_EXPORTER_OTLP_ENDPOINT for local tracing
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# OTLP protocol
# Options: grpc (default), http
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# Use insecure connection (no TLS) for OTLP
# Options: true (default for localhost), false (use TLS)
# OTEL_EXPORTER_OTLP_INSECURE=true
# OTEL_EXPORTER_OTLP_HEADERS=key1=value1,key2=value2
# OTEL_EXPORTER_JAEGER_ENDPOINT=http://localhost:14268/api/traces
# OTEL_EXPORTER_ZIPKIN_ENDPOINT=http://localhost:9411/api/v2/spans
# OTEL_SERVICE_NAME=mcp-gateway
# OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,environment=production
# OTEL_BSP_MAX_QUEUE_SIZE=2048
# OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
# OTEL_BSP_SCHEDULE_DELAY=5000
# =============================================================================
# W3C Baggage Configuration
# =============================================================================
# OpenTelemetry baggage allows propagating context across service boundaries.
# Use this to extract HTTP headers into baggage for distributed tracing.
#
# Common use cases:
# - Multi-tenant request tracking (tenant IDs, organization IDs)
# - User context propagation (user IDs, session IDs)
# - Request correlation (trace IDs, request IDs)
# - Feature flags and A/B testing metadata
# - Security context (authentication realm, authorization scope)
# Enable extraction of HTTP headers into OpenTelemetry baggage
# Options: true, false (default)
# When enabled, headers matching OTEL_BAGGAGE_HEADER_MAPPINGS are extracted
# and attached to the current span's baggage for downstream propagation
# OTEL_BAGGAGE_ENABLED=false
# JSON array mapping HTTP headers to baggage keys
# Format: [{"header_name": "X-Header", "baggage_key": "baggage.key"}]
#
# IMPORTANT: Baggage keys should use dot notation for namespacing
# Recommended prefixes: tenant., user., request., feature., security.
#
# Example 1: Basic multi-tenant tracking
# OTEL_BAGGAGE_HEADER_MAPPINGS='[
# {"header_name": "X-Tenant-ID", "baggage_key": "tenant.id"},
# {"header_name": "X-Organization-ID", "baggage_key": "tenant.org_id"}
# ]'
#
# Example 2: User context and request correlation
# OTEL_BAGGAGE_HEADER_MAPPINGS='[
# {"header_name": "X-User-ID", "baggage_key": "user.id"},
# {"header_name": "X-User-Email", "baggage_key": "user.email"},
# {"header_name": "X-Session-ID", "baggage_key": "user.session_id"},
# {"header_name": "X-Request-ID", "baggage_key": "request.id"},
# {"header_name": "X-Correlation-ID", "baggage_key": "request.correlation_id"}
# ]'
#
# Example 3: Comprehensive tracking (multi-tenant + user + features)
# OTEL_BAGGAGE_HEADER_MAPPINGS='[
# {"header_name": "X-Tenant-ID", "baggage_key": "tenant.id"},
# {"header_name": "X-Tenant-Name", "baggage_key": "tenant.name"},
# {"header_name": "X-User-ID", "baggage_key": "user.id"},
# {"header_name": "X-User-Role", "baggage_key": "user.role"},
# {"header_name": "X-Request-ID", "baggage_key": "request.id"},
# {"header_name": "X-Feature-Flags", "baggage_key": "feature.flags"},
# {"header_name": "X-AB-Test-Variant", "baggage_key": "feature.ab_variant"}
# ]'
#
# Example 4: Security and compliance tracking
# OTEL_BAGGAGE_HEADER_MAPPINGS='[
# {"header_name": "X-Auth-Realm", "baggage_key": "security.realm"},
# {"header_name": "X-Auth-Scope", "baggage_key": "security.scope"},
# {"header_name": "X-Client-IP", "baggage_key": "security.client_ip"},
# {"header_name": "X-Forwarded-For", "baggage_key": "security.forwarded_for"},
# {"header_name": "X-Compliance-Level", "baggage_key": "security.compliance_level"}
# ]'
# Propagate baggage to external services
# Options: true, false (default: false)
#
# ⚠️ SECURITY WARNING: DISABLED BY DEFAULT FOR GOOD REASON ⚠️
#
# When enabled, baggage is propagated to ALL external HTTP requests made by
# the gateway, including:
# - Upstream MCP servers
# - External APIs and webhooks
# - Third-party services
# - Plugin endpoints
#
# SECURITY IMPLICATIONS:
# 1. DATA LEAKAGE: Baggage may contain sensitive tenant/user identifiers that
# external services should NOT receive. This can leak:
# - Internal tenant IDs and organizational structure
# - User identifiers and session tokens
# - Internal request correlation IDs
# - Feature flags revealing your product roadmap
#
# 2. COMPLIANCE VIOLATIONS: Propagating user data to third parties without
# consent may violate GDPR, CCPA, HIPAA, or other regulations.
#
# 3. ATTACK SURFACE: Malicious external services could harvest baggage data
# to map your internal architecture or user base.
#
# 4. TRUST BOUNDARY: External services are outside your security perimeter.
# Baggage should only cross trust boundaries when explicitly required.
#
# WHEN TO ENABLE:
# - Only enable if you control ALL external services the gateway calls
# - Use allowlists to restrict which external hosts receive baggage
# - Audit external service access to baggage data
# - Document which external services receive what baggage keys
# - Consider using separate baggage keys for internal vs external propagation
#
# ALTERNATIVES TO CONSIDER:
# - Use service-specific headers instead of baggage for external calls
# - Implement a baggage filtering proxy for external requests
# - Use separate observability backends for internal vs external traces
#
# OTEL_BAGGAGE_PROPAGATE_TO_EXTERNAL=false
# Maximum number of baggage items (default: 32)
# Prevents unbounded baggage growth and DoS attacks
# W3C Baggage spec recommends keeping this low for performance
# OTEL_BAGGAGE_MAX_ITEMS=32
# Maximum total baggage size in bytes (default: 8192)
# Total size of all baggage key-value pairs combined
# Prevents header size attacks and network overhead
# W3C Baggage spec recommends 8KB limit for HTTP header compatibility
# OTEL_BAGGAGE_MAX_SIZE_BYTES=8192
# Log baggage items that were rejected (invalid key/value, size limit exceeded)
# Default: false — enable for debugging baggage propagation issues
# OTEL_BAGGAGE_LOG_REJECTED=false
# Log when baggage values are sanitized (control characters stripped)
# Default: false — enable for auditing baggage sanitization
# OTEL_BAGGAGE_LOG_SANITIZATION=false
# Prometheus Metrics Configuration
# Enable Prometheus-compatible metrics endpoint at /metrics/prometheus
# Options: true, false (default)
# When true: Exposes metrics at /metrics/prometheus (requires JWT authentication)
# When false: Returns HTTP 503 on metrics endpoint
# Enable this only when a Prometheus stack is configured to scrape.
# Prometheus scrape config needs: authorization: { type: Bearer, credentials: }
# Generate a scrape token: python -m mcpgateway.utils.create_jwt_token --username prometheus@monitoring --exp 0 --secret $JWT_SECRET_KEY
# ENABLE_METRICS=false
# Comma-separated regex patterns for endpoints to exclude from metrics collection
# Use this to avoid high-cardinality issues with dynamic paths or reduce overhead
# Examples:
# - Exclude SSE endpoints: /servers/.*/sse
# - Exclude static files: /static/.*
# - Exclude health checks: .*health.*
# - Multiple patterns: /servers/.*/sse,/static/.*,.*health.*
# Default: "" (no exclusions)
# METRICS_EXCLUDED_HANDLERS=
# Prometheus metrics namespace (prefix for all metric names)
# Used to group metrics by application or organization
# Example: mycompany_gateway_http_requests_total
# Default: "default"
# METRICS_NAMESPACE=default
# Prometheus metrics subsystem (secondary prefix for metric names)
# Used for further categorization within namespace
# Example: mycompany_api_http_requests_total (if subsystem=api)
# Default: "" (no subsystem)
# METRICS_SUBSYSTEM=
# Custom static labels for app_info gauge metric
# Format: comma-separated "key=value" pairs (low-cardinality values only)
# WARNING: Never use high-cardinality values (user IDs, request IDs, timestamps)
# Examples:
# - Single label: environment=production
# - Multiple labels: environment=production,region=us-east-1,team=platform
# - K8s example: cluster=prod-us-east,namespace=mcp-gateway
# Default: "" (no custom labels)
# METRICS_CUSTOM_LABELS=
# -----------------------------------------------------------------------------
# Plugin Framework Settings
# -----------------------------------------------------------------------------
# The plugin framework has its own configuration via pydantic-settings with the
# PLUGINS_ env var prefix. These settings allow the plugin framework to operate
# independently of the gateway configuration (mcpgateway.config).
#
# When the plugin framework is used standalone (e.g., via the mcpplugins CLI or
# as a library), only these PLUGINS_-prefixed variables are needed. When running
# inside the gateway, both the gateway settings (above) AND these framework
# settings are in effect.
#
# The plugin framework settings share some env var names with the gateway
# (e.g. PLUGINS_ENABLED, PLUGINS_CLI_MARKUP_MODE). Other settings mirror
# gateway-level HTTPX_*/SKIP_SSL_VERIFY but are scoped to plugin requests:
# HTTPX_CONNECT_TIMEOUT → PLUGINS_HTTPX_CONNECT_TIMEOUT
# HTTPX_READ_TIMEOUT → PLUGINS_HTTPX_READ_TIMEOUT
# SKIP_SSL_VERIFY → PLUGINS_SKIP_SSL_VERIFY
# Plugin Framework Configuration
# Enable the plugin system for extending gateway functionality
# Options: true, false (default)
# When true: Loads and executes plugins from PLUGINS_CONFIG_FILE
# PLUGINS_ENABLED=false
# Allow plugin HTTP_AUTH_CHECK_PERMISSION grants to override built-in RBAC decisions.
# Disabled by default: plugin grants are audit-only unless this is explicitly enabled.
# PLUGINS_CAN_OVERRIDE_RBAC=false
# DANGEROUS: Allow pre-request plugin hooks to override auth-sensitive headers
# (authorization, cookie, x-api-key, proxy-authorization) the client already sent.
# Only enable when all loaded plugins are fully trusted (e.g. WXO auth token exchange).
# Requires server restart to take effect.
# PLUGINS_CAN_OVERRIDE_AUTH_HEADERS=false
# Path to the plugin configuration file
# Contains plugin definitions, hooks, and settings
# Default: plugins/config.yaml
# PLUGINS_CONFIG_FILE=plugins/config.yaml
# Plugin execution timeout in seconds
# PLUGINS_PLUGIN_TIMEOUT=30
# Plugin framework log level
# PLUGINS_LOG_LEVEL=INFO
# Skip SSL/TLS certificate verification for plugin HTTP requests
# WARNING: Only use in development or with self-signed certificates
# PLUGINS_SKIP_SSL_VERIFY=false
# HTTP client pool settings for plugin framework
# These mirror the gateway HTTPX_* settings but are scoped to plugin requests
# PLUGINS_HTTPX_MAX_CONNECTIONS=200
# PLUGINS_HTTPX_MAX_KEEPALIVE_CONNECTIONS=100
# PLUGINS_HTTPX_KEEPALIVE_EXPIRY=30.0
# PLUGINS_HTTPX_CONNECT_TIMEOUT=5.0
# PLUGINS_HTTPX_READ_TIMEOUT=120.0
# PLUGINS_HTTPX_WRITE_TIMEOUT=30.0
# PLUGINS_HTTPX_POOL_TIMEOUT=10.0
# Optional defaults for mTLS when connecting to external MCP plugins (STREAMABLEHTTP transport)
# Provide file paths inside the container. Plugin-specific TLS blocks override these defaults.
# PLUGINS_CLIENT_MTLS_CA_BUNDLE=/app/certs/plugins/ca.crt
# PLUGINS_CLIENT_MTLS_CERTFILE=/app/certs/plugins/gateway-client.pem
# PLUGINS_CLIENT_MTLS_KEYFILE=/app/certs/plugins/gateway-client.key
# PLUGINS_CLIENT_MTLS_KEYFILE_PASSWORD=
# PLUGINS_CLIENT_MTLS_VERIFY=true
# PLUGINS_CLIENT_MTLS_CHECK_HOSTNAME=true
# Optional defaults for plugin server TLS when exposing plugins over HTTP
# PLUGINS_SERVER_SSL_ENABLED=false
# PLUGINS_SERVER_SSL_KEYFILE=/app/certs/plugins/server.key
# PLUGINS_SERVER_SSL_CERTFILE=/app/certs/plugins/server.pem
# PLUGINS_SERVER_SSL_CA_CERTS=/app/certs/plugins/ca.crt
# PLUGINS_SERVER_SSL_CERT_REQS=2
# PLUGINS_SERVER_SSL_KEYFILE_PASSWORD=
# Plugin MCP server bind settings
# PLUGINS_SERVER_HOST=0.0.0.0
# PLUGINS_SERVER_PORT=9000
# PLUGINS_SERVER_UDS=/tmp/mcpgateway-plugins.sock
# Plugin server runtime (external MCP server)
# PLUGINS_TRANSPORT=stdio
# PLUGINS_CONFIG_PATH=./resources/plugins/config.yaml
# Optional defaults for mTLS when connecting to external plugins over gRPC
# PLUGINS_GRPC_CLIENT_MTLS_CA_BUNDLE=/app/certs/plugins/grpc-ca.crt
# PLUGINS_GRPC_CLIENT_MTLS_CERTFILE=/app/certs/plugins/grpc-client.pem
# PLUGINS_GRPC_CLIENT_MTLS_KEYFILE=/app/certs/plugins/grpc-client.key
# PLUGINS_GRPC_CLIENT_MTLS_KEYFILE_PASSWORD=
# PLUGINS_GRPC_CLIENT_MTLS_VERIFY=true
# Optional defaults for plugin gRPC server TLS
# PLUGINS_GRPC_SERVER_SSL_ENABLED=false
# PLUGINS_GRPC_SERVER_SSL_KEYFILE=/app/certs/plugins/grpc-server.key
# PLUGINS_GRPC_SERVER_SSL_CERTFILE=/app/certs/plugins/grpc-server.pem
# PLUGINS_GRPC_SERVER_SSL_CA_CERTS=/app/certs/plugins/grpc-ca.crt
# PLUGINS_GRPC_SERVER_SSL_CLIENT_AUTH=none
# PLUGINS_GRPC_SERVER_SSL_KEYFILE_PASSWORD=
# Plugin gRPC server bind settings
# PLUGINS_GRPC_SERVER_HOST=0.0.0.0
# PLUGINS_GRPC_SERVER_PORT=50051
# PLUGINS_GRPC_SERVER_UDS=/tmp/mcpgateway-plugins-grpc.sock
# Unix domain socket transport for plugin communication
# PLUGINS_UNIX_SOCKET_PATH=/tmp/mcpgateway-plugins-unix.sock
# Enable auto-completion for plugins CLI
# PLUGINS_CLI_COMPLETION=false
# Set markup mode for plugins CLI
# Valid options:
# rich: use rich markup
# markdown: allow markdown in help strings
# disabled: disable markup
# If unset (commented out), uses "rich" if rich is detected, otherwise disables it.
# Project defaults block sets PLUGINS_CLI_MARKUP_MODE=rich
PLUGINS_CLI_MARKUP_MODE=rich
# =============================================================================
# Well-Known URI Configuration
# =============================================================================
# Enable well-known URI endpoints (/.well-known/*)
# WELL_KNOWN_ENABLED=true
# robots.txt content - Default blocks all crawlers (private API)
# Use multiline with proper escaping or keep on one line
# WELL_KNOWN_ROBOTS_TXT="User-agent: *\nDisallow: /\n\n# ContextForge is a private API gateway\n# Public crawling is disabled by default"
# security.txt content - Define your security contact information
# Format: RFC 9116 (https://www.rfc-editor.org/rfc/rfc9116.html)
# Leave empty to disable security.txt
# Example:
# WELL_KNOWN_SECURITY_TXT="Contact: mailto:security@example.com\nExpires: 2025-12-31T23:59:59Z\nPreferred-Languages: en\nCanonical: https://example.com/.well-known/security.txt"
# WELL_KNOWN_SECURITY_TXT=""
# Enable security.txt endpoint (auto-enabled when content is provided)
# WELL_KNOWN_SECURITY_TXT_ENABLED=false
# Additional custom well-known files (JSON format)
# Example: {"ai.txt": "AI Usage: This service uses AI for tool orchestration...", "dnt-policy.txt": "We respect DNT headers..."}
# WELL_KNOWN_CUSTOM_FILES="{}"
# Cache control for well-known files (seconds) - 3600 = 1 hour
# WELL_KNOWN_CACHE_MAX_AGE=3600
# =============================================================================
# Well-Known URI Examples
# =============================================================================
# Example 1: Basic security.txt
# WELL_KNOWN_SECURITY_TXT="Contact: mailto:security@mycompany.com\nContact: https://mycompany.com/security\nEncryption: https://mycompany.com/pgp-key.txt\nPreferred-Languages: en, es\nCanonical: https://api.mycompany.com/.well-known/security.txt"
# Example 2: Custom AI policy
# WELL_KNOWN_CUSTOM_FILES={"ai.txt": "# AI Usage Policy\n\nThis ContextForge uses AI for:\n- Tool orchestration\n- Response generation\n- Error handling\n\nWe do not use AI for:\n- User data analysis\n- Behavioral tracking\n- Decision making without human oversight"}
# Example 3: Allow specific crawlers
# WELL_KNOWN_ROBOTS_TXT="User-agent: internal-monitor\nAllow: /health\nAllow: /metrics\n\nUser-agent: *\nDisallow: /"
# Example 4: Multiple custom files
# WELL_KNOWN_CUSTOM_FILES={"ai.txt": "# AI Usage Policy\n\nThis ContextForge uses AI for:\n- Tool orchestration\n- Response generation\n- Error handling\n\nWe do not use AI for:\n- User data analysis\n- Behavioral tracking\n- Decision making without human oversight", "dnt-policy.txt": "# Do Not Track Policy\n\nWe respect the DNT header.\nNo tracking cookies are used.\nOnly essential session data is stored.", "change-password": "https://mycompany.com/account/password"}
# =============================================================================
# Startup Tuning
# =============================================================================
# Batch size for gateway/tool slug refresh at startup
# SLUG_REFRESH_BATCH_SIZE=1000
# =============================================================================
# Validation Settings
# =============================================================================
# These settings control input validation and security patterns
# Most users won't need to change these defaults
# HTML/JavaScript injection patterns (regex)
# Used to detect potentially dangerous HTML/JS content
# VALIDATION_DANGEROUS_HTML_PATTERN - Pattern to detect dangerous HTML tags
# VALIDATION_DANGEROUS_JS_PATTERN - Pattern to detect JavaScript injection attempts
#
# Default dangerous HTML pattern (regex)
# VALIDATION_DANGEROUS_HTML_PATTERN="<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\\b|*(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)>"
# Default dangerous JS pattern (regex)
# VALIDATION_DANGEROUS_JS_PATTERN="(?i)(?:^|\\s|[\\\"'`<>=])(javascript:|vbscript:|data:\\s*[^,]*[;\\s]*(javascript|vbscript)|\\bon[a-z]+\\s*=|<\\s*script\\b)"
# Allowed URL schemes for external requests
# Controls which URL schemes are permitted for gateway operations
# Default: ["http://", "https://", "ws://", "wss://"]
# VALIDATION_ALLOWED_URL_SCHEMES=["http://", "https://", "ws://", "wss://"]
# Character validation patterns (regex)
# Used to validate various input fields
# VALIDATION_NAME_PATTERN - Pattern for validating names (allows spaces)
# VALIDATION_IDENTIFIER_PATTERN - Pattern for validating IDs (no spaces)
# VALIDATION_SAFE_URI_PATTERN - Pattern for safe URI characters
# VALIDATION_UNSAFE_URI_PATTERN - Pattern to detect unsafe URI characters
# VALIDATION_TOOL_NAME_PATTERN - MCP tool naming pattern
# VALIDATION_TOOL_METHOD_PATTERN - MCP tool method naming pattern
#
# Name pattern (allows spaces)
# VALIDATION_NAME_PATTERN="^[a-zA-Z0-9_.\\-\\s]+$"
# Identifier pattern (no spaces)
# VALIDATION_IDENTIFIER_PATTERN="^[a-zA-Z0-9_\\-\\.]+$"
# Safe URI pattern
# VALIDATION_SAFE_URI_PATTERN="^[a-zA-Z0-9_\\-.:/?=&%{}]+$"
# Unsafe URI pattern
# VALIDATION_UNSAFE_URI_PATTERN="[<>\"'\\]"
# MCP tool naming pattern per SEP-986
# VALIDATION_TOOL_NAME_PATTERN="^[a-zA-Z0-9_][a-zA-Z0-9._/-]*$"
# MCP tool method naming pattern
# VALIDATION_TOOL_METHOD_PATTERN="^[a-zA-Z][a-zA-Z0-9_\\./-]*$"
# Size limits for various inputs (in characters or bytes)
# VALIDATION_MAX_NAME_LENGTH=255
# VALIDATION_MAX_DESCRIPTION_LENGTH=8192
# VALIDATION_MAX_TEMPLATE_LENGTH=65536
# VALIDATION_MAX_CONTENT_LENGTH=1048576
# VALIDATION_MAX_JSON_DEPTH=10
# VALIDATION_MAX_URL_LENGTH=2048
# VALIDATION_MAX_RPC_PARAM_SIZE=262144
# VALIDATION_MAX_METHOD_LENGTH=128
# Rate limiting for validation operations
# Maximum requests per minute for validation endpoints
# VALIDATION_MAX_REQUESTS_PER_MINUTE=60
# Allowed MIME types for validation (JSON array)
# Controls which content types pass validation checks
# VALIDATION_ALLOWED_MIME_TYPES=["text/plain", "text/html", "text/css", "text/markdown", "text/javascript", "application/json", "application/xml", "application/pdf", "image/png", "image/jpeg", "image/gif", "image/svg+xml", "application/octet-stream"]
# =============================================================================
# Non-Settings Environment Variables
# =============================================================================
# --- Runtime / launcher envs --------------------------------------------------
# Disable access logging for performance
# Options: true, false (default)
# When true: Disables both gunicorn and uvicorn access logs
# Access logs create massive I/O overhead under high concurrency
# Default: true (disabled for performance)
# Set to false to enable access logging for debugging
# DISABLE_ACCESS_LOG=true
# Force start even if another instance is running
# Options: true, false (default)
# Bypasses the lock file check at /tmp/mcpgateway-gunicorn.lock
# FORCE_START=false
# --- Gunicorn Production Server Configuration --------------------------------
# These settings are used by run-gunicorn.sh for production deployments.
# They control the Gunicorn WSGI server behavior.
# Number of worker processes
# Options: "auto" (default, 2*CPU+1 capped at 16), or any positive integer
# Recommendation: 2-4 x $(NUM_CORES) for CPU-bound, more for I/O-bound workloads
# GUNICORN_WORKERS=auto
# Worker timeout in seconds
# Workers that don't respond within this time are killed and restarted
# Increase for long-running requests (e.g., LLM streaming, large file uploads)
# Default: 600 (10 minutes)
# GUNICORN_TIMEOUT=600
# Maximum requests per worker before automatic restart
# Helps prevent memory leaks by periodically recycling workers
# Default: 100000
# GUNICORN_MAX_REQUESTS=100000
# Random jitter added to max requests (prevents thundering herd on restart)
# Default: 100
# GUNICORN_MAX_REQUESTS_JITTER=100
# Preload application before forking workers
# Options: true (default), false
# true: Saves memory (shared code), runs migrations once before forking
# false: Each worker loads app independently (more memory, better isolation)
# GUNICORN_PRELOAD_APP=true
# Developer mode with hot reload
# Options: true, false (default)
# Enables --reload flag and reduces workers for easier debugging
# WARNING: Disables preload_app. Not for production!
# GUNICORN_DEV_MODE=false
# --- SSL/TLS Configuration (launcher) ----------------------------------------
# Enable HTTPS for production deployments (run-gunicorn.sh)
# Enable TLS/SSL
# Options: true, false (default)
# SSL=false
# Path to SSL certificate file (PEM format)
# CERT_FILE=certs/cert.pem
# Path to SSL private key file (PEM format)
# KEY_FILE=certs/key.pem
# Passphrase for encrypted private key (optional)
# If your key is passphrase-protected, set this value
# The key will be decrypted by the SSL key manager before Gunicorn starts
# KEY_FILE_PASSWORD=
# CERT_PASSPHRASE=
# --- Direct env reads (application code) -------------------------------------
# Content type for outgoing HTTP requests to upstream services
# Options: application/json (default), application/x-www-form-urlencoded, multipart/form-data
# Direct env read (mcpgateway/config.py)
# FORGE_CONTENT_TYPE=application/json
# SQLAlchemy echo commands - debug only, used to identify N+1 issues, etc.
# Direct env read (mcpgateway/db.py)
# SQLALCHEMY_ECHO=0
# Copy resource attributes to span attributes (for Arize compatibility)
# Some observability backends like Arize require certain attributes as span attributes
# rather than resource attributes. Enable this to copy arize.project.name and model_id.
# Read via mcpgateway/config.py
# OTEL_COPY_RESOURCE_ATTRS_TO_SPANS=false
# Deployment environment label for observability resource attributes
# Read via mcpgateway/config.py
# DEPLOYMENT_ENV=development
# Jaeger exporter auth (only used when OTEL_TRACES_EXPORTER=jaeger)
# Read via mcpgateway/config.py
# OTEL_EXPORTER_JAEGER_USER=
# OTEL_EXPORTER_JAEGER_PASSWORD=
# Test mode for observability (disables tracing when set to 1)
# Direct env read (mcpgateway/observability.py)
# MCP_TESTING=0
# =============================================================================
# Langfuse LLM Observability Integration
# =============================================================================
# Langfuse provides trace visualization, prompt management, evaluations,
# cost tracking, and LLM analytics. Integrates via OTLP/HTTP.
#
# Quick start: make langfuse-up
# Access: http://localhost:3100
# Combined: make langfuse-monitoring-up (Langfuse + Grafana/Tempo; gateway traces still go to Langfuse by default)
#
# Usage: docker compose -f docker-compose.yml -f docker-compose.with-langfuse.yml up -d
# Langfuse OTLP endpoint override for the gateway.
# Defaults to the local compose service when unset in the overlay.
# LANGFUSE_OTEL_ENDPOINT=http://localhost:3100/api/public/otel/v1/traces
# Langfuse API keys used by ContextForge to connect to Langfuse via OTLP.
# For the local self-hosted compose overlay, unset values fall back to the
# compose-local dev defaults `pk-lf-contextforge` / `sk-lf-contextforge`.
# Set these when you want a different local project or when connecting to an
# external Langfuse instance.
# LANGFUSE_PUBLIC_KEY=pk-lf-
# LANGFUSE_SECRET_KEY=sk-lf-
# Optional OTEL auth override: base64("publicKey:secretKey")
# When LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are set, the gateway can
# derive the Authorization header automatically and this override is not needed.
# LANGFUSE_OTEL_AUTH=
# Langfuse UI host port (default: 3100 to avoid Grafana conflict on 3000)
# LANGFUSE_PORT=3100
# Langfuse worker metrics port (localhost only)
# LANGFUSE_WORKER_PORT=3130
# Langfuse UI URL (used for NEXTAUTH_URL and CORS)
# LANGFUSE_URL=http://localhost:3100
# Auto-provisioned admin user override for the local self-hosted Langfuse overlay
# LANGFUSE_INIT_USER_EMAIL=admin@example.com
# LANGFUSE_INIT_USER_PASSWORD=
# Auto-provisioned organization and project
# LANGFUSE_INIT_ORG_ID=contextforge
# LANGFUSE_INIT_ORG_NAME=ContextForge
# LANGFUSE_INIT_PROJECT_ID=contextforge-gateway
# LANGFUSE_INIT_PROJECT_NAME=ContextForge Gateway
# Optional overrides for the local self-hosted Langfuse overlay only.
# ContextForge does not read these. They are used only by docker-compose.with-langfuse.yml.
# If unset, the overlay uses local-only defaults defined in that compose file.
# LANGFUSE_POSTGRES_PASSWORD=
# LANGFUSE_CLICKHOUSE_USER=clickhouse
# LANGFUSE_CLICKHOUSE_PASSWORD=
# LANGFUSE_MINIO_USER=minio
# LANGFUSE_MINIO_PASSWORD=
# LANGFUSE_REDIS_AUTH=
# LANGFUSE_NEXTAUTH_SECRET=
# LANGFUSE_SALT=
# LANGFUSE_ENCRYPTION_KEY=
# Langfuse optional features
# LANGFUSE_TELEMETRY_ENABLED=true
# LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=false
# Monitoring stack host port overrides for `make monitoring-up` and
# `make langfuse-monitoring-up`. These are compose-only and are not read by
# mcpgateway/config.py.
# NGINX_PORT=8080
# GRAFANA_PORT=3000
# LOKI_PORT=3101
# PROMETHEUS_PORT=9090
# TEMPO_PORT=3200
# TEMPO_OTLP_GRPC_PORT=4317
# TEMPO_OTLP_HTTP_PORT=4318
# TEMPO_IMAGE_TAG=2.10.0
# PGADMIN_PORT=5050
# REDIS_COMMANDER_PORT=8081
# POSTGRES_EXPORTER_PORT=9187
# REDIS_EXPORTER_PORT=9121
# PGBOUNCER_EXPORTER_PORT=9127
# NGINX_EXPORTER_PORT=9113
# CADVISOR_PORT=8085
# OTEL trace controls for Langfuse and other OTLP backends
# Langfuse-specific attributes auto-enable when LANGFUSE_OTEL_ENDPOINT points to
# Langfuse. Set these explicitly only when you want to override that behavior.
# OTEL_EMIT_LANGFUSE_ATTRIBUTES=
# OTEL_CAPTURE_IDENTITY_ATTRIBUTES=
#
# Payload capture is allowlist-based. By default the gateway does not capture
# observation input or output payloads unless the relevant span names are listed.
# The local `docker-compose.with-langfuse.yml` overlay sets a dev-friendly input
# allowlist for `tool.invoke,prompt.render,llm.proxy,a2a.invoke`.
# OTEL_REDACT_FIELDS=password,secret,token,api_key,authorization,credential,auth_value,access_token,refresh_token,auth_token,client_secret,cookie,set-cookie,private_key
# OTEL_MAX_TRACE_PAYLOAD_SIZE=32768
# OTEL_CAPTURE_INPUT_SPANS=tool.invoke,prompt.render
# OTEL_CAPTURE_OUTPUT_SPANS=
# --- Auxiliary tools and CLIs (non-gateway runtime) --------------------------
# These are used by helper tools, CLIs, and SDK wrappers (not the main gateway server).
# Gateway CLI defaults (mcpgateway/cli.py)
# MCG_HOST=127.0.0.1
# MCG_PORT=4444
# Export/import CLI auth (mcpgateway/cli_export_import.py)
# MCPGATEWAY_BEARER_TOKEN=
# MCP wrapper for tool calls (mcpgateway/wrapper.py)
# MCP_SERVER_URL=
# MCP_AUTH=
# MCP_TOOL_CALL_TIMEOUT=60
# MCP_WRAPPER_LOG_LEVEL=INFO
# CONCURRENCY=10
# Reverse proxy helper (mcpgateway/reverse_proxy.py)
# REVERSE_PROXY_GATEWAY=
# REVERSE_PROXY_TOKEN=
# REVERSE_PROXY_RECONNECT_DELAY=1
# REVERSE_PROXY_MAX_RETRIES=0
# REVERSE_PROXY_LOG_LEVEL=INFO
# DB readiness helper (mcpgateway/utils/check_schema_at_head.py)
# DB_WAIT_MAX_TRIES=30
# DB_WAIT_INTERVAL=2
# DB_CONNECT_TIMEOUT=2
# Builder / deploy tooling (mcpgateway/tools/builder/*)
# MCP_DEPLOY_DIR=./deploy
# MCP_DEBUG=
# CONTAINER=false
# ToolOps LLM provider envs (mcpgateway/toolops/utils/llm_util.py)
# LLM_PROVIDER=openai
# NOTE: MAX_TOEKNS is misspelled in code; use the exact env name shown below.
# NOTE: *TEMPERATURE envs are currently not read (commented in code) but listed for completeness.
#
# OpenAI
# OPENAI_API_KEY=
# OPENAI_BASE_URL=https://api.openai.com
# OPENAI_MODEL=
# OPENAI_TEMPERATURE=0.7
# OPENAI_MAX_RETRIES=2
# OPENAI_MAX_TOEKNS=600
#
# Azure OpenAI
# AZURE_OPENAI_API_KEY=
# AZURE_OPENAI_ENDPOINT=
# AZURE_OPENAI_API_VERSION=
# AZURE_OPENAI_DEPLOYMENT=
# AZURE_OPENAI_MODEL=
# AZURE_OPENAI_TEMPERATURE=0.7
# AZURE_OPENAI_MAX_RETRIES=2
# AZURE_OPENAI_MAX_TOEKNS=600
#
# Anthropic
# ANTHROPIC_API_KEY=
# ANTHROPIC_MODEL=
# ANTHROPIC_TEMPERATURE=0.7
# ANTHROPIC_MAX_RETRIES=2
# ANTHROPIC_MAX_TOKENS=4096
#
# AWS Bedrock
# AWS_BEDROCK_MODEL_ID=
# AWS_BEDROCK_REGION=
# AWS_BEDROCK_TEMPERATURE=0.7
# AWS_BEDROCK_MAX_TOKENS=4096
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# AWS_SESSION_TOKEN=
#
# Ollama
# OLLAMA_BASE_URL=http://localhost:11434
# OLLAMA_MODEL=
# OLLAMA_TEMPERATURE=0.7
#
# Watsonx
# WATSONX_APIKEY=
# WATSONX_URL=
# WATSONX_PROJECT_ID=
# WATSONX_MODEL_ID=
# WATSONX_MAX_NEW_TOKENS=1000
# WATSONX_DECODING_METHOD=greedy
# =============================================================================
# Development Configuration
# =============================================================================
# Enable development mode (relaxed security, verbose logging)
# Options: true, false (default)
# WARNING: Never use in production!
# DEV_MODE=false
# Enable auto-reload on code changes (for development)
# Options: true, false (default)
# Requires: Running with uvicorn directly (not gunicorn)
# RELOAD=false
# Enable Jinja2 template auto-reload (for development)
# Options: true, false (default)
# Set to true for development to auto-detect template changes
# Performance impact: Disabling reduces CPU usage for admin UI under load
# Note: `make dev` automatically sets this to true
# TEMPLATES_AUTO_RELOAD=false
# Enable debug mode (verbose error messages, stack traces)
# Options: true, false (default)
# WARNING: May expose sensitive information!
# DEBUG=false
# Expose detailed error information in API error responses
# Options: true, false (default)
# WARNING: May leak internal stack traces or config details — never enable in production
# EXPOSE_ERROR_DETAILS=false
# Header Passthrough — quick ref (full docs → Header Passthrough section ~line 1840)
# ENABLE_HEADER_PASSTHROUGH=false
# ENABLE_OVERWRITE_BASE_HEADERS=false
# DEFAULT_PASSTHROUGH_HEADERS=["X-Tenant-Id", "X-Trace-Id"]
# Sensitive Header Passthrough (Phase 1 - Issue #3621)
# Allow Authorization, X-API-Key, and other sensitive headers in passthrough_headers whitelist.
# REQUIRES: ENABLE_HEADER_PASSTHROUGH=true (will fail startup if base feature is disabled)
# Default: false for security.
# When enabled, whitelisted sensitive headers bypass router-level filtering.
# ENABLE_SENSITIVE_HEADER_PASSTHROUGH=false
# Authorization Header Conflict Resolution:
# When gateway uses auth, use X-Upstream-Authorization header to pass
# authorization to upstream servers (automatically renamed to Authorization)
# GlobalConfig In-Memory Cache TTL (Issue #1715)
# Caches GlobalConfig (passthrough headers settings) in memory to reduce DB queries.
# Under load (1000+ concurrent users), this eliminates 42,000+ redundant DB queries.
# Trade-off: Config changes take up to TTL seconds to propagate (use admin API to force refresh).
# Default: 60 seconds. Range: 5-3600 seconds.
# Project defaults block sets GLOBAL_CONFIG_CACHE_TTL=300 for local dev
# GLOBAL_CONFIG_CACHE_TTL=60
# A2A Stats In-Memory Cache TTL
# Caches A2A agent counts (total, active) to avoid redundant COUNT queries on /metrics calls.
# Trade-off: Agent count changes take up to TTL seconds to propagate (use admin API to force refresh).
# Default: 30 seconds. Range: 5-3600 seconds.
# A2A_STATS_CACHE_TTL=30
# Project defaults block sets MCPGATEWAY_UI_TOOL_TEST_TIMEOUT=120000 for local dev
# MCPGATEWAY_UI_TOOL_TEST_TIMEOUT=60000
# =============================================================================
# Security Validation Settings
# =============================================================================
# Minimum length for secret keys (JWT, encryption)
# MIN_SECRET_LENGTH=32
# Minimum length for passwords
# MIN_PASSWORD_LENGTH=12
# ------------------------------------------------------------------------------
# SECURITY-CRITICAL: Values below MUST be changed for production deployments!
# Defaults are for LOCAL DEVELOPMENT ONLY. Using defaults in production
# exposes your deployment to full compromise.
#
# Generate secure keys using: python -m mcpgateway.scripts.init_secrets
# Manual fallback: python3 -c 'import secrets; print(secrets.token_urlsafe(32))'
# ------------------------------------------------------------------------------
# REQUIRE_STRONG_SECRETS:
# Defaults to TRUE if ENVIRONMENT=production.
# If set to true, the gateway will FAIL TO START if critical secrets are weak or missing.
# Set to false only for local testing. NOT RECOMMENDED for production!
# REQUIRE_STRONG_SECRETS=true
# =============================================================================
# ToolOps Configuration
# =============================================================================
# Enable the ToolOps functionality (true/false)
# When disabled, ToolOps features will be completely hidden from UI and APIs
# Default: false (must be explicitly enabled)
# TOOLOPS_ENABLED=false
# =============================================================================
# LLM Chat MCP Client Configuration
# =============================================================================
# Enable the LLM Chat functionality (true/false)
# When disabled, LLM chat features will be completely hidden from UI and APIs
# Default: true
# LLMCHAT_ENABLED=true
# Enable stdio transport for LLM Chat MCP server config.
# Disabled by default; enable only when trusted stdio subprocess execution is required.
# MCPGATEWAY_STDIO_TRANSPORT_ENABLED=false
# LLM Provider Configuration
# All LLM providers are now configured via Admin UI -> Settings -> LLM Settings.
# Add providers (OpenAI, Azure OpenAI, Anthropic, AWS Bedrock, Ollama, watsonx)
# and their models through the Admin UI. API keys and credentials are securely
# stored in the database.
# Redis Configuration for chat session storage and maintaining history
# CACHE_TYPE should be set to "redis" and REDIS_URL configured appropriately as mentioned in the caching section.
# Seconds for active_session key TTL
# LLMCHAT_SESSION_TTL=300
# Seconds for lock expiry
# LLMCHAT_SESSION_LOCK_TTL=30
# How many times to poll while waiting
# LLMCHAT_SESSION_LOCK_RETRIES=10
# Seconds between polls
# LLMCHAT_SESSION_LOCK_WAIT=0.2
# Seconds for chat history expiry
# LLMCHAT_CHAT_HISTORY_TTL=3600
# Maximum message history to store per user
# LLMCHAT_CHAT_HISTORY_MAX_MESSAGES=50
# =============================================================================
# LLM Settings (Internal API)
# =============================================================================
# These settings control the internal LLM API that allows the gateway to
# act as a unified LLM provider. Configure external providers in the Admin UI.
# API prefix for internal LLM endpoints (OpenAI-compatible)
# Default: /v1
# LLM_API_PREFIX=/v1
# Request timeout in seconds for LLM API calls
# Default: 120
# LLM_REQUEST_TIMEOUT=120
# Enable streaming responses for LLM Chat
# Default: true
# LLM_STREAMING_ENABLED=true
# Provider health check interval in seconds
# Default: 300 (5 minutes)
# LLM_HEALTH_CHECK_INTERVAL=300
# =============================================================================
# Pagination Configuration
# =============================================================================
# Default number of items per page for paginated endpoints
# Applies to: tools, resources, prompts, servers, gateways, users, teams, tokens, etc.
# Default: 50, Min: 1, Max: 1000
# PAGINATION_DEFAULT_PAGE_SIZE=50
# Maximum allowed items per page (prevents abuse)
# Default: 500, Min: 1, Max: 10000
# PAGINATION_MAX_PAGE_SIZE=500
# Minimum items per page
# Default: 1
# PAGINATION_MIN_PAGE_SIZE=1
# Threshold for switching from offset to cursor-based pagination
# When result set exceeds this count, use cursor-based pagination for performance
# Default: 10000
# PAGINATION_CURSOR_THRESHOLD=10000
# Enable cursor-based pagination globally
# Options: true (default), false
# When false, only offset-based pagination is used
# PAGINATION_CURSOR_ENABLED=true
# Default sort field for paginated queries
# Default: created_at
# PAGINATION_DEFAULT_SORT_FIELD=created_at
# Default sort order for paginated queries
# Options: asc, desc (default)
# PAGINATION_DEFAULT_SORT_ORDER=desc
# Maximum offset allowed for offset-based pagination (prevents abuse)
# Default: 100000 (100K records)
# PAGINATION_MAX_OFFSET=100000
# Cache pagination counts for performance (seconds)
# Set to 0 to disable caching
# Default: 300 (5 minutes)
# PAGINATION_COUNT_CACHE_TTL=300
# Enable pagination links in API responses
# Options: true (default), false
# PAGINATION_INCLUDE_LINKS=true
# Base URL for pagination links (defaults to request URL)
# PAGINATION_BASE_URL=https://api.example.com
# =============================================================================
# gRPC Support Settings (EXPERIMENTAL)
# =============================================================================
# Enable gRPC to MCP translation support (disabled by default)
# Requires: pip install mcp-contextforge-gateway[grpc]
# MCPGATEWAY_GRPC_ENABLED=false
# Enable gRPC server reflection by default for service discovery
# MCPGATEWAY_GRPC_REFLECTION_ENABLED=true
# Maximum gRPC message size in bytes (4MB default)
# MCPGATEWAY_GRPC_MAX_MESSAGE_SIZE=4194304
# Default gRPC call timeout in seconds
# MCPGATEWAY_GRPC_TIMEOUT=30
# Enable TLS for gRPC connections by default
# MCPGATEWAY_GRPC_TLS_ENABLED=false
# =============================================================================
# Header Size Validation
# =============================================================================
# Enable RFC 6585 header size validation (returns HTTP 431 on oversized headers)
# Default: true
# HEADER_SIZE_VALIDATION_ENABLED=true
# Maximum total size of all request headers combined (default: 16KB)
# MAX_HEADER_TOTAL_SIZE_BYTES=16384
# Maximum size of a single header field name+value (default: 8KB)
# MAX_HEADER_FIELD_SIZE_BYTES=8192
# Maximum number of header fields per request (default: 100)
# MAX_HEADER_COUNT=100
# =============================================================================
# Audit Trail Logging
# =============================================================================
# Enable audit trail logging for compliance (CRUD operations on all resources)
# Default: false (disabled for performance - causes a DB write on EVERY API request)
# When enabled, logs all create, read, update, delete operations to the audit_trails table
# WARNING: This can generate millions of rows during load testing!
# Only enable for production compliance requirements (SOC2, HIPAA, etc.)
# AUDIT_TRAIL_ENABLED=false
# =============================================================================
# Permission Audit Logging
# =============================================================================
# Enable permission audit logging for RBAC checks (one DB row per permission check)
# Default: false (disabled for performance under load)
# PERMISSION_AUDIT_ENABLED=false
# =============================================================================
# Security Event Logging
# =============================================================================
# Enable security event logging (authentication attempts, authorization failures, etc.)
# Default: false (disabled for performance - can cause high DB write load)
# When enabled, the AuthContextMiddleware will log authentication events to the database
# This is INDEPENDENT of observability settings
# SECURITY_LOGGING_ENABLED=false
# Security logging level - controls what events are logged to the database
# Options:
# - "all" : Log ALL events including successful auth (WARNING: high DB load!)
# - "failures_only" : Log only authentication/authorization failures (recommended)
# - "high_severity" : Log only high/critical severity events
# Default: failures_only
# SECURITY_LOGGING_LEVEL=failures_only
# Failed auth attempts before high severity alert
# SECURITY_FAILED_AUTH_THRESHOLD=5
# Threat score threshold for alerts (0.0-1.0)
# SECURITY_THREAT_SCORE_ALERT=0.7
# Time window for rate limit checks (minutes)
# SECURITY_RATE_LIMIT_WINDOW_MINUTES=5
# =============================================================================
# Observability Settings
# =============================================================================
# Enable observability tracing and metrics collection
# When enabled, all HTTP requests will be traced with detailed timing, status codes, and context
# OBSERVABILITY_ENABLED=false
# Automatically trace HTTP requests
# OBSERVABILITY_TRACE_HTTP_REQUESTS=true
# Number of days to retain trace data
# OBSERVABILITY_TRACE_RETENTION_DAYS=7
# Maximum number of traces to retain (prevents unbounded growth)
# OBSERVABILITY_MAX_TRACES=100000
# Trace sampling rate (0.0-1.0) - 1.0 means trace everything, 0.1 means trace 10%
# OBSERVABILITY_SAMPLE_RATE=1.0
# Paths to include for tracing (JSON array of regex patterns)
# OBSERVABILITY_INCLUDE_PATHS=["^/rpc/?$","^/sse$","^/message$","^/mcp(?:/|$)","^/servers/[^/]+/mcp/?$","^/servers/[^/]+/sse$","^/servers/[^/]+/message$","^/a2a(?:/|$)"]
# Paths to exclude from tracing (JSON array of regex patterns, applied after include 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
# -----------------------------------------------------------------------------
# Plugin metadata observability sinks
# (G1: PluginResult.metadata → observability_spans)
# -----------------------------------------------------------------------------
# Records a plugin.metrics. DB span for each plugin that returns metadata
# on an invoke_hook() call. Both sinks are on by default; disable to reduce DB
# write amplification if you have many active plugins per request.
# Write a plugin.metrics. internal span per plugin metadata entry.
# Default: true
# PLUGIN_METRICS_DB_SPANS_ENABLED=true
# Also record numeric plugin metadata fields (e.g. total_detections) as
# ObservabilityMetric rows, making them queryable as metrics not just span attrs.
# Default: true
# PLUGIN_METRICS_DB_NUMERIC_ROWS_ENABLED=true
# Max ObservabilityMetric rows written per invoke_hook() call across all plugins.
# Caps DB write amplification when many plugins each report several numeric fields.
# Default: 16
# PLUGIN_METRICS_MAX_NUMERIC_PER_CALL=16
# =============================================================================
# CPEX Control-Execution Telemetry
# =============================================================================
#
# Structured observability for CPEX plugin enforcement decisions on every tool
# invocation. Requires CPEX >= 0.1.2. Silent no-op on older CPEX builds.
# Also requires OBSERVABILITY_ENABLED=true to have an active trace to attach to.
#
# How it works:
# - One cpex.control.summary span per tool call (aggregate counts + outcome)
# - One cpex.control.result span per plugin evaluated (name, status, decision,
# duration, reason, error_code, config key names)
# - Both written to the internal observability_spans DB table and, when OTel
# tracing is active, also exported via the OTel SDK sink.
#
# Master switch — enable to emit cpex.control.* spans.
# DISABLED BY DEFAULT: each traced tool call creates up to 1 summary +
# CPEX_CONTROL_TELEMETRY_MAX_RESULTS result DB spans. Review storage and
# cardinality implications before enabling in production.
# Default: false
# CPEX_CONTROL_TELEMETRY_ENABLED=false
# Independently toggle the DB sink (cpex.control.summary + cpex.control.result rows).
# Default: true
# CPEX_CONTROL_TELEMETRY_DB_ENABLED=true
# Emit flattened cpex.control.results..* attributes on the summary span.
# Off by default. Enable only when downstream tooling requires dynamic key names.
# The fixed-schema cpex.control.result child spans remain the source of truth.
# Default: false
# CPEX_CONTROL_TELEMETRY_FLATTEN_RESULTS=false
# Max per-control result records exported per tool invocation (0–128).
# Controls how many cpex.control.result child spans are written per call.
# Default: 32
# CPEX_CONTROL_TELEMETRY_MAX_RESULTS=32
# Informational attribute-count hint for external OTel-collector configuration
# (e.g. transform/attributes processor limits). Not enforced gateway-side;
# gateway-side enforcement is planned for Phase 5 attribute-policy wiring.
# Default: 256
# CPEX_CONTROL_TELEMETRY_MAX_ATTRIBUTES=256
# Emit cpex.control.result.reason and cpex.control.result.error_code on per-control spans.
# DISABLED BY DEFAULT: these fields may contain PII, tool argument values, or exception
# content. Enable only when the observability sink is secured and a redaction boundary is
# in place.
# Default: false
# CPEX_CONTROL_TELEMETRY_EMIT_REASON=false
# Emit cpex.control.agent.id (authenticated caller email) on the summary span.
# DISABLED BY DEFAULT: high-cardinality PII field with GDPR/data-residency implications.
# Enable only when the observability sink is secured and a redaction boundary is in place.
# Default: false
# CPEX_CONTROL_TELEMETRY_EMIT_AGENT_ID=false
# =============================================================================
# Performance Tracking Thresholds
# =============================================================================
# Enable performance tracking and metrics (internal)
# PERFORMANCE_TRACKING_ENABLED=true
# Alert thresholds (milliseconds)
# PERFORMANCE_THRESHOLD_DATABASE_QUERY_MS=100.0
# PERFORMANCE_THRESHOLD_TOOL_INVOCATION_MS=2000.0
# PERFORMANCE_THRESHOLD_RESOURCE_READ_MS=1000.0
# PERFORMANCE_THRESHOLD_HTTP_REQUEST_MS=500.0
# Alert if performance degrades by this multiplier vs baseline
# PERFORMANCE_DEGRADATION_MULTIPLIER=1.5
# =============================================================================
# Performance Monitoring Settings
# =============================================================================
# Enable performance tracking tab in admin UI (default: false)
# Shows real-time CPU, memory, disk, network, worker, and request metrics
# MCPGATEWAY_PERFORMANCE_TRACKING=false
# Metric collection interval in seconds (default: 10)
# How often to sample system metrics for historical data
# MCPGATEWAY_PERFORMANCE_COLLECTION_INTERVAL=10
# Snapshot retention period in hours (default: 24)
# How long to keep individual metric snapshots
# MCPGATEWAY_PERFORMANCE_RETENTION_HOURS=24
# Aggregate retention period in days (default: 90)
# How long to keep hourly/daily aggregated metrics
# MCPGATEWAY_PERFORMANCE_RETENTION_DAYS=90
# Maximum performance snapshots to retain (default: 10000)
# Prevents unbounded database growth
# MCPGATEWAY_PERFORMANCE_MAX_SNAPSHOTS=10000
# Enable distributed mode for multi-container deployments (default: false)
# Uses Redis to aggregate metrics from multiple workers/containers
# MCPGATEWAY_PERFORMANCE_DISTRIBUTED=false
# Enable network connections counting (default: true)
# psutil.net_connections() can be CPU intensive under heavy load
# Disable to skip network connection counting entirely
# MCPGATEWAY_PERFORMANCE_NET_CONNECTIONS_ENABLED=true
# Cache TTL for net_connections in seconds (default: 15)
# Throttles expensive psutil.net_connections() calls
# Higher values reduce CPU usage but report stale connection counts
# MCPGATEWAY_PERFORMANCE_NET_CONNECTIONS_CACHE_TTL=15
# =============================================================================
# Ed25519 Key Support
# =============================================================================
# Enable Ed25519 signing for certificates
# ENABLE_ED25519_SIGNING=false
# Previous Ed25519 private key for signing rotation
# PREV_ED25519_PRIVATE_KEY=""
# Previous Ed25519 public key (derived automatically if private key is set)
# PREV_ED25519_PUBLIC_KEY=
# Current Ed25519 private key for signing
# ED25519_PRIVATE_KEY=
# Current Ed25519 public key (derived automatically if private key is set)
# ED25519_PUBLIC_KEY=
# SSL context cache settings
# SSL_CONTEXT_CACHE_MAX_SIZE=100
# SSL_CONTEXT_CACHE_TTL=
# =============================================================================
# Bootstrap additional system roles
# =============================================================================
# Enable Bootstrap additional system roles feature
# Allows defining a set of roles to be added while bootstraping db
# Options: false (default), true
# MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_ENABLED=false
# Path to the additional roles configuration file
# JSON file contains an array of JSON objects as shown below
# Example:
# [{
# "name": "example_role_1",
# "description": "Read-only access to resources",
# "scope": "team",
# "permissions": ["teams.join", "tools.read", "resources.read"],
# "is_system_role": true
# },
# {
# "name": "example_role_2",
# "description": "Read-only access to prompts",
# "scope": "team",
# "permissions": ["teams.join", "tools.read", "resources.read", "prompts.read"],
# "is_system_role": true
# }]
# MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_FILE=additional_roles_in_db.json
LEGACY_API_ENABLED=true
LEGACY_API_SUNSET_DATE=Sat, 26 Sep 2026 00:00:00 GMT
# =============================================================================
# ContextForge Web UI (docker-compose --profile testing or --profile experimental)
# =============================================================================
# BFF-style frontend for the gateway API - see docker-compose.yml (web_ui, web_ui_redis)
# Image to pull for the web_ui service
# WEB_UI_IMAGE=ghcr.io/contextforge-org/contextforge-web-ui:latest
# Host port the UI listens on (also used as the container port - see docker-compose.yml)
# WEB_UI_PORT=3001
# Bind address inside the container - must be 0.0.0.0 in Docker or other containers cannot connect
# WEB_UI_HOST=0.0.0.0
# Gateway API base URL the UI talks to (internal compose network address:port)
# WEB_UI_CONTEXTFORGE_URL=http://gateway:4444
# Set true once the UI is served over HTTPS (marks the session cookie Secure)
# WEB_UI_COOKIE_SECURE=false
# Session store for the UI's BFF - dedicated Redis, separate from the gateway's cache redis
# WEB_UI_REDIS_URL=redis://web_ui_redis:6379/0