Skip to main content
Glama
Sys0xdbg

better-discord-mcp

by Sys0xdbg
README.md
# 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

C2.9/5.0

Scored across 128 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count1/5

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.

Completeness5/5

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.

Maintenance

ActivityInactive
ResponsivenessNo issues