mcpField
D
discord-mcp

discord-mcp

本地服务
github.com/jaimenbell通信协作111 周前更新

关于 discord-mcp

discord-mcp 是供 Agent 和其他 MCP 客户端调用 Discord REST API 的参考 MCP 服务。它默认提供 5 个读取工具,7 个写入工具则需通过环境变量显式启用。

主要功能

  1. 01读取服务器频道、角色和分类
  2. 02检查频道权限覆盖设置
  3. 03获取成员的角色 ID
  4. 04创建和修改频道、角色及服务器信息
  5. 05发布消息和删除频道
  6. 06结构化错误及写入开关

适用场景

  1. 01查看服务器的频道和角色配置
  2. 02查询成员已分配的角色
  3. 03启用写入后管理服务器结构
  4. 04启用写入工具后发布频道消息

MCP 相关

将 Discord REST API 操作作为 MCP 工具提供。读取工具始终可用;写入工具需要设置 DISCORD_MCP_ENABLE_WRITE=1。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"jaimenbell-discord": {
"command": "uvx",
"args": [
"jaimenbell-discord-mcp"
]
}
}
}
启动命令uvx jaimenbell-discord-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypijaimenbell-discord-mcp0.1.1

工具

12

收录的工具列表 · 更新于 2 周前

参数类型必填说明
guild_idstring是

详细介绍

discord-mcp

License: MIT Tests

一个基于真实 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_channelsGET /guilds/{guild_id}/channels服务器中的所有频道(所有类型)
list_rolesGET /guilds/{guild_id}/roles服务器中的所有身份组
list_categoriesGET /guilds/{guild_id}/channels(经过筛选)仅限分类频道(Discord 类型 4)——Discord 没有专门用于获取分类频道的端点,因此会在客户端筛选相同的频道负载
get_channel_permission_overwritesGET /channels/{channel_id}(permission_overwrites 字段)单个频道上设置的身份组/成员允许和拒绝权限位字段
get_member_rolesGET /guilds/{guild_id}/members/{member_id}(roles 字段)当前分配给某个服务器成员的身份组 ID
写入(由 DISCORD_MCP_ENABLE_WRITE=1 控制,默认关闭)
工具Discord 端点用途
create_channelPOST /guilds/{guild_id}/channels创建文字/语音/分类频道
edit_channelPATCH /channels/{channel_id}重命名/更改主题/更改所属分类/调整现有频道顺序
create_rolePOST /guilds/{guild_id}/roles创建身份组
edit_rolePATCH /guilds/{guild_id}/roles/{role_id}编辑现有身份组
edit_guildPATCH /guilds/{guild_id}更新服务器级标识信息(名称/图标/横幅/描述)
delete_channelDELETE /channels/{channel_id}不可逆操作。 删除频道
create_messagePOST /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_error 403 无法区分——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_S10.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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。