Skip to content

Environment variables

discord-mcp’s runtime behavior is fully controlled by environment variables. There are no config files. There is no mutable runtime state. The Zod schema in packages/mcp-core/src/config.ts is the single source of truth — every value below comes from there.

The schema parses process.env at startup. Invalid values cause serve to exit 1 with a per-issue error list; doctor’s env-vars check is the canonical reporter.

  • Type: string, minimum 50 characters
  • Default: (none — required)
  • Description: Discord bot token used to authenticate against the REST API. Accepts both Bot <token> and a bare token. Treated as a credential — never logged, redacted from audit events.
  • Example: DISCORD_TOKEN="MTQwOTU3NjE..." or DISCORD_TOKEN="Bot MTQwOTU3NjE..."
  • Related: This is the only required variable. Everything else has defaults.
  • Type: enum
  • Allowed: fatal, error, warn, info, debug, trace
  • Default: info
  • Description: Pino log level for the embedded logger. debug and trace produce verbose middleware-chain output useful for development; info is the production default.
  • Example: LOG_LEVEL=debug
  • Related: stderr is the only sink (stdio transport reserves stdout for JSON-RPC).

Plan 8 Phase A-B. All OTel variables are no-ops unless OTEL_ENABLED=true.

  • Type: boolean string
  • Default: false
  • Description: Master switch for OpenTelemetry. When false, the SDK is never imported — zero startup cost, zero runtime overhead.
  • Example: OTEL_ENABLED=true
  • Related: enables every other OTEL_* variable.
  • Type: string
  • Default: discord-mcp
  • Description: service.name resource attribute. Identifies this server in your tracing backend (Honeycomb, Tempo, Jaeger).
  • Example: OTEL_SERVICE_NAME=discord-mcp-prod
  • Type: string
  • Default: matches the package version (currently 0.12.0)
  • Description: service.version resource attribute. Useful for distinguishing rolling deploys.
  • Example: OTEL_SERVICE_VERSION=1.2.3-canary
  • Type: URL (validated)
  • Default: (none — optional)
  • Description: OTLP collector endpoint. When unset, the SDK still boots if OTEL_CONSOLE_EXPORTER=true is set; otherwise it stays inert. Honeycomb’s endpoint is https://api.honeycomb.io; local Jaeger/Tempo defaults to http://localhost:4318.
  • Example: OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
  • Related: OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS.
  • Type: enum
  • Allowed: http/protobuf, http/json, grpc
  • Default: http/protobuf
  • Description: Wire format for OTLP export. http/protobuf is the recommended default; http/json is useful for debugging.
  • Example: OTEL_EXPORTER_OTLP_PROTOCOL=http/json
  • Type: string (key=value,key=value)
  • Default: (none — optional)
  • Description: Comma-separated header pairs sent on every OTLP export. Most commonly used for backend auth.
  • Example: OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=abc123,env=prod"
  • Type: enum
  • Allowed: always_on, always_off, traceidratio, parentbased_always_on, parentbased_always_off, parentbased_traceidratio
  • Default: parentbased_always_on
  • Description: Trace sampling strategy. parentbased_* respects upstream sampling decisions (correct for nested agent calls). Use traceidratio with OTEL_TRACES_SAMPLER_ARG for fixed-rate sampling.
  • Example: OTEL_TRACES_SAMPLER=parentbased_traceidratio
  • Related: OTEL_TRACES_SAMPLER_ARG controls the ratio.
  • Type: number, 0–1 (coerced from string)
  • Default: 1
  • Description: Sampling ratio for traceidratio and parentbased_traceidratio. 0.1 = 10%, 1 = always.
  • Example: OTEL_TRACES_SAMPLER_ARG=0.05
  • Related: meaningful only when paired with a ratio sampler.
  • Type: boolean string
  • Default: false
  • Description: Pipe spans to the console (stderr — stdio reserves stdout for JSON-RPC). Useful as a dev aid when no collector is available.
  • Example: OTEL_CONSOLE_EXPORTER=true

Plan 8 Phase C. The retry policy wraps every Discord REST verb via cockatiel.

  • Type: boolean string (default-on; !== 'false' semantics)
  • Default: true
  • Description: Master switch for retry behavior. When false, classified retryable errors (5xx, 429, network resets) propagate to the caller on the first attempt.
  • Example: MCP_RETRY_ENABLED=false
  • Related: every other MCP_RETRY_* variable is meaningful only when this is on.
  • Type: integer, 1–10
  • Default: 3
  • Description: Maximum total attempts (including the first try). 3 means retry up to 2 times after the initial failure.
  • Example: MCP_RETRY_MAX_ATTEMPTS=5
  • Type: integer, 50–5000 (ms)
  • Default: 200
  • Description: Base delay for exponential backoff between attempts. The actual delay is base * 2^attempt, then jittered.
  • Example: MCP_RETRY_BASE_DELAY_MS=500
  • Type: integer, 500–60000 (ms)
  • Default: 10000
  • Description: Cap on the per-attempt delay. Prevents the exponential backoff from running away on long retry chains.
  • Example: MCP_RETRY_MAX_DELAY_MS=30000
  • Type: enum
  • Allowed: none, full, decorrelated
  • Default: full
  • Description: Jitter strategy for the backoff delay. full randomizes uniformly in [0, computed]. decorrelated is the AWS-recommended choice for high-concurrency clients. none disables jitter (deterministic — useful for tests, not production).
  • Example: MCP_RETRY_JITTER=decorrelated
  • Type: integer, 1000–120000 (ms)
  • Default: 30000
  • Description: Per-call timeout for ordinary tool invocations. Includes the full middleware chain plus the Discord REST call.
  • Example: MCP_TIMEOUT_DEFAULT_MS=10000
  • Type: integer, 1000–300000 (ms)
  • Default: 60000
  • Description: Timeout for tools tagged “long” (intelligence sampling, bulk operations). Longer than MCP_TIMEOUT_DEFAULT_MS because LLM-backed sampling can legitimately take 30+ seconds.
  • Example: MCP_TIMEOUT_LONG_MS=120000

Plan 8 Phase D. The circuit breaker and bulkhead sit outside the retry layer in the cockatiel composite policy.

  • Type: boolean string (default-on; !== 'false' semantics)
  • Default: true
  • Description: Master switch for the circuit breaker. When tripped, requests fail fast with CircuitOpenError (HTTP 503-equivalent) for MCP_CIRCUIT_HALF_OPEN_AFTER_MS before probing again.
  • Example: MCP_CIRCUIT_ENABLED=false
  • Type: integer, 3–100
  • Default: 10
  • Description: Number of consecutive failures (after retries are exhausted) that trip the breaker. Lower values are more reactive; higher values tolerate transient blips.
  • Example: MCP_CIRCUIT_FAILURE_THRESHOLD=5
  • Type: integer, 5000–600000 (ms)
  • Default: 60000
  • Description: How long the circuit stays open before attempting a half-open probe. Surfaced to clients via CircuitOpenError.recoveryHint (“wait Nms”).
  • Example: MCP_CIRCUIT_HALF_OPEN_AFTER_MS=30000
  • Type: integer, 1–1000
  • Default: 100
  • Description: Maximum concurrent in-flight Discord REST calls. Excess requests are rejected immediately with BulkheadFullError (no head-of-line blocking — the queue size is hard-coded to 0). The minimum sane value is around 10 to avoid pipeline self-deadlock.
  • Example: MCP_BULKHEAD_LIMIT=50

Plan 8 Phase E. Mutating-only audit log written by auditMiddleware.

  • Type: boolean string (default-on; !== 'false' semantics)
  • Default: true
  • Description: Master switch for audit logging. When false, the audit middleware short-circuits and no events are emitted regardless of MCP_AUDIT_SINK.
  • Example: MCP_AUDIT_ENABLED=false
  • Related: setting MCP_AUDIT_SINK=none is functionally equivalent.
  • Type: enum
  • Allowed: stderr, file, otlp, none
  • Default: stderr
  • Description: Where audit events go. stderr writes one JSONL line per event. file writes to MCP_AUDIT_FILE (default ./discord-mcp-audit.jsonl). otlp is a stub that ships under Plan 8 Phase F. none is an explicit opt-out.
  • Example: MCP_AUDIT_SINK=file
  • Related: MCP_AUDIT_FILE (only meaningful when sink is file).
  • Type: path (string)
  • Default: (none — falls back to ./discord-mcp-audit.jsonl at runtime)
  • Description: Output path for FileAuditSink. Created if missing; appended-to if existing. The file is written in JSONL format — one event per line — for easy jq / grep post-processing.
  • Example: MCP_AUDIT_FILE=/var/log/discord-mcp/audit.jsonl
  • Related: only consulted when MCP_AUDIT_SINK=file.

A minimal production-ish env file:

Terminal window
# Required
DISCORD_TOKEN="Bot MTQwOTU3NjE..."
# Tighter retry, bulkhead at 50
MCP_RETRY_MAX_ATTEMPTS=5
MCP_BULKHEAD_LIMIT=50
# Audit to file
MCP_AUDIT_SINK=file
MCP_AUDIT_FILE=/var/log/discord-mcp/audit.jsonl
# Telemetry → Honeycomb
OTEL_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io
OTEL_EXPORTER_OTLP_HEADERS="x-honeycomb-team=$HONEYCOMB_API_KEY"
OTEL_SERVICE_NAME=discord-mcp-prod
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

A debug/dev override:

Terminal window
DISCORD_TOKEN="Bot MTQwOTU3NjE..."
LOG_LEVEL=debug
# Disable resilience to surface raw errors
MCP_RETRY_ENABLED=false
MCP_CIRCUIT_ENABLED=false
# Spans to console
OTEL_ENABLED=true
OTEL_CONSOLE_EXPORTER=true