Skip to content

Rate limits

Discord applies a global bot limit and route-specific buckets. The response headers define each bucket’s capacity and reset timing; a limited request returns HTTP 429 with a retry delay.

tool
→ discord-mcp bulkhead
→ shared circuit breaker
→ discord-mcp retry policy
→ @discordjs/rest route/global scheduler
→ Discord

@discordjs/rest remains the rate-limit authority. It tracks route hashes and global or route-bucket reset timing. discord-mcp configures rejectOnRateLimit: () => true, so a queued or surfaced limit becomes a RateLimitError instead of sleeping invisibly inside the SDK.

The server also sets retries: 0, leaving Cockatiel as the single retry owner. Known delays at or below MCP_RETRY_MAX_DELAY_MS may be honored inline. Longer delays surface without another HTTP attempt; resumable blueprint apply returns retry_after_ms so its caller can close the process, wait, and resume from the authenticated checkpoint.

Layer Runtime scope Saturation behavior
Bulkhead All REST calls using the policy Rejects immediately with BULKHEAD_FULL; queue size is zero.
Circuit breaker One shared breaker per built policy Fast-fails upstream/network failures with CIRCUIT_OPEN; known rate-limit windows do not open it.
Retry Selected transient failures MCP_RETRY_MAX_ATTEMPTS is extra retries; disable with MCP_RETRY_ENABLED=false.
Timeout Each policy attempt Can expire while an inner REST request is waiting; no dedicated timeout code today.

These layers protect local capacity and upstream stability. They do not replace Discord’s bucket model or grant a higher request allowance.

  • Prefer a dedicated bulk endpoint when it expresses the same operation; it reduces call count and preserves per-item result reporting.
  • Keep parallel agent calls bounded. Raising MCP_BULKHEAD_LIMIT increases concurrency, not Discord’s quota.
  • Pipelines are sequential. Their parent holds no REST slot, so a bulkhead limit of 1 does not deadlock the executor.
  • Use MCP_RETRY_ENABLED=false-not a retry count of 1-when testing the first surfaced transient failure.
  • If a webhook fits the product flow, remember that its route has its own Discord bucket semantics; it is not a universal bypass for Discord limits.
  • mcp.bulkhead.rejected.count rising means local concurrency was exhausted.
  • mcp.circuit.transitions{to_state="open"} shows the shared breaker opening.
  • mcp.deadletter.count with its error.type label records policy failures that reached the caller.

The resilience metrics are process-wide and do not carry tool or route labels. Use tool spans and metrics to locate the workload that created the pressure.