Skip to main content
ClaudeWave
MCP ServersOfficial Registry0 stars0 forksPythonMITUpdated today
Install in Claude Code / Claude Desktop
Method: pip / Python · -e
Claude Code CLI
claude mcp add discord-mcp -- python -m -e
claude_desktop_config.json (Claude Desktop)
{
  "mcpServers": {
    "discord-mcp": {
      "command": "python",
      "args": ["-m", "venv"]
    }
  }
}
1. Run the command above in your terminal (Claude Code), or paste the JSON config into claude_desktop_config.json (Claude Desktop).
2. Replace any <placeholder> values with your API keys or paths.
3. Restart Claude. The MCP server and its tools appear automatically.
💡 Install first: pip install -e
Use cases

MCP Servers overview

# discord-mcp

[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Tests](https://img.shields.io/badge/tests-163%20%28162%20passing%2C%201%20skipped%29-brightgreen)](#testing)

An MCP server over the real [Discord REST API](https://discord.com/developers/docs/reference)
-- so a Claude agent calls `list_channels(guild_id="...")` instead of
hand-rolling an authenticated `httpx` request. Built to the
[github-mcp](https://github.com/jaimenbell/github-mcp)/[bus-mcp](https://github.com/jaimenbell/bus-mcp)
standard in this portfolio (own pyproject, fastmcp server, typed errors, real
test suite, honest README) -- fifth flagship, first over Discord.

**5 read-only tools, always on** + **7 write tools, gated OFF by default**
behind `DISCORD_MCP_ENABLE_WRITE=1` -- see "Write tools" below.

## Quickstart (60 seconds)

```bash
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
```

Add to your Claude Desktop/Code MCP config:

```json
{
  "mcpServers": {
    "discord-mcp": {
      "command": "python",
      "args": ["C:/path/to/discord-mcp/run_server.py"],
      "env": { "DISCORD_BOT_TOKEN": "your-bot-token-here" }
    }
  }
}
```

Without `DISCORD_BOT_TOKEN` set, every tool call still returns a clean
structured error (Discord's own 401) instead of crashing -- see "Typed
errors" below. Write tools also need `DISCORD_MCP_ENABLE_WRITE=1` in the same
`env` block, or they refuse locally with `policy_refusal` -- see "Write
tools" below.

## What this is / is not

This is a **reference portfolio implementation** demonstrating an MCP server
over a real external SaaS API (Discord) -- it is **NOT** an official Discord
MCP server, and it is not affiliated with Discord Inc. It started from an
earlier, separate sibling project's `HttpDiscordClient`, a stdlib-`urllib`
Discord transport built and verified against a real live guild for that
project's own server-provisioning tooling. This repo hand-adapts that
client's request-building, header construction (including its deliberately
descriptive `User-Agent` -- see below), and error handling onto `httpx`
(matching this fleet's other MCP servers) as its own standalone client with
no dependency on that sibling project. discord-mcp does not import from or
depend on that other package at all.

Started as 5 read-only tools with no write capability at all; now ships a
7-tool write group, off by default, mirroring the `*_MCP_ENABLE_WRITE`-style
gate already shipped in github-mcp/bus-mcp/desktop-mcp in this same
portfolio -- see "Write tools" below.

## Tools

### Read (always on, no gate)

| Tool | Discord endpoint | Purpose |
|---|---|---|
| `list_channels` | `GET /guilds/{guild_id}/channels` | All channels (every type) in a guild |
| `list_roles` | `GET /guilds/{guild_id}/roles` | All roles in a guild |
| `list_categories` | `GET /guilds/{guild_id}/channels` (filtered) | Category channels only (Discord type 4) -- Discord has no dedicated categories-only endpoint, so this filters the same channels payload client-side |
| `get_channel_permission_overwrites` | `GET /channels/{channel_id}` (`permission_overwrites` field) | Role/member allow+deny bitfields set on one channel |
| `get_member_roles` | `GET /guilds/{guild_id}/members/{member_id}` (`roles` field) | Role ids currently assigned to one guild member |

### Write (gated behind `DISCORD_MCP_ENABLE_WRITE=1`, default OFF)

| Tool | Discord endpoint | Purpose |
|---|---|---|
| `create_channel` | `POST /guilds/{guild_id}/channels` | Create a text/voice/category channel |
| `edit_channel` | `PATCH /channels/{channel_id}` | Rename/re-topic/re-parent/reorder an existing channel |
| `create_role` | `POST /guilds/{guild_id}/roles` | Create a role |
| `edit_role` | `PATCH /guilds/{guild_id}/roles/{role_id}` | Edit an existing role |
| `edit_guild` | `PATCH /guilds/{guild_id}` | Update guild-level identity (name/icon/banner/description) |
| `delete_channel` | `DELETE /channels/{channel_id}` | **Destructive.** Delete a channel |
| `create_message` | `POST /channels/{channel_id}/messages` | Post a message to a text channel (`content`: non-empty, <= 2000 chars) |

## Write tools

Seven write tools were added on top of the original 5 read-only tools,
mirroring the exact write-gate pattern already shipped in this portfolio
(github-mcp's `GITHUB_MCP_ENABLE_WRITE`, desktop-mcp's
`DESKTOP_MCP_ENABLE_*`, and most closely bus-mcp's `BUS_MCP_ENABLE_WRITE` +
`gated_write` decorator, copied as the reference template). `create_message`
was added a night later than the other 6, once it became clear that none of
create_channel/edit_channel/create_role/edit_role/edit_guild/delete_channel
can actually post content to a channel -- it reuses the exact same gate and
the `post()` helper `create_channel`/`create_role` already added to
`client.py`, no new HTTP plumbing.

- **Off by default.** Set `DISCORD_MCP_ENABLE_WRITE=1` (or `true`/`yes`/`on`)
  in the server's environment to enable the write group. Unset (or any other
  value) means every write tool call refuses **locally**, with zero Discord
  API calls attempted, returning a structured `policy_refusal` error:
  ```json
  {"ok": false, "error": {"type": "policy_refusal", "message": "Tool group 'write' is disabled. Set DISCORD_MCP_ENABLE_WRITE=1 in the server's environment to enable it.", "group": "write", "tool": "create_channel", "required_env": "DISCORD_MCP_ENABLE_WRITE"}}
  ```
- **Enforced at the route layer, not just the MCP-tool layer.** The
  `@config.gated_write` decorator wraps each write function directly in
  `discord_mcp/routes.py` (not merely the `@mcp.tool` wrapper in
  `server.py`), so the gate is unit-testable without spinning up fastmcp or
  a real transport, and can't be bypassed by any alternate calling path into
  `routes.py`.
- **Read fresh from the environment on every call**, never cached at
  import time -- an operator can arm/disarm the write group without
  restarting the server process, and tests can monkeypatch it per-test.
- **`delete_channel` gets no separate or lower bar.** Despite being
  genuinely destructive/irreversible against a real guild, it is gated
  behind the exact same `DISCORD_MCP_ENABLE_WRITE` env var as the other
  write tools -- confirmed by dedicated tests in `test_routes.py` (setting
  plausible-but-wrong var names like `DISCORD_MCP_ENABLE_DELETE` does
  **not** arm it).
- **Same auth path as the read tools.** Write calls reuse the exact same
  `_headers()`/pooled `httpx.AsyncClient` construction in `client.py` as
  every read tool -- there is only one auth code path in this repo.
- **`create_message` validates content locally, not just via Discord's own
  400.** `content` must be a non-empty string of at most 2000 characters
  (Discord's real message-length limit) -- checked by
  `client.validate_message_content` before any request is built, same
  "never even attempt the call" discipline as the snowflake-id checks. A
  violation returns `validation_error`, not a raw Discord 400.

### `icon_base64`/`banner_base64` on `edit_guild` -- a flagged assumption

Discord's docs describe `PATCH /guilds/{id}`'s `icon`/`banner` fields as an
"image data" string -- a full data URI (`data:image/png;base64,<base64>`),
not a bare base64 payload. `edit_guild` accepts either:

- a full `data:...;base64,...` URI, passed through unchanged, or
- raw base64 bytes, which are **assumed to be PNG** and wrapped as
  `data:image/png;base64,<value>`.

**This PNG assumption is not verified against a real Discord response** --
this repo has not confirmed whether Discord accepts, rejects, or silently
mis-renders a non-PNG image (JPEG, animated GIF for boosted-server icons,
etc.) sent under an `image/png` label. If you have a non-PNG image, pass a
full `data:image/...;base64,...` URI yourself rather than relying on the
default. Flagged here rather than guessed silently.

## Typed errors, never a raw crash

Every tool returns `{"ok": true, ...}` on success or `{"ok": false, "error":
{...}}` on failure -- never an unhandled exception or stack trace.

- **`network_error`** -- connection refused, timeout, DNS failure, or a
  malformed request URL. Discord's API wasn't reachable at all.
- **`auth_error`** (401) -- missing or invalid bot token.
- **`permission_error`** (403, with Discord's own JSON error body, e.g.
  `{"message": "Missing Access", "code": 50001}`) -- the bot lacks the
  permission/scope for this call, or isn't in the guild.
- **`cloudflare_blocked`** (403, with **no** JSON error body) -- Discord's
  API docs ask for a descriptive `User-Agent`; without one, a client's
  default UA is a well-known bot fingerprint that Discord's Cloudflare edge
  can reject with a bare 403 *before* the request ever reaches route-level
  permission checks. This is otherwise indistinguishable from a real
  `permission_error` 403 -- Discord's real permission-denied responses
  always carry a JSON body, so **absence of a JSON body on a 403 is the
  signal this type is built on.** This is a best-effort heuristic, not a
  certainty: a proxy, load balancer, or future Discord change that strips
  the body on a *different* kind of 403 would also land here. discord-mcp
  sends the same descriptive `User-Agent` this heuristic exists to explain
  (see `client.py`'s `_headers()`), so in practice this type should rarely
  fire from this server's own calls -- see "Honest limitations" below.
- **`not_found`** (404) -- bad/unknown guild, channel, role, or member id.
- **`rate_limited`** (429) -- Discord's rate limit. Carries `retry_after_s`,
  parsed straight from Discord's own JSON body (`retry_after`, in seconds).
  This server does **not** auto-retry -- it surfaces the limit as a
  structured error immediately and leaves any backoff/retry decision to the
  caller.
- **`decode_error`** -- a 2xx response whose body isn't valid JSON (should
  not happen against the real API; guards against a malformed proxy/mock).
- **`discord_api_error`** -- any other 4xx/5xx not covered above.
-

What people ask about discord-mcp

What is jaimenbell/discord-mcp?

+

jaimenbell/discord-mcp is mcp servers for the Claude AI ecosystem with 0 GitHub stars.

How do I install discord-mcp?

+

You can install discord-mcp by cloning the repository (https://github.com/jaimenbell/discord-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.

Is jaimenbell/discord-mcp safe to use?

+

jaimenbell/discord-mcp has not been audited yet by our security agent. Review the original repository on GitHub before using it in production.

Who maintains jaimenbell/discord-mcp?

+

jaimenbell/discord-mcp is maintained by jaimenbell. The last recorded GitHub activity is from today, with 0 open issues.

Are there alternatives to discord-mcp?

+

Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.

Deploy discord-mcp to your cloud

Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.

Maintain this repo? Add a badge to your README

Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.

Featured on ClaudeWave: jaimenbell/discord-mcp
[![Featured on ClaudeWave](https://claudewave.com/api/badge/jaimenbell-discord-mcp)](https://claudewave.com/repo/jaimenbell-discord-mcp)
<a href="https://claudewave.com/repo/jaimenbell-discord-mcp"><img src="https://claudewave.com/api/badge/jaimenbell-discord-mcp" alt="Featured on ClaudeWave: jaimenbell/discord-mcp" width="320" height="64" /></a>

More MCP Servers

discord-mcp alternatives