better-discord-mcp
# better-discord-mcp
An MCP server that lets an AI client (Claude Desktop / Claude Code) read and
control Discord through a **BetterDiscord plugin**. It drives your logged-in
Discord client, so it can read any channel you can see, send messages, and
perform moderation (ban / kick / timeout / delete) wherever your account has
permission.
```
Claude ──stdio──▶ MCP server ──WebSocket(127.0.0.1:6473)──▶ BD plugin ──▶ Discord
```
## ⚠️ Important: Terms of Service
This automates a **user account**, not a bot account. Automating a user account
("selfbotting") violates Discord's Terms of Service and can result in account
termination, regardless of intent. Use only on your own or explicitly
authorized servers, ideally with a test account. You accept the risk.
For ToS-compliant moderation, build a real Discord bot instead — but that cannot
"see your client" the way this does.
## Setup
### 1. Install the BetterDiscord plugin
- Copy `plugin/DiscordMCP.plugin.js` into your BetterDiscord plugins folder:
- Windows: `%AppData%\BetterDiscord\plugins\`
- Open Discord → Settings → Plugins → enable **DiscordMCP**.
- It will keep trying to connect to the bridge every 3s (toast shows status).
### 2. Install server deps
```
npm install
```
### 3. Register the MCP server with your client
**Claude Code:**
```
claude mcp add discord -- node "/absolute/path/to/better-discord-mcp/server/index.js"
```
**Claude Desktop** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"discord": {
"command": "node",
"args": ["/absolute/path/to/better-discord-mcp/server/index.js"]
}
}
}
```
The server hosts the WebSocket bridge; the plugin connects to it. Start order
doesn't matter — the plugin auto-reconnects. Use the `discord_status` tool to
confirm the link is up.
## Tools
**Read / info**
| Tool | Purpose |
|------|---------|
| `discord_status` | Is the plugin connected? |
| `discord_get_context` | Current guild / channel / user |
| `discord_get_guilds` | List your servers |
| `discord_get_channels` | Channels in a guild |
| `discord_get_messages` | Recent messages in a channel |
| `discord_search_messages` | Search a guild or channel |
| `discord_get_user` | User / guild-member info |
| `discord_get_roles` | List a guild's roles |
| `discord_get_dms` | List open DM channels |
**Messaging**
| Tool | Purpose |
|------|---------|
| `discord_send_message` | Send a message |
| `discord_edit_message` | Edit your own message |
| `discord_add_reaction` / `discord_remove_reaction` | React to a message |
| `discord_trigger_typing` | Show typing indicator |
| `discord_open_dm` | Open/fetch a DM with a user |
**Moderation** (🔒 gated — need `confirm:true`)
| Tool | Purpose |
|------|---------|
| `discord_delete_message` 🔒 | Delete a message |
| `discord_purge_messages` 🔒 | Bulk-delete recent messages (<14 days) |
| `discord_ban_user` 🔒 | Ban (needs Ban Members) |
| `discord_kick_user` 🔒 | Kick (needs Kick Members) |
| `discord_timeout_user` 🔒 | Timeout / un-timeout a member |
| `discord_add_role` | Add a role (Manage Roles) |
| `discord_remove_role` 🔒 | Remove a role (Manage Roles) |
🔒 = destructive: the first call returns a confirmation preview and does
nothing; re-call with `confirm:true` to execute. Disable with
`DISCORD_MCP_REQUIRE_CONFIRM=false`.
### Full surface (~100 tools)
This exposes essentially all of Discord's REST-driven features. Beyond the
common tools above, there are named tools for: audit log, bans (list/get/unban),
threads (create/edit/join/leave/members/archived lists), forum posts, channel
permission overwrites, channel/role reordering, polls, replies & embeds,
crossposting, scheduled events, AutoMod rules, stickers, integrations,
webhooks (create/edit/execute), invites (resolve/join), friends/block/notes,
status, prune, voice mute/deafen & status, welcome screen, vanity url, and more.
See `COVERAGE.md` for the complete map and what is **not** reachable.
### `discord_request` — raw escape hatch
For anything without a named tool (templates, onboarding, slash-command
registration, brand-new endpoints), call any REST endpoint directly:
```
discord_request(method="post", path="/guilds/123/templates", body={ "name": "..." })
```
Non-GET raw calls are also gated by the confirmation rule.
### What is NOT possible via this bridge
Voice **audio** (joining/streaming/listening), live event streaming, and the
full online-member list are **gateway/WebRTC**, not REST — this bridge can't
drive them. It *can* move/mute/deafen members and set voice channel status.
Use polling (`get_messages`) instead of live message events.
## Config
- `DISCORD_MCP_PORT` (default `6473`) — must match `this.port` in the plugin.
- `DISCORD_MCP_TIMEOUT` (default `15000` ms) — per-request timeout.
- `DISCORD_MCP_REQUIRE_CONFIRM` (default `true`) — gate destructive tools.
## How API calls are authenticated
Current Discord builds obfuscate the internal REST client (`getAPIBaseURL`) and
the token module (`getToken`), so the plugin doesn't rely on them. Instead it
**sniffs your `Authorization` header** from Discord's own outgoing requests
(hooking `XMLHttpRequest`/`fetch`) and then calls `https://discord.com/api/v9`
directly with `fetch`. This is version-proof — it survives Discord updates.
One consequence: the token is only captured after Discord makes an authenticated
request. Most live traffic goes over the gateway WebSocket, so right after
enabling the plugin you may need to **switch channels once** to trigger a REST
call. After that the token is cached for the session.
## Troubleshooting
- **`not connected`**: Discord must be running with the plugin enabled. Check the
plugin toast / Discord devtools console (Ctrl+Shift+I) for errors.
- **`Auth token not captured yet`**: click into a different channel once (forces a
REST request the plugin can read the token from), then retry.
- **`Discord API 401/403`**: your account lacks permission for that action, or the
captured token is stale — toggle the plugin off/on and switch channels.
- **Diagnostics**: `discord_request(path="__debug__")` dumps module/token state
for debugging without touching devtools.
TDQS
Scored across 128 tools
Most tools have clearly distinct purposes, but there are several overlapping tools for sending messages (send_message, reply_message, send_embed, send_file, execute_webhook) that could cause confusion for an agent. Overall, the descriptions help disambiguate.
All tools follow a consistent verb_noun pattern with the 'discord_' prefix in snake_case, e.g., discord_send_message, discord_create_role. No naming inconsistencies.
With 128 tools, the server is massively over-scoped. This far exceeds the typical well-scoped range of 3-15 tools, making it difficult for agents to navigate and select the correct tool.
The tool set covers virtually all Discord API features, including guild, channel, role, permission, webhook, emoji, thread, scheduled events, automod, voice, and user management. A generic discord_request tool ensures no endpoint is missing.