Overview

Run discord-mcp in production with a safe baseline

If you only read one section here, read resilience - it's the one set of knobs that affects every Discord call the server makes. Telemetry is opt-in (OTEL_ENABLED=false by default, zero overhead when off), and audit logging is on by default but tunable per sink.

Start with a safe baseline#

DISCORD_TOKEN=your-discord-bot-token
ALLOWED_GUILDS=111122223333444455

This starts REST-only transport, info logging, retry, circuit breaking, and audit logging, all enabled, with destructive tools staying in dry-run mode until you explicitly opt in. ALLOWED_GUILDS is optional for backward compatibility but strongly recommended whenever your bot can see more than one server.

MCP_DRY_RUN applies only to the 31 confirmation-gated tools (see how it works). Setting it to false removes the dry-run half of that gate for that set, but each call still needs __confirm: true. It does not protect ordinary writes: with the default MCP_WRITE_MODE=allow, ordinary writes execute immediately. Set MCP_WRITE_MODE=preview when every mutation should return a preview instead.

Verify with the online doctor after your client has started the server:

discord-mcp doctor --online

MCP_DRY_RUN is intentionally not part of the schema-validated environment contract, verify that value byte-for-byte before enabling writes.

Resilience#

Every Discord REST call passes through one shared policy: bulkhead -> circuit breaker -> retry -> timeout -> Discord REST. The pipeline meta-tool executes steps sequentially and doesn't reserve a slot for its parent call, each leaf request enters this policy independently.

Layer Default
Retry Enabled, 3 extra retries, 200-10000ms backoff with full jitter
Timeout 30000ms per attempt
Circuit breaker Enabled, 10-failure threshold, 60000ms half-open window
Bulkhead 100 in-flight REST calls, no queue

MCP_RETRY_MAX_ATTEMPTS counts extra retries, not total calls, the default of 3 permits one initial call plus three retries. A surfaced 429 is retried inline only when the known delay fits within MCP_RETRY_MAX_DELAY_MS, longer windows surface without another attempt so checkpointed workflows like guild_blueprint_apply can return retry_after_ms and let you resume later.

The circuit breaker is global, not partitioned per Discord route: one failing route can trip fast-fail (CIRCUIT_OPEN) for calls that share the policy. Tune the failure threshold only after observing real traffic. The bulkhead rejects overflow immediately with BULKHEAD_FULL, its queue size is zero, MCP_BULKHEAD_LIMIT=1 is valid and doesn't deadlock the pipeline.

Telemetry (OpenTelemetry)#

Disabled by default, zero overhead when off. Enable it and point it at an OTLP HTTP collector:

export OTEL_ENABLED=true
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4318
export OTEL_SERVICE_NAME=discord-mcp
discord-mcp serve

Never set OTEL_CONSOLE_EXPORTER=true with stdio transport, the console exporter writes to stdout via console.dir, which corrupts the JSON-RPC stream. Key metrics: mcp.tool.duration_ms, mcp.tool.calls, mcp.tool.errors labelled by tool name and category (never by request ID, to bound cardinality), plus mcp.circuit.transitions, mcp.bulkhead.rejected.count, and mcp.deadletter.count as process-wide health counters. Every tool call also produces a span with redacted arguments attached.

Audit logging#

The audit middleware emits an event for tools whose metadata declares idempotent: false, this is not a complete write audit, some Discord writes are idempotent and are skipped by the current predicate. Treat the trail as an operational aid, not proof every mutation was captured.

Sink Selector Use when
stderr (default) MCP_AUDIT_SINK=stderr Local stdio, or HTTP deployments whose stderr ships to a log aggregator
file MCP_AUDIT_SINK=file + MCP_AUDIT_FILE=/path Long-running daemons where stderr is owned by another process
otlp (stub) MCP_AUDIT_SINK=otlp Reserved for the OTel Logs pipeline, currently falls back to stderr with a visible fallback prefix
none MCP_AUDIT_SINK=none or MCP_AUDIT_ENABLED=false Regulated contexts that ban logging mutating-call bodies

Sensitive fields (token, bearer_token, auth, password, secret, plus 17 tools' explicit content-bearing fields) are redacted before hitting any sink, with a length-aware [REDACTED:${length}ch] marker. MCP_AUDIT_ENABLED=false always overrides the sink setting, there's no way to force audit on when it's disabled at the master switch.

HTTP transport security#

A 2026-08-08 review closed the transport-level v1 gate for the remediated source tree: authenticated request bodies are now bounded (MCP_HTTP_MAX_BODY_BYTES, default 4MiB, 413 on overflow), in-flight requests are capped (MCP_HTTP_MAX_IN_FLIGHT, default 16, 503 with Retry-After on overflow), remote bearer tokens require at least 32 characters, and the locked dependency tree carries no known vulnerabilities at moderate severity or above. No P0 finding was identified, and no P1 remains open.

Public deployments still need a hardened HTTPS reverse proxy in front: TLS termination, redacted access logs, connection/header/body timeouts, and a caller-aware rate limit, the in-process controls cap concurrent work but aren't an IP or identity quota. Keep ALLOWED_GUILDS, MCP_CATEGORIES, and your bot's Discord role narrow, and don't share one deployment between mutually untrusted users.

For the full 208-tool surface these controls apply to, see tools by category. For the middleware chain these settings plug into, see how it works.

Next steps#

Back to the overview or troubleshooting if a setting isn't behaving as expected.

Updated

Was this page helpful?