Skip to content

Error handling

discord-mcp treats Discord failures as tool results, not broken JSON-RPC exchanges. Check isError, then branch on the stable structuredContent.code.

Every formatted failure guarantees four structured fields:

{
"isError": true,
"content": [{ "type": "text", "text": "Human-readable diagnosis and recovery" }],
"structuredContent": {
"code": "DISCORD_NOT_FOUND",
"retriable": false,
"category": "client",
"recovery_hint": "Verify the ID, resource visibility, and credential scope."
}
}

content[0].text carries the human-readable diagnosis. recovery_hint keeps the next action machine-readable. Error-specific structured fields can include retry_after_ms, missing, have, resource, issues, suggested_tool, or trace_id; do not assume they exist for every code.

All custom errors extend DiscordError:

DiscordError
├─ DiscordClientError
└─ DiscordServerError
CodeCategoryRetry as-is?Meaning
DISCORD_PERMISSION_DENIEDclientnoThe route credential or resource lacks authorization.
DISCORD_RATE_LIMITEDclientyesDiscord returned a rate limit and retry delay.
DISCORD_NOT_FOUNDclientnoThe resource does not exist or is not visible.
VALIDATION_FAILEDclientnoInput failed schema or domain validation.
DISCORD_AUTH_INVALIDclientnoDiscord rejected the credential used by the route.
DISCORD_CLOUDFLARE_BLOCKEDclientyesDiscord temporarily blocked the caller IP.
SCOPE_REJECTEDclientnoThe tool’s category is not enabled.
GUILD_NOT_ALLOWEDclientnoReserved; no runtime allowlist currently constructs it.
DRY_RUN_PREVIEWclientnoA confirm-gated tool was previewed, not executed.
CANCELLEDclientnoThe MCP client cancelled the call.
DISCORD_SERVER_ERRORserveryesDiscord returned an upstream server failure.
CIRCUIT_OPENserveryesThe local circuit breaker is open.
BULKHEAD_FULLserveryesThe local REST concurrency limit rejected the call.
INTERNAL_ERRORserveryesAn unexpected failure reached the formatter.

Raw @discordjs/rest failures are normalized before the generic fallback:

Discord HTTP statusPublic codeRetry as-is?
400VALIDATION_FAILEDno
401DISCORD_AUTH_INVALIDno
403DISCORD_PERMISSION_DENIEDno
404DISCORD_NOT_FOUNDno
429DISCORD_RATE_LIMITEDyes, after retry_after_ms when supplied
5xxDISCORD_SERVER_ERRORyes, with backoff

The formatter exposes the HTTP status and numeric Discord error code when available, but never returns the REST URL or request body because either may contain webhook, interaction, or OAuth credentials.

There is currently no dedicated public timeout error. A Cockatiel timeout that reaches the generic formatter is reported as INTERNAL_ERROR.

Only tools registered with confirmation metadata-currently 29 tools-participate in this gate. Ordinary writes such as messages_send can execute immediately.

{
"isError": true,
"structuredContent": {
"code": "DRY_RUN_PREVIEW",
"retriable": false,
"category": "client",
"tool": "messages_delete",
"preview": {
"channel_id": "111122223333444455",
"message_id": "999900001111222233"
}
}
}

To execute a gated call, the server must run with MCP_DRY_RUN=false and the request must include __confirm: true. See Confirmation.

The REST adapter explicitly maps only two Cockatiel failures:

Cockatiel failurePublic code
BrokenCircuitErrorCIRCUIT_OPEN
BulkheadRejectedErrorBULKHEAD_FULL

The formatter also handles those raw Cockatiel classes defensively. Cancellation uses discord-mcp’s own CancelledError; Cockatiel timeout cancellation is not currently converted into a dedicated domain error.