Trace a tool call from your client to Discord and back
System design#
MCP client --> Transport (stdio / Streamable HTTP / InMemory) --> MCP SDK v2 Server
--> Middleware chain --> Tool piece --> Resilient REST adapter --> Discord REST API
Tool piece -.optional.-> your client's LLM via MCP sampling
Server -.optional.-> Gateway client --> Discord Gateway WebSocket
- Transport owns JSON-RPC framing. The CLI defaults to stdio,
serve --httpprovides stateless Streamable HTTP, tests use an in-memory transport. - MCP SDK v2 Server bridges MCP to the piece registry and hosts the middleware, tool, precondition, and resource stores.
- Middleware chain applies telemetry, default guild resolution, validation, category gate, preconditions, and audit, in that order, before any tool executes.
- Tool piece implements one of the 208 tools: a Discord REST call, an MCP sampling callback for the five intelligence tools, or caller-invoked external asset discovery.
- REST adapter wraps
@discordjs/restin Cockatiel bulkhead, circuit-breaker, retry, and timeout policies, see production configuration for the exact defaults. - Gateway client is optional under
--gateway, it emits notifications for five URI keys but adds noresources/listorresources/readhandlers.
Tool and precondition classes are registered explicitly in server.ts, boot does not scan the filesystem. Adding a tool means adding it there too, which keeps construction consistent at the cost of an explicit registration step.
The confirmation gate#
discord-mcp's confirmation contract is a mechanical two-key guard for selected high-risk operations. A tool that declares the confirm_required precondition, 31 of the 208 tools, doesn't fire unless both hold at the same time:
- The operator launched the server with
MCP_DRY_RUN=false. - The agent passed
__confirm:truein the call's arguments.
If either is missing, the tool throws a DRY_RUN_PREVIEW error carrying the redacted arguments instead of executing. MCP_DRY_RUN defaults to true (fail closed), and only the literal string false flips it, values like 0, no, or a typo such as fasle are all treated as dry-run still active.
__confirm is deliberately not part of any tool's Zod input schema, so a handler never sees it directly and can't accidentally treat it as a business argument. It's still advertised in tools/list: the server injects it into the published JSON Schema of the 31 gated tools after Zod-to-JSON-Schema conversion, so agents can discover it and schema-validating clients can send it, on exactly the tools where it does something.
This is not human-in-the-loop authorization. An autonomous or compromised caller can send __confirm:true whenever the server is armed with MCP_DRY_RUN=false, the server has no way to distinguish a human-approved assertion from one the agent produced itself. Keep dry-run enabled as a hard stop and configure your client or UI to require human approval before consequential calls reach the server at all.
The 31 gated tools are every irreversible delete, plus ban, kick, prune, leave-guild, and the two command bulk-overwrites (which replace a guild's entire command set in one call). Notably not gated: messages_send, messages_edit, channels_create_guild_channel, webhooks_execute, roles_create, members_add_role, components_v2_send, and every other tool that doesn't declare the precondition, they execute on the first call regardless of MCP_DRY_RUN. If you need "this deployment cannot write to Discord at all," that boundary is your bot's own Discord permissions, not this gate. See the full per-category breakdown in moderation and safety tools and commands and interactions tools.
Guild blueprints: the Observe, Approve, Act, Verify lifecycle#
Complete guild builds use a separate, heavier safety lifecycle layered on top of the confirmation gate: a target-bound dry run (guild_blueprint_plan), exact human approval of that plan, a checkpointed and resumable apply (guild_blueprint_apply), and an independent Discord readback (guild_blueprint_evidence) that produces Activity Evidence. Walk through the full lifecycle in get a verified Discord outcome, or watch it happen in the live gaming-server demo.
Sampling: zero-API-key intelligence#
The five intelligence tools call back into your MCP client's own LLM via the requestSampling capability instead of holding an API key server-side. When a client doesn't support sampling, the tool returns raw data plus a host_llm_should_process fallback marker rather than failing silently. See summarize a busy channel for the full pattern.
Related#
Production configuration covers the resilience, telemetry, and audit settings referenced above in full. Troubleshooting covers what it looks like when a piece of this chain misbehaves.
Next steps#
Back to the overview or get your first verified Discord outcome.