Skip to content

Resilience

Every Discord REST call passes through one shared policy:

bulkhead → circuit breaker → retry → timeout → Discord REST

The pipeline meta-tool executes steps sequentially. Its parent invocation does not reserve a REST slot; each leaf REST request enters this policy independently.

Layer Environment variables Default
Retry MCP_RETRY_ENABLED, MCP_RETRY_MAX_ATTEMPTS, MCP_RETRY_BASE_DELAY_MS, MCP_RETRY_MAX_DELAY_MS, MCP_RETRY_JITTER enabled; 3 extra retries; 200–10000 ms; full jitter
Timeout MCP_TIMEOUT_DEFAULT_MS 30000 ms per attempt
Circuit breaker MCP_CIRCUIT_ENABLED, MCP_CIRCUIT_FAILURE_THRESHOLD, MCP_CIRCUIT_HALF_OPEN_AFTER_MS enabled; 10 failures; 60000 ms
Bulkhead MCP_BULKHEAD_LIMIT 100 in-flight REST calls; no queue

MCP_RETRY_MAX_ATTEMPTS is Cockatiel’s number of retries, not the total number of calls. The default 3 permits one initial call plus three retries. Setting it to 1 still permits one retry; use MCP_RETRY_ENABLED=false to disable this layer.

The classifier retries selected transient failures such as server errors and Discord rate limits. Idempotent HTTP methods can be replayed. Non-idempotent requests are protected from replay when the outcome is ambiguous, because a second POST could duplicate the action.

A known Discord Retry-After is retried inline only when it is at most MCP_RETRY_MAX_DELAY_MS. Longer windows surface without another HTTP attempt. Checkpointed workflows such as guild_blueprint_apply return the delay as retry_after_ms, allowing the caller to stop the MCP process, wait the full window, and resume the same target-bound plan.

Discord’s REST client still tracks route buckets and computes known limit windows. discord-mcp passes { retries: 0 } and rejectOnRateLimit: () => true, making the outer policy the single owner of waiting and retry. It retries a surfaced 429 only when replay is safe and the known delay fits the bounded inline threshold. Known rate-limit windows do not count as upstream failures for the shared circuit breaker.

See Rate limits for the ownership boundary.

buildPolicy() creates one breaker shared by all calls using that policy. It is not partitioned per Discord route. Once the failure threshold opens it, subsequent calls fast-fail with CIRCUIT_OPEN until the half-open probe window.

Tune the threshold only after observing real traffic. A global breaker is a coarse upstream-protection mechanism: one failing route can affect other REST calls that share it.

The bulkhead limits concurrent REST calls across tools and rejects overflow immediately with BULKHEAD_FULL; its queue size is zero.

MCP_BULKHEAD_LIMIT=1 is valid. It does not deadlock the pipeline: steps are sequential, the parent holds no REST slot, and each leaf releases its slot before the next step begins.

  1. Disable retry with MCP_RETRY_ENABLED=false when you need the first failure.
  2. Treat MCP_RETRY_MAX_ATTEMPTS as extra retries when estimating latency.
  3. Keep non-idempotent calls out of manual retry loops unless their result is known.
  4. Expect CIRCUIT_OPEN to affect the shared policy, not a single route.
  5. Back off and reduce concurrency when a call returns BULKHEAD_FULL.