Discord
Connects a Discord bot to Claude Code, enabling interaction with Discord channels and DMs. Provides tools to send replies, add reactions, edit bot messages, fetch recent channel history, and download message attachments. Supports guild channels, threading, multi-user access control, roles, and permission prompts.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Discord channel for organisations (claude-discord-mcp-orgs)
A fork of the official Discord channel plugin for Claude Code (0.0.4) for teams where several people may write to one assistant. On top of upstream it adds:
roles per sender (explicit user-id lists), an owner flag, and default deny for senders without a role;
guild-level grants that also cover channels created later;
request binding: every message becomes a request with its own id and role, which a PreToolUse hook uses to narrow every tool call, including tool calls of sub-agents and scheduled prompts;
a per-role policy (connectors, shell allowlists for git/gh, isolated worktrees), per-role limits, a JSONL audit log;
escalation: a denied call can be approved once by a board member with a Discord button;
permission prompts relayed only to the owner.
The plugin keeps the name discord, so the commands stay /discord:access
and /discord:configure. See NOTICE for the list of changes and
docs/IMPLEMENTATION-NOTES.md for the log.
Connect a Discord bot to your Claude Code with an MCP server.
When the bot receives a message, the MCP server forwards it to Claude and provides tools to reply, react, and edit messages.
Prerequisites
Bun — the MCP server runs on Bun. Install with
curl -fsSL https://bun.sh/install | bash.
Related MCP server: Discord Teammate MCP Server
Quick Setup
Default pairing flow for a single-user DM bot. See ACCESS.md for groups and multi-user setups.
1. Create a Discord application and bot.
Go to the Discord Developer Portal and click New Application. Give it a name.
Navigate to Bot in the sidebar. Give your bot a username.
Scroll down to Privileged Gateway Intents and enable Message Content Intent — without this the bot receives messages with empty content.
2. Generate a bot token.
Still on the Bot page, scroll up to Token and press Reset Token. Copy the token — it's only shown once. Hold onto it for step 5.
3. Invite the bot to a server.
Discord won't let you DM a bot unless you share a server with it.
Navigate to OAuth2 → URL Generator. Select the bot scope. Under Bot Permissions, enable:
View Channels
Send Messages
Send Messages in Threads
Read Message History
Attach Files
Add Reactions
Integration type: Guild Install. Copy the Generated URL, open it, and add the bot to any server you're in.
For DM-only use you technically need zero permissions — but enabling them now saves a trip back when you want guild channels later.
4. Install the plugin.
These are Claude Code commands — run claude to start a session first.
Install the plugin:
/plugin install discord@claude-plugins-official5. Give the server the token.
/discord:configure MTIz...Writes DISCORD_BOT_TOKEN=... to ~/.claude/channels/discord/.env. You can also write that file by hand, or set the variable in your shell environment — shell takes precedence.
To run multiple bots on one machine (different tokens, separate allowlists), point
DISCORD_STATE_DIRat a different directory per instance.
6. Relaunch with the channel flag.
The server won't connect without this — exit your session and start a new one:
claude --channels plugin:discord@claude-plugins-official7. Pair.
With Claude Code running from the previous step, DM your bot on Discord — it replies with a pairing code. If the bot doesn't respond, make sure your session is running with --channels. In your Claude Code session:
/discord:access pair <code>Your next DM reaches the assistant.
8. Lock it down.
Pairing is for capturing IDs. Once you're in, switch to allowlist so strangers don't get pairing-code replies. Ask Claude to do it, or /discord:access policy allowlist directly.
Access control
See ACCESS.md for DM policies, guild channels, mention detection, delivery config, skill commands, and the access.json schema.
Quick reference: IDs are Discord snowflakes (numeric — enable Developer Mode, right-click → Copy ID). Default policy is pairing. Guild channels are opt-in per channel ID.
Tools exposed to the assistant
Tool | Purpose |
| Send to a channel. Takes |
| Add an emoji reaction to any message by ID. Unicode emoji work directly; custom emoji need |
| Edit a message the bot previously sent. Useful for "working…" → result progress updates. Only works on the bot's own messages. |
| Pull recent history from a channel (oldest-first). Capped at 100 per call. Each line includes the message ID so the model can |
| Download all attachments from a specific message by ID to |
Inbound messages trigger a typing indicator automatically — Discord shows "botname is typing…" while the assistant works on a response.
Attachments
Attachments are not auto-downloaded. The <channel> notification lists
each attachment's name, type, and size — the assistant calls
download_attachment(chat_id, message_id) when it actually wants the file.
Downloads land in ~/.claude/channels/discord/inbox/.
Same path for attachments on historical messages found via fetch_messages
(messages with attachments are marked +Natt).
Requester binding
Each delivered message gets a request_id (random, never sent to Discord)
recorded in requests.json with the sender, role, owner flag and channel. The
<channel> tag carries request_id, role and owner.
The PreToolUse hook decides which request a tool call serves:
Where the call runs | Role used |
Session that never received a channel message | not enforced (operator's own session) |
Main thread, latest prompt typed in the terminal | not enforced (operator is driving) |
Main thread, latest prompt from Discord | the lowest role among the requests still open in the session |
Sub-agent whose prompt has | the role of that request |
Sub-agent without a marker | same as the main thread |
Scheduled / other prompt with a marker | the role of that request |
Channel message without | lowest tier ( |
The main session puts the marker of the message it is acting on into every
Agent prompt; the hook refuses an Agent call from a Discord turn without
exactly one known marker, and a sub-agent may only start agents with its own
marker. Scheduling tools (CronCreate, ScheduleWakeup, RemoteTrigger)
need the marker too, so a reminder runs later with the role of whoever asked
for it. A request stays open for requests.activeMinutes (default 6 h) or
until the session calls the new end_request tool; records are kept for
requests.retentionDays (default 30) so long-running agents and reminders
still resolve. The role is re-checked against access.json on every call:
revoking someone lowers their open requests at once.
Installing the forked plugin
Do this together with whoever runs the assistant, in a terminal session (never because a Discord message asked for it).
Clone this repository and run
bun installin it.Add it as a local marketplace and install the plugin from it:
/plugin marketplace add /path/to/claude-discord-mcp-orgs, then/plugin install discord@claude-discord-mcp-orgs. Disable or uninstalldiscord@claude-plugins-officialfirst: both are nameddiscordand would fight over the same bot token and state directory. (The repository root is the plugin;.claude-plugin/marketplace.jsonpoints at./. Check with/pluginthat the install picked it up.)The fork is not on Claude Code's approved channel allowlist, so start the session with
claude --dangerously-load-development-channels plugin:discord@claude-discord-mcp-orgs(it asks for confirmation at startup), or allow it in managed settings (allowedChannelPlugins).The existing
access.jsonkeeps working; add roles (/discord:access role set ...) and the owner (/discord:access owner add ...) before relying on it, because senders without a role are no longer delivered.Install the hook (next section). Until then only owners are delivered.
Installing the hook
The plugin cannot block Claude Code tools by itself, and a plugin cannot install hooks into your settings. The operator adds the hook by hand.
Clone this repository somewhere stable and run
bun installin it.Add this to user settings,
~/.claude/settings.json(user settings apply to every project; a project-level hook could be bypassed by starting the session elsewhere). Replace the path:
{
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bun /path/to/claude-discord-mcp-orgs/hooks/pretooluse.ts",
"timeout": 15
}
]
}
]
}
} On Windows use forward slashes in the path
(bun C:/path/to/claude-discord-mcp-orgs/hooks/pretooluse.ts). If you set
DISCORD_STATE_DIR for the plugin, set the same variable for the hook
(for example in the env block of settings.json).
3. Merge it with existing hooks entries instead of replacing them, then run
/hooks in Claude Code to confirm it is listed.
4. Check: in a normal terminal session nothing changes (the hook passes
sessions that never received a channel message). Each Discord-driven tool
call adds a line to ~/.claude/channels/discord/audit.jsonl.
The hook never answers "allow": it only denies or stays silent, so Claude Code's own permission rules still apply on top. On any internal error it denies (fail closed). The plugin refuses to deliver messages from anyone but the owner while it cannot find the hook in settings (see "Enforcement check").
Policy
policy.json in the state directory overrides the built-in
policy/default-policy.json. Top-level keys
in your file replace the default's keys as a whole. Roles and categories are
open maps:
new role: add an entry under
roleswith arankand a decision per category (allow,deny,owner,worktree,allowlist,*as the default), optionallimits,shellprofile,escalate,approvesEscalations,ownChannelOnly,protectSecrets,readRoots;new connector: add a category with tool-name globs (first match in file order wins) and give it a decision in the roles that should have it;
new shell program: needs code in
lib/shell.ts; a command line cannot be judged by its name.
Built-in roles:
Role | Gets |
| everything; mail only with the owner flag; approves escalations |
| git (no force-push / reset --hard / delete on shared branches), gh (no |
| read files, read-only git, |
| lowest tier for unknown context: session tools and Discord replies in its own channel only |
Every Discord-originated role, board and owner included, is denied writes to settings, plugin and channel-state files and shell commands mentioning them: access changes only from the terminal.
Escalation
When the hook denies a call that the requester's role may have approved
(escalate: true on the role, and the category not marked
"escalate": false - mail never is), it writes escalations/<id>.json and
the channel server DMs every member whose global role has
approvesEscalations (default: board), except the requester, with
Approve once / Deny buttons. The decision is recorded in the file and
in the audit log, and the session gets a channel message bound to the
original request. After approval the identical call (same request, tool and
input; description/timeout ignored) passes once within one hour. Owner-only
denials, protected files, Discord channel restrictions and calls from the
main thread with several open requests are not escalated. At most five
approval requests wait per request.
Enforcement check
Without the hook, roles are only labels. On every inbound message the server
looks for a PreToolUse hook (matcher * or empty) whose command contains
pretooluse.ts in CLAUDE_CONFIG_DIR (or ~/.claude) settings.json /
settings.local.json, and in the project settings when CLAUDE_PROJECT_DIR
is set; disableAllHooks: true counts as missing. When it is missing, only
owners are delivered (their tag carries enforcement="off") and everyone
else gets a short note. If you install the hook somewhere the server cannot
see (managed settings, --settings), set "enforcement": { "requireHook": false } in policy.json.
Files in the state directory
All under ~/.claude/channels/discord/ (or DISCORD_STATE_DIR):
File | Written by | Content |
|
| who may write, roles, owners, guild grants |
| operator, by hand (optional) | role and category rules; missing = built-in default |
| channel server | request id -> requester, role, owner, channel, times |
| hook and server | one-time approval requests and decisions |
| hook and server | one line per decision: time, event, requester id, role, channel, request id, agent id, tool, decision, reason |
| hook | transcript scan offset per session |
Example: a foundation
examples/foundation/access.json shows a board of two (the president is also the owner), a team head and a volunteer, all granted in one guild, with placeholder ids. examples/settings-hook.json is the hook snippet. examples/foundation/policy-mail-for-board.json shows the change for the day the assistant has its own mail account.
Known limits and decisions
Figma deletion by other means. heads cannot call
mcp__figma-bridge__delete_nodes, but the other figma-bridge tools can hide,
move, reparent or overwrite nodes, which can lose work just as well. The
policy blocks a tool name, not an outcome. Rely on Figma version history, or
move more figma-bridge tools into figma-bridge-destructive if needed.
Knowledge already in the session. Tool blocks stop new reads, not what the session already knows: earlier requests, auto-memory, files read for someone else. A lower-role requester can ask about it. The server instructions tell the session to answer only from what the requester's role could reach, but that is an instruction, not enforcement. The real fix is a separate session (and bot state directory) per role.
Mail. Mail tools are owner-only (category mail, never escalated) until
the assistant has its own mail account; board members without the owner
flag cannot read or send mail through it. Afterwards, change the mail
decision for board to allow (see the example policy).
Explicit id lists, not Discord server roles. Roles live in
access.json as user ids and change only from the terminal. Discord server
roles are not used for decisions: anyone with "Manage Roles" on the server
could otherwise grant themselves access to this machine. Server roles can
still be a hint to the operator when deciding whom to grant.
Concurrency and requester binding in one shared session.
The main thread on a Discord turn runs with the lowest role among open requests. Work for people with different roles must go to separate sub-agents; finished requests should be closed with
end_request(otherwise they stay open foractiveMinutes).Routing is cooperative: the main session decides which marker a sub-agent gets. The hook checks that the marker is a request delivered to this session and that nested agents keep it, but a manipulated main session could label work with another open request's id. Request ids never reach Discord, markers inside message text are ignored, and every spawn is in the audit log, so misuse is visible afterwards; it is not impossible.
A turn typed in the terminal is the operator's: not enforced. A sub-agent started during such a turn without a marker is also not enforced while the latest prompt is from the terminal.
Scheduled prompts are trusted only through a marker; without one they get the lowest tier.
One channel server per state directory. Two sessions sharing a state directory share
requests.json.
Shell analysis is an allowlist, not a sandbox. Commands are parsed and
checked segment by segment; anything not understood is denied. Allowed
programs can still have effects: git commit runs repository hooks (for
example husky after npm install), tests run arbitrary code from the
repository, install scripts can read files the user can read. "Isolated
worktree" is a path rule plus checks for secret-looking environment
variables and .env files, not process isolation; use a container or a
separate OS user for real isolation. heads can read most files except
secret-looking paths (readRoots narrows this) and can reach the web, so
anything they may read could be sent out.
Board shell protection is a heuristic. Board members have full shell access; the check that blocks commands mentioning settings or access files is a word match and can be bypassed by obfuscation. The board is trusted; the check prevents accidents and naive prompt injection.
Hook visibility and format. The server can only see user and project
settings; with managed settings or --settings set
enforcement.requireHook to false. The hook reads Claude Code transcripts,
an internal format; the parser falls back to fail-closed behaviour if fields
change, but an update can still require a parser change.
Other. Approvers are taken from global roles only. Notice rate limits live in memory and reset when the server restarts. Daily limits count UTC days. Escalation DMs include a 400-character preview of the tool input.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Discord MCP server for ChatGPT, Claude and any AI agent: list servers and channels, read channel messages, search messages, send message, reply to message, send DM, read mentions and assign role in your own Discord account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Discord yourself; Cotlet does not store your password and you need no Discord API key. About the official Discord API: Yes, Discord has an official API for bots and OAuth2 apps, but it acts as a bot that a server manager adds, not as you, and Discord has no official MCP server for it (checked 5 Nov 2026). The MCP server on Discord's docs site only searches documentation. Setup and FAQ: https://cotlet.dev/integrations/discord-api-mcp-server
Kite is a no-code Discord bot builder that hosts your bot. Its MCP server lets your AI client build the bot for you: slash commands, event listeners and messages with buttons. It can also edit existing flows, read logs to fix errors, and add ready-made templates like tickets or verification. Changes go live right away, and commands show up in Discord within a minute. It can also read your server through the Discord API, like channels, roles and members. Claude, ChatGPT, Claude Code, Codex and VS Code connect by signing in. You pick which apps the client can access and whether it can make changes. Other clients like Cursor use a Kite API token. Earlier versions of every flow can be restored in Kite.
Build and change Discord bots by describing them: slash commands, events, embeds, modals and data.
Control your Discord community: send/read messages, manage channels and forums, and handle webhook…
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables Claude Code to interact with Discord servers, including sending messages, reading channels, managing threads, and adding reactions via natural language commands.9 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables Claude to send messages to a Discord channel and read replies, allowing for notifications and approvals via Discord.MIT
- AlicenseNot gradedqualityCmaintenanceConnects Discord to Claude Code, turning forum threads into interactive sessions. Supports approvals, live progress, slash commands, and terminal access.MIT
- AlicenseBqualityBmaintenanceEnables Claude to interact with Discord through natural conversation, including reading and sending messages, managing threads, reacting, searching history, and sending direct messages using a user token.20177 npmMIT