Claude Code · Discord · MCP · AI Agent · Windows
Overview
The official Discord channel plugin binds the Discord gateway to an MCP stdio pipe that dies the moment the terminal closes, so the bot is only online while Claude Code is open. This project runsbot.ts as its own long-lived process that holds the gateway connection itself. Each DM from an allowlisted user spawns a fresh headless claude -p loaded with the full global config (MCP servers, plugins, CLAUDE.md), and that process posts the reply back to Discord. A Windows logon-task daemon keeps it alive across terminal close, crashes, and reboots.
It is distributed both as an npm package (bunx claude-discord-daemon / cdd) and as a Claude Code plugin (the /claude-discord-daemon:discord command), under the MIT license.
Core features
- Live named-tool status card: while a run is in flight, a single embed is edited in place and names the exact tool, skill, MCP, agent, or command that is running (for example
MCP matlab·evaluate,Bash,agent code-reviewer,skill xlsx,/commit) instead of a vague “thinking” - Reply overwrites the status card: on success the card is edited in place into the final reply, with a faint Discord
-#footer (elapsed time and per-kind tool counts), so the chat keeps a single message; file, long, or multi-chunk replies go out as fresh messages (edits cannot attach files) and the card collapses to just the footer - Rich output directives, each on its own line in the reply:
[[embed: {json}]]renders a colored embed card; the JSON may span multiple lines[[file: /absolute/path]]attaches a produced file, verified for existence and size before sending- a trailing
[[buttons: A | B]]line renders 2-5 real Discord choice buttons; the clicked label is fed back into the same session as the next message
- Attachment download: files the user uploads (≤ 50 MB) are downloaded to a shared inbox and their local paths are appended to the prompt for
claudetoRead - Permission relay: a headless run’s permission prompts become a Discord Allow / Deny button card that the owner clicks, with no trip back to a terminal
- Idle watchdog: no total time limit by default; a run is killed only when its event stream goes silent past
BOT_IDLE_TIMEOUT_MS(a wedged process), never for merely taking a long time - Natural-language new-conversation reset: a message that is entirely one of a few reset phrases drops the stored session, so the next message starts fresh
- Per-chat session continuity: each chat’s session UUID is stored in
sessions.jsonand later messages--resumeit - 24/7 daemon: a logon scheduled task, detached launch, restart on crash, and a single-instance loopback lock keep it running across terminal close, crashes, and reboots
- Portable: no hardcoded paths; the working directory defaults to the engine directory and every path is overridable via env vars
Architecture
The boot chain that keeps it up is: logon taskClaudeDiscordBridge → wscript launch.vbs (detached) → supervise.cmd loop → bun bot.ts.
The bridge owns its own discord.js gateway instead of using the official channel engine because Anthropic gates any non-approved custom channel behind --dangerously-load-development-channels, and that flag shows a startup confirmation dialog. A headless daemon has no TTY to answer it, so it hangs. With bot.ts holding the gateway, no development-channel flag and no interactive confirmation are needed, and it can run unattended as a service.
Project files:
claude-discord-daemon
bot.ts · long-lived Discord gateway process (the bridge engine)
perm-mcp.ts · permission relay MCP
cli.ts · cdd / claude-discord-daemon CLI
launch.vbs · launches the supervisor detached
supervise.cmd · restarts on crash
install-service.ps1 · registers the logon scheduled task
.env.example · environment variable template
commands
skills
.claude-plugin
videos
Tech stack
- Runtime: Bun ≥ 1.1.0, TypeScript
- Discord: discord.js v14
- Permission relay:
perm-mcp.ts, built on the MCP SDK (@modelcontextprotocol/sdk) - Daemon: Windows logon scheduled task,
launch.vbs,supervise.cmd - Distribution: npm package and Claude Code plugin
Quick start
1
Write the bot token
Before starting, put your bot token and sender allowlist in the bridge’s state directory (default You can also set
~/.claude/channels/discord/, overridable via DISCORD_STATE_DIR). Write the token to ~/.claude/channels/discord/.env:DISCORD_BOT_TOKEN directly in the process environment; a real env var wins over the .env file.2
Set the allowlist
Create Only DMs from user IDs in
~/.claude/channels/discord/access.json and add your Discord user ID to allowFrom:allowFrom are processed; everything else is ignored (fail-closed). The file is re-read on every message, so allowlist edits apply without a restart.3
Install and start (pick one)
As an npm package:As a Claude Code plugin:Claude Code namespaces plugin commands with the plugin name, so the command is It runs the same
/claude-discord-daemon:discord. Drive the same daemon with it:cdd CLI under the hood, falling back to bunx claude-discord-daemon when cdd is not on PATH.4
Invite the bot and DM it
Invite the bot into any Discord server you are also in, then DM the bot directly.
5
Manage the daemon
claude-discord-daemon and cdd (identical).Configuration reference
Optionalaccess.json keys, with the same semantics as the official plugin:
Everything in the environment except
DISCORD_BOT_TOKEN is optional; set any of these in the process environment to override the default:
New conversation: send a message that is entirely one of the following (whole message, case-insensitive) to drop the stored session, so the next message starts a fresh
claude -p session:
來開新對話討論 X does not reset the session. On reset the bot reacts and confirms.
Notes
In practice
This fits keeping Claude Code on call in a Discord DM without leaving a terminal open: by default, long jobs are not killed for running long (only stream silence counts as wedged), operations that need approval are decided with a button click in the chat, and the bot can take attachments and send back produced files. The limits are that it only supports Windows 11 and that only allowlisted users can trigger a run.Links
- GitHub: felimet/claude-discord-daemon
- Bun: bun.sh