Skip to main content
Glama
eam2539

Discord MCP Bridge

by eam2539
README.md
# Discord → Local MCP Bridge

把**当前 Discord 客户端里你自己的账号已经能看到的消息和频道**,以安全、只读的方式暴露给本机 MCP Server,让本地 AI Agent(Claude / ChatGPT / 任意 MCP Client)可以搜索和读取 Discord 内容。

```
Discord 客户端(已登录)
   └── BetterDiscord 插件(数据采集端)
         ├── Flux 事件监听(MESSAGE_CREATE / UPDATE / DELETE / DELETE_BULK)
         ├── normalize → 去重 → 防抖/批量/背压队列
         └── WebSocket 客户端 ──→ 127.0.0.1:<port>(token 握手)
                                      └── MCP Server(Node.js,独立进程)
                                            ├── SQLite + FTS5 本地消息索引
                                            ├── 8 个只读 MCP tools(stdio)
                                            └── 供 AI 调用
```

## 核心原则(硬性约束)

- 不需要 Discord Bot,不需要把 Bot 邀请进服务器,不需要管理员权限
- **不读取、不保存、不发送 Discord User Token**,不模拟登录,不是 self-bot
- 不自动发送消息、不执行任何 Discord 用户操作(纯只读)
- 只读取当前客户端已允许当前用户访问的数据,绝不绕过 Discord 权限模型
- 所有通信只绑定 `127.0.0.1`,禁止 `0.0.0.0`,无任何公网回连
- 插件与 MCP Server 可独立启动/停止,任一方重启都能自动恢复

## 目录结构

```
discord-mcp-bridge/
├── plugin/        # BetterDiscord 插件(采集 + WS 客户端 + 设置页)
├── mcp-server/    # Node.js MCP Server(WS 网关 + SQLite/FTS5 + MCP tools)
└── shared/        # 双方共用的协议与类型定义
```

## 快速开始

### 1. 安装插件

```bash
cd plugin
npm install
npm run build        # 构建 + 自动安装到 ~/.config/BetterDiscord/plugins/
```

完全重启 Discord(或 Ctrl+R)。首次加载后,插件会:

- 生成 48 位随机 hex 的本地会话 token,写入
  `~/.config/BetterDiscord/plugins/DiscordMCPBridge.config.json` 的 `bridge.token`
- 开始增量索引当前客户端能看到的频道/服务器/消息(默认不含 DM)

### 2. 启动 MCP Server

```bash
cd mcp-server
npm install
npm run build
npm start
```

Server 会从上面同一个配置文件读取 token 和端口(默认 `8787`),双方自动配对,无需手动同步密钥。

### 3. 配置 MCP Client

**Claude Desktop**(`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "discord": {
      "command": "node",
      "args": ["/absolute/path/to/discord-mcp-bridge/mcp-server/dist/mcp-server/src/index.js"]
    }
  }
}
```

**Claude Code / 通用 stdio**:

```bash
claude mcp add discord -- node /absolute/path/to/discord-mcp-bridge/mcp-server/dist/mcp-server/src/index.js
```

可用参数:`--port <n>`、`--db <path>`、`--config <path>`;环境变量 `DISCORD_MCP_PORT`、`DISCORD_MCP_DB`、`DISCORD_MCP_RETENTION_DAYS` 同样生效。

## MCP Tools(全部只读)

| Tool | 说明 |
| --- | --- |
| `discord_list_guilds` | 当前用户可访问的服务器(优先实时读取,插件离线时回退本地索引) |
| `discord_list_channels` | 指定服务器下可见的频道(含 text/announcement/forum/thread/category) |
| `discord_get_channel` | 单个频道元数据 |
| `discord_get_loaded_messages` | 当前客户端已加载/缓存的消息(`loaded_only=true`,支持 before/after 游标) |
| `discord_search_messages` | **本地 SQLite FTS5 全文搜索**(关键词/多词/短语 + guild/channel/author/时间过滤) |
| `discord_get_message` | 单条消息 |
| `discord_get_thread` | Thread 元数据与本地可得的消息 |
| `discord_get_context` | 某条消息前后的上下文窗口 |

搜索结果统一标注 `"source": "local_cache"` / `"client_loaded"`,明确告诉 AI 数据来自本地索引,而非完整服务器历史。

## 安全说明

- 会话 token 仅存于本地配置文件,每次连接用 `timingSafeEqual` 比较,可随时在插件设置页 **Regenerate Token**(Server 逐连接重读 token,无需重启)
- WS 网关仅接受单一已认证客户端;浏览器 `Origin` 存在时必须匹配 Discord 域
- 数据库默认只存搜索所需字段(消息文本/作者名/时间戳/附件元数据等),不存 token、cookie、authorization 头或任何私密凭据
- 索引数据库位于 `~/.local/share/discord-mcp-bridge/index.db`(可配 `--db`),插件设置页提供 **Clear Local Index** 一键清除
- 保留期默认 90 天(可配),Server 启动与每日定时清理

## 已知限制

- **只有本地索引过的数据可搜索。** 如果你从未在客户端打开过某频道,数据库里就没有该频道的历史。这是设计使然:插件不扫描、也无法获得完整服务器历史
- 删除/编辑会跟随 Flux 事件增量同步;Server 离线期间插件会缓冲,重连后补推
- 中文等 CJK 查询自动回退为子串匹配(FTS5 unicode61 分词不含 CJK)
- Discord 内部模块(MessageStore 等)随官方更新可能变化;插件把所有 Webpack 查找集中在 `ModuleResolver`,找不到时会给出明确诊断(如 `MessageStore not found — Discord internal API may have changed.`)而不是崩溃
- `discord_get_loaded_messages` 只返回当前客户端缓存,翻旧历史请配合 `discord_search_messages`

## 测试

```bash
cd mcp-server
npm test    # 23 个用例:IPC 认证/重连/RPC、入库/删除/FTS/过滤/分页、tools 集成
```

## 故障排查

| 现象 | 处理 |
| --- | --- |
| Server 启动报 `No bridge token found` | 先让 Discord 带插件跑一次(生成 token),再启动 Server |
| 工具返回 `PLUGIN_NOT_CONNECTED` | Discord 未开 / 插件被禁用 / 端口不一致;在插件设置页查看连接状态 |
| 端口冲突 `Port 8787 is already in use` | 换 `--port`,并在插件设置页同步修改 Port |
| 模块找不到类诊断 | Ctrl+R 重载;仍失败说明 Discord 内部 API 变更,需更新 `ModuleResolver` |