关于 discord-mcp
discord-mcp 是供 Agent 和其他 MCP 客户端调用 Discord REST API 的参考 MCP 服务。它默认提供 5 个读取工具,7 个写入工具则需通过环境变量显式启用。
主要功能
- 01读取服务器频道、角色和分类
- 02检查频道权限覆盖设置
- 03获取成员的角色 ID
- 04创建和修改频道、角色及服务器信息
- 05发布消息和删除频道
- 06结构化错误及写入开关
适用场景
- 01查看服务器的频道和角色配置
- 02查询成员已分配的角色
- 03启用写入后管理服务器结构
- 04启用写入工具后发布频道消息
MCP 相关
将 Discord REST API 操作作为 MCP 工具提供。读取工具始终可用;写入工具需要设置 DISCORD_MCP_ENABLE_WRITE=1。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
12收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| guild_id | string | 是 |
详细介绍
discord-mcp
一个基于真实 Discord REST API 的 MCP 服务——Claude Agent 可以调用 list_channels(guild_id="..."),而不必手动编写经过身份验证的 httpx 请求。本项目按照此作品集中的 github-mcp/bus-mcp 标准构建(独立的 pyproject、fastmcp 服务、类型化错误、完整测试套件、如实的 README)——这是第五个旗舰项目,也是首个基于 Discord 的项目。
5 个只读工具,始终启用 + 7 个默认关闭的写入工具,由 DISCORD_MCP_ENABLE_WRITE=1 控制——详见下文“写入工具”。
快速开始(60 秒)
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
将以下内容添加到 Claude Desktop/Code MCP 配置中:
{
"mcpServers": {
"discord-mcp": {
"command": "python",
"args": ["C:/path/to/discord-mcp/run_server.py"],
"env": { "DISCORD_BOT_TOKEN": "your-bot-token-here" }
}
}
}
如果未设置 DISCORD_BOT_TOKEN,每次工具调用仍会返回清晰的结构化错误(Discord 自身的 401),而不会崩溃——详见下文“类型化错误”。写入工具还要求在同一个 env 块中设置 DISCORD_MCP_ENABLE_WRITE=1,否则会在本地通过 policy_refusal 拒绝调用——详见下文“写入工具”。
这是什么/不是什么
这是一个参考作品集实现,用于演示如何基于真实的外部 SaaS API(Discord)构建 MCP 服务——它不是 Discord 官方 MCP 服务,也与 Discord Inc. 无关。它最初基于另一个独立同级项目的 HttpDiscordClient。该项目使用标准库 urllib 构建了 Discord 传输层,并针对真实运行中的服务器进行了验证,用于该项目自身的服务器配置工具。本仓库将该客户端的请求构造、标头构造(包括特意写得较为详细的 User-Agent——见下文)和错误处理手动移植到 httpx 上(与此项目系列中的其他 MCP 服务保持一致),作为一个独立客户端使用,不依赖那个同级项目。discord-mcp 完全不会导入或依赖那个其他软件包。
最初版本只有 5 个只读工具,不具备任何写入能力;现在新增了 7 个写入工具组,默认关闭,与同一作品集中的 github-mcp/bus-mcp/desktop-mcp 已实现的 *_MCP_ENABLE_WRITE 式开关一致——详见下文“写入工具”。
工具
读取(始终启用,无需开关)
| 工具 | Discord 端点 | 用途 |
|---|---|---|
list_channels | GET /guilds/{guild_id}/channels | 服务器中的所有频道(所有类型) |
list_roles | GET /guilds/{guild_id}/roles | 服务器中的所有身份组 |
list_categories | GET /guilds/{guild_id}/channels(经过筛选) | 仅限分类频道(Discord 类型 4)——Discord 没有专门用于获取分类频道的端点,因此会在客户端筛选相同的频道负载 |
get_channel_permission_overwrites | GET /channels/{channel_id}(permission_overwrites 字段) | 单个频道上设置的身份组/成员允许和拒绝权限位字段 |
get_member_roles | GET /guilds/{guild_id}/members/{member_id}(roles 字段) | 当前分配给某个服务器成员的身份组 ID |
写入(由 DISCORD_MCP_ENABLE_WRITE=1 控制,默认关闭)
| 工具 | Discord 端点 | 用途 |
|---|---|---|
create_channel | POST /guilds/{guild_id}/channels | 创建文字/语音/分类频道 |
edit_channel | PATCH /channels/{channel_id} | 重命名/更改主题/更改所属分类/调整现有频道顺序 |
create_role | POST /guilds/{guild_id}/roles | 创建身份组 |
edit_role | PATCH /guilds/{guild_id}/roles/{role_id} | 编辑现有身份组 |
edit_guild | PATCH /guilds/{guild_id} | 更新服务器级标识信息(名称/图标/横幅/描述) |
delete_channel | DELETE /channels/{channel_id} | 不可逆操作。 删除频道 |
create_message | POST /channels/{channel_id}/messages | 在文字频道中发布消息(content:非空,最多 2000 个字符) |
写入工具
在原有的 5 个只读工具基础上新增了 7 个写入工具,沿用了此工具集中已采用的相同写入门控模式(github-mcp 的 GITHUB_MCP_ENABLE_WRITE、desktop-mcp 的 DESKTOP_MCP_ENABLE_*,以及最相近的 bus-mcp 的 BUS_MCP_ENABLE_WRITE + gated_write 装饰器,并以其作为参考模板)。create_message 比其他 6 个工具晚一晚添加,因为后来发现 create_channel/edit_channel/create_role/edit_role/edit_guild/delete_channel 都无法实际向频道发布内容——它复用了完全相同的门控,以及 create_channel/create_role 已添加到 client.py 的 post() 辅助函数,无需新增 HTTP 处理逻辑。
- 默认关闭。 在服务的环境中设置
DISCORD_MCP_ENABLE_WRITE=1(或true/yes/on)即可启用写入工具组。未设置(或设置为任何其他值)时,所有写入工具调用都会在本地拒绝,不会尝试调用任何 Discord API,并返回结构化的policy_refusal错误:{"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"}} - 在路由层强制执行,而不只是在 MCP 工具层。
@config.gated_write装饰器直接包装discord_mcp/routes.py中的每个写入函数(而不仅仅是server.py中的@mcp.tool包装器),因此无需启动 fastmcp 或实际传输即可进行单元测试,而且无法通过任何进入routes.py的备用调用路径绕过门控。 - 每次调用时都从环境中重新读取,绝不在导入时缓存——操作人员无需重启服务进程即可启用或停用写入工具组,测试也可以针对每个测试进行猴子补丁。
delete_channel没有单独或更低的门槛。 尽管它对真实服务器确实具有破坏性且不可逆,但它与其他写入工具一样,受同一个DISCORD_MCP_ENABLE_WRITE环境变量控制——test_routes.py中的专项测试已确认,设置看似合理但错误的变量名(如DISCORD_MCP_ENABLE_DELETE)不会启用该工具。- 与只读工具使用相同的身份验证路径。 写入调用复用
client.py中与所有只读工具完全相同的_headers()/池化httpx.AsyncClient构造逻辑——本仓库只有一个身份验证代码路径。 create_message会在本地验证内容,而不只是依赖 Discord 自身返回 400。content必须是非空字符串,且最多包含 2000 个字符(Discord 的实际消息长度限制)——在构造任何请求之前,由client.validate_message_content检查;这遵循与 snowflake-id 检查相同的“绝不尝试调用”原则。违规时会返回validation_error,而不是原始 Discord 400 错误。
edit_guild 中的 icon_base64/banner_base64——一项已标注的假设
Discord 文档将 PATCH /guilds/{id} 的 icon/banner 字段描述为“图像数据”字符串,即完整的数据 URI(data:image/png;base64,<base64>),而不是纯 base64 内容。edit_guild 接受以下任一格式:
- 完整的
data:...;base64,...URI,并原样传递;或 - 原始 base64 字节,假定为 PNG,并封装为
data:image/png;base64,<value>。
此 PNG 假设尚未通过真实的 Discord 响应验证——本仓库尚未确认,Discord 对以 image/png 标记发送的非 PNG 图像(JPEG、用于服务器加成图标的动画 GIF 等)是接受、拒绝,还是会静默地错误渲染。如果使用非 PNG 图像,请自行传入完整的 data:image/...;base64,... URI,而不要依赖默认行为。此处特意标明该假设,而不是悄然猜测。
类型化错误,不会直接崩溃
每个工具成功时都会返回 {"ok": true, ...},失败时则返回 {"ok": false, "error": {...}}——绝不会抛出未处理的异常或堆栈跟踪。
network_error——连接被拒绝、超时、DNS 失败或请求 URL 格式错误。完全无法访问 Discord 的 API。auth_error(401)——缺少或无效的机器人令牌。permission_error(403,包含 Discord 自身的 JSON 错误正文,例如{"message": "Missing Access", "code": 50001})——机器人缺少此次调用所需的权限或范围,或不在该服务器中。cloudflare_blocked(403,不包含 JSON 错误正文)——Discord 的 API 文档要求提供描述性User-Agent;如果不提供,客户端的默认 UA 是一个众所周知的机器人指纹,可能会被 Discord 的 Cloudflare 边缘节点拒绝,返回一个裸 403,而请求甚至还没到达路由级权限检查。这种情况与真正的permission_error403 无法区分——Discord 真正的权限拒绝响应始终会包含 JSON 正文,因此**此类型的判断依据就是 403 响应中没有 JSON 正文。**这只是一种尽力而为的启发式判断,并非绝对可靠:如果代理、负载均衡器或 Discord 未来的更改在其他类型的 403 响应中剥除了正文,也会被归为此类型。discord-mcp 会发送此启发式判断旨在说明的同一种描述性User-Agent(参见client.py中的_headers()),因此实际使用中,此服务自身的调用很少会触发此类型——参见下文“坦诚说明限制”。not_found(404)——服务器、频道、角色或成员 ID 错误或未知。rate_limited(429)——触发了 Discord 的速率限制。包含retry_after_s,直接从 Discord 自身的 JSON 正文中解析(retry_after,单位为秒)。此服务不会自动重试——而是立即将限制作为结构化错误返回,并将是否退避或重试的决定留给调用方。decode_error——2xx 响应正文不是有效的 JSON(真实 API 不应出现这种情况;用于防范格式错误的代理或模拟对象)。discord_api_error——其他未归入以上类型的 4xx/5xx 错误。policy_refusal(仅限写入工具)——写入工具组已禁用;参见上文“写入工具”。不会尝试调用 Discord API。invalid_id——服务器、频道、角色或成员 ID 在构建请求前未通过 snowflake 格式验证(用于防止路径注入)。validation_error(仅限create_message)——content为空或超过 Discord 实际的 2000 字符消息长度限制;在构建请求前捕获此错误。
内部会由 discord_mcp/client.py 抛出类型化异常 DiscordUnreachable / DiscordApiError(对于 2xx 响应正文不是 JSON 的情况,还会使用其子类 DiscordDecodeError);discord_mcp/routes.py 会捕获这两类异常,并在工具返回前将其规范化为上述字典结构。测试会针对每种错误类型,在每种 HTTP 方法(GET/POST/PATCH/DELETE)下测试这两个层。
明确的局限性
cloudflare_blocked与permission_error的区分是一种启发式判断(依据是否存在 JSON 响应体),并非 Discord 文档说明或保证的行为。对于它旨在解释的特定故障模式(因缺少通用 User-Agent 而遭到边缘节点拒绝),这种判断是准确的;但如果 403 响应体因其他原因被剥离(例如中间的代理行为异常),也会被归类为cloudflare_blocked。get_channel_permission_overwrites和get_member_roles并未通过实时冒烟测试覆盖所有可能的实际边界情况(例如成员没有任何角色、频道没有任何权限覆盖项)——仅通过 respx 模拟的单元测试进行了测试。实时冒烟测试只调用list_channels/list_roles,不会调用任何写入工具。- 任何地方都未实现分页。
list_channels/list_roles对应的是单次请求、不分页的 Discord 端点(Discord 对这两类端点也不分页),因此这对范围内的工具并无影响——但需要对成员列表分页的大型公会完全不在范围内(此处没有list_members工具)。 edit_guild中icon_base64/banner_base64默认“假定为 PNG”的行为尚未通过真实的 Discord 响应验证——请参阅上文“写入工具”。- 没有任何写入工具通过真实的 Discord API 进行过测试(这是刻意为之——本任务的限制要求不进行任何真实网络调用;实时冒烟测试仍仅执行只读操作)。这包括
create_message——在本仓库自己的构建/测试过程中,它从未向真实运行中的公会发送过真实消息;这仍是一个刻意分开的操作,需要由操作员执行。
环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
DISCORD_BOT_TOKEN | 未设置 | Bot 令牌,以 Authorization: Bot <token> 发送。在单元测试套件中,即使没有令牌,只读工具仍可运行(完全使用模拟);对于真实 API,缺少令牌会由 Discord 自身返回常规的 auth_error(401)。 |
DISCORD_MCP_ENABLE_WRITE | 未设置(关闭) | 设置为 1/true/yes/on 以启用 7 个写入工具。请参阅上文“写入工具”。 |
DISCORD_MCP_TIMEOUT_S | 10.0 | 每次请求的超时时间(秒) |
DISCORD_MCP_LIVE | 未设置 | 设置为 1 以运行真实网络冒烟测试(请参阅“测试”) |
DISCORD_MCP_SMOKE_GUILD_ID | 未设置 | 实时冒烟测试的目标公会 ID。未设置时会正常跳过测试。 |
DISCORD_MCP_SMOKE_ENV_PATH | 未设置 | 外部 .env 文件的路径;如果环境中尚未设置 DISCORD_BOT_TOKEN,实时冒烟测试会从该文件读取。 |
DISCORD_API_BASE(https://discord.com/api/v10)是固定常量,无法通过环境变量覆盖——与 bus-mcp 可自托管的 BUS_MCP_BASE_URL 不同,Discord 的 REST API 只有一个真实的基础 URL。
用法示例
连接到 Claude 会话后,Agent 可以:
list_channels(guild_id="123456789012345678")
list_roles(guild_id="123456789012345678")
list_categories(guild_id="123456789012345678")
get_channel_permission_overwrites(channel_id="...")
get_member_roles(guild_id="123456789012345678", member_id="...")
设置 DISCORD_MCP_ENABLE_WRITE=1 后:
create_channel(guild_id="...", name="general", type=0)
edit_channel(channel_id="...", name="renamed", position=3)
create_role(guild_id="...", name="Mod", color=1752220, hoist=True)
edit_role(guild_id="...", role_id="...", name="Senior Mod", permissions="8")
edit_guild(guild_id="...", name="New Server Name")
delete_channel(channel_id="...")
create_message(channel_id="...", content="hello from discord-mcp")
测试
.venv/Scripts/python.exe -m pytest -q
共 163 项测试:162 项单元测试(使用 respx 模拟,不进行真实网络请求)+ 1 项在线冒烟测试(受条件限制,见下文)。所有 12 个工具的正常路径均已覆盖;上述每种错误类型都在客户端层(test_client.py)和路由规范化层(test_routes.py)进行测试;test_server.py 会对每个 @mcp.tool 异步包装函数执行 asyncio.run(),并猴子补丁替换 routes 模块,而不只是检查 list_tools()。因此,server.py 与 routes.py 之间的参数名称不匹配或关键字参数丢失都会被发现。
写入工具的测试覆盖尤其充分(test_config.py + test_routes.py):针对 7 个写入工具,分别断言关闭开关时会拒绝请求,且 len(respx.calls) == 0(证明根本不会尝试调用 Discord);打开开关时,通过模拟 Discord 响应测试成功路径(包括请求正文断言);打开开关时,测试 Discord API 错误的透传(权限、未找到、速率限制、网络、5xx);并对 delete_channel 进行专门的双重检查——既确认它与其他写入工具一样会拒绝请求,也确认任何看似合理但错误的环境变量名称(例如 DISCORD_MCP_ENABLE_DELETE 等)都无法意外启用它。
create_message 还专门进行了内容长度边界测试(test_client.py/test_routes.py):恰好 2000 个字符时会被接受(1 次 respx 调用);2001 个字符和空字符串都会在本地被拒绝,并返回 validation_error,且 respx 调用数为零。
在线冒烟测试
tests/test_live_smoke.py::test_live_list_channels_and_roles_against_a_real_guild 受 DISCORD_MCP_LIVE=1 和 DISCORD_MCP_SMOKE_GUILD_ID 控制(默认未设置——两者都必须设置才能运行),并会针对该服务器调用 Discord 真实 API 的 list_channels/list_roles。如果当前进程的环境中已设置 DISCORD_BOT_TOKEN,测试会从中读取;否则,如果 DISCORD_MCP_SMOKE_ENV_PATH 指向外部 .env 文件,就会从该文件加载(只加载一次,绝不会打印或记录其值)——此仓库没有自己的 .env,也永远不会有;真实令牌存放在由运维人员单独管理的位置。测试始终只读——没有任何写入工具的在线冒烟测试。
DISCORD_MCP_LIVE=1 DISCORD_MCP_SMOKE_GUILD_ID=<your-guild-id> \
DISCORD_MCP_SMOKE_ENV_PATH=<path-to-.env-with-token> \
.venv/Scripts/python.exe -m pytest tests/test_live_smoke.py -v
截至撰写本文时,已确认其在真实服务器上通过:真实频道、角色和分类均以非空列表形式返回。
安装 / 连接
python -m venv .venv
.venv/Scripts/python.exe -m pip install -e ".[test]"
在 ~/.claude.json 的 mcpServers.discord-mcp 下注册为 stdio 服务,并通过绝对路径调用 run_server.py(无需设置 cwd——入口会将自身目录添加到 sys.path)。真实注册配置中的 env: {} 不包含真实机器人令牌;如果需要经过身份验证的调用,请在实际启动服务的进程中设置 DISCORD_BOT_TOKEN;如果要启用写入工具组,请设置 DISCORD_MCP_ENABLE_WRITE=1。在真实注册配置中启用写入开关是运维人员有意执行的独立操作,并非此仓库的默认配置。
握手检查
.venv/Scripts/python.exe scripts/list_tools.py
无需启动传输,即可打印已注册的 12 个工具名称——纯粹的自省功能,可用于验证服务在任何更改后是否能正常接线。
不在范围内
- 分页 /
list_members(见上文“诚实说明的限制”)。 - 对受速率限制(429)的请求进行重试——会以结构化错误形式呈现,并交由调用方处理。
- 确定无正文的 403 所有可能的原因(见上文对
cloudflare_blocked启发式判断的诚实说明)。 - 上述 7 个工具以外的任何写入操作(例如成员角色分配、踢出或封禁,消息编辑/删除,嵌入内容/附件/表情回应,Webhook 管理)——
create_message仅支持纯文本content,不支持嵌入内容、文件或表情回应。该作者的其他作品中另有一个范围更广、结构不同且支持写入的项目,包含类似apply_server_structure的工具,但不在此处的范围内。
商业支持
由 Jaimen Bell 维护。如需生产环境中的 MCP 集成、自定义服务或 Agent 可靠性相关工作,请访问 jaimenbell.dev。
正在构建自己的 MCP 服务?MCP Starter Kit 包含模板、构建指南,以及发布此项目过程中的实战经验。
mcp-name: io.github.jaimenbell/discord-mcp
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。