内容
# 天桥
天桥通过 NapCat 的 OneBot v11 反向 WebSocket 连接 QQ,并可选地通过 Telegram Bot API 连接 Telegram,到一个固定的 Astral 代码应用服务器会话。它还暴露了 MCP 工具,使代理可以回复聊天平台,发送文件,提及人员,撤回/删除消息,并在需要时获取最近的对话上下文。
本项目适用于自托管的个人或团队自动化。它与 QQ,腾讯,NapCat,OneBot,Telegram 或 Astral Code 无关。
## 功能
- 通过 NapCat 的 OneBot v11 接收 QQ 私人消息和群消息。
- 通过长期轮询接收 Telegram 私人消息,群消息,超级群消息和频道帖子。
- 将每个接受的消息路由到一个配置的 Astral 应用服务器线程。
- 仅当在群消息中被提及,回复或聊天配置为始终触发时触发群消息。
- 触发来自配置的私人 QQ 或 Telegram 用户/聊天的每个消息。
- 在 SQLite 中本地存储允许的对话历史记录,以便后续 MCP 查询。
- 包括紧凑的上下文:平台,聊天/群名称/ ID,发送者 ID,昵称,用户名/卡片,消息 ID,回复 ID,触发类型,未读计数和附件元数据。
- 支持应用服务器 `turn/steer`,当固定的 Astral 线程已经有活动时。
- 暴露 Streamable HTTP 或 stdio MCP 工具,用于 QQ 和 Telegram 回复,历史记录,媒体,文件,提及和回复特定消息 ID。
- 接受通用外部事件 webhook 并将它们转发到固定的 Astral 会话。
- 暴露一个只读 Web UI,用于连接状态,路由,最近的消息,最近的对话和最近的桥接日志。
- 在外出 QQ 发送操作前添加 3-5 秒的随机延迟。
## 要求
- Node.js 26 或更新版本。桥使用 `node:sqlite`。
- pnpm 10 或更新版本。
- 运行中的 Astral 代码应用服务器。
- 配置了 OneBot v11 反向 WebSocket 的 NapCat。
- 可选:启用 Telegram 支持时,从 BotFather 获取的 Telegram 机器人令牌。
## 快速开始
```bash
pnpm install
cp examples/config.example.json config.json
pnpm dev -- --config ./config.json
```
构建生产环境:
```bash
pnpm build
pnpm start -- --config ./config.json
```
将 NapCat 的 OneBot v11 反向 WebSocket 指向:
```text
ws://127.0.0.1:6701/onebot/v11/ws
```
如果桥在 Docker 或另一个主机上运行,请将主机和端口替换为 NapCat 可访问的地址,例如:
```text
ws://bridge:6701/onebot/v11/ws
```
## 配置
从 `examples/config.example.json` 开始:
```json
{
"onebot": {
"host": "127.0.0.1",
"port": 6701,
"path": "/onebot/v11/ws",
"accessToken": null,
"actionTimeoutMs": 10000
},
"mcp": {
"transport": "stdio",
"host": "127.0.0.1",
"port": 6710,
"path": "/mcp"
},
"astral": {
"appServerUrl": "ws://127.0.0.1:4222",
"authToken": null,
"threadId": "REPLACE_WITH_FIXED_ASTRAL_THREAD_ID",
"cwd": null,
"modelProvider": null,
"model": null,
"includeImageInputs": false
},
"qq": {
"botUserId": "REPLACE_WITH_BOT_QQ",
"allowedGroupIds": ["REPLACE_GROUP_ID"],
"alwaysTriggerGroupIds": [],
"allowedPrivateUserIds": ["REPLACE_USER_ID"],
"triggerKeywords": ["astral", "bot"],
"recordUntriggered": true
},
"telegram": {
"enabled": false,
"botToken": "REPLACE_WITH_TELEGRAM_BOT_TOKEN",
"botUsername": "REPLACE_WITH_TELEGRAM_BOT_USERNAME",
"allowedChatIds": ["REPLACE_TELEGRAM_CHAT_ID"],
"alwaysTriggerChatIds": [],
"triggerKeywords": ["astral", "bot"],
"recordUntriggered": true,
"pollTimeoutSeconds": 25,
"pollIntervalMs": 1000,
"apiBaseUrl": "https://api.telegram.org"
},
"tts": {
"enabled": false,
"apiKey": "REPLACE_WITH_TTS_API_KEY",
"baseUrl": "https://api.xiaomimimo.com/v1",
"model": "mimo-v2.5-tts",
"voice": "mimo_default",
"format": "wav",
"timeoutMs": 60000
},
"externalEvents": {
"enabled": true,
"path": "/api/events",
"authToken": "REPLACE_WITH_EVENT_API_TOKEN",
"maxBodyBytes": 65536,
"debounceMs": 2000,
"maxBatchEvents": 20,
"maxBatchBodyChars": 6000
},
"storage": {
"dbPath": "./data/astral-bridge.db",
"mediaDir": "./media",
"downloadMedia": false
}
}
```
环境变量覆盖:
| 变量 | 描述 |
| --- | --- |
| `ASTRAL_BRIDGE_CONFIG` | JSON 配置文件的路径。 |
| `ASTRAL_BRIDGE_APP_SERVER_URL` | Astral 应用服务器 WebSocket URL。 |
| `ASTRAL_BRIDGE_APP_SERVER_AUTH_TOKEN` | Astral 应用服务器的 Bearer 令牌。 |
| `ASTRAL_BRIDGE_THREAD_ID` | 固定的 Astral 线程/会话 ID。 |
| `ASTRAL_BRIDGE_MODEL_PROVIDER` | 新的回合和恢复的固定线程设置的模型提供者可选覆盖。 |
| `ASTRAL_BRIDGE_MODEL` | 新的回合和恢复的固定线程设置的模型可选覆盖。 |
| `ASTRAL_BRIDGE_BOT_QQ` | 机器人 QQ 用户 ID。 |
| `ASTRAL_BRIDGE_ALLOWED_GROUP_IDS` | 以逗号分隔的允许的群 ID。 |
| `ASTRAL_BRIDGE_ALWAYS_TRIGGER_GROUP_IDS` | 以逗号分隔的群 ID,其中每个非机器人消息都被转发到 Astral。 |
| `ASTRAL_BRIDGE_ALLOWED_PRIVATE_USER_IDS` | 以逗号分隔的允许的私人用户 ID。 |
| `ASTRAL_BRIDGE_TRIGGER_KEYWORDS` | 以逗号分隔的 QQ 触发关键词。任何允许的群/私人消息中包含一个关键词(不区分大小写)将被转发到 Astral。 |
| `ASTRAL_BRIDGE_TELEGRAM_ENABLED` | 启用 Telegram 长期轮询。 |
| `ASTRAL_BRIDGE_TELEGRAM_BOT_TOKEN` | 来自 BotFather 的 Telegram 机器人令牌。 |
| `ASTRAL_BRIDGE_TELEGRAM_BOT_USERNAME` | 可选的机器人用户名,不带或带 `@`; `getMe` 也会在运行时发现它。 |
| `ASTRAL_BRIDGE_TELEGRAM_ALLOWED_CHAT_IDS` | 以逗号分隔的 Telegram `chat.id` 允许列表。使用 `/chatid` 获取它。 |
| `ASTRAL_BRIDGE_TELEGRAM_ALWAYS_TRIGGER_CHAT_IDS` | 以逗号分隔的 Telegram 聊天 ID,其中每个非机器人消息都被转发到 Astral。 |
| `ASTRAL_BRIDGE_TELEGRAM_TRIGGER_KEYWORDS` | 以逗号分隔的 Telegram 触发关键词。任何允许的聊天消息中包含一个关键词(不区分大小写)将被转发到 Astral。 |
| `ASTRAL_BRIDGE_TELEGRAM_RECORD_UNTRIGGERED` | 存储非触发的 Telegram 消息来自允许的聊天。 |
| `ASTRAL_BRIDGE_TELEGRAM_POLL_TIMEOUT_SECONDS` | Telegram `getUpdates` 长期轮询超时。 |
| `ASTRAL_BRIDGE_TELEGRAM_POLL_INTERVAL_MS` | Telegram 失败轮询后重试前的延迟。 |
| `ASTRAL_BRIDGE_TTS_ENABLED` | 启用 TTS 支持的 QQ/Telegram 语音消息工具。 |
| `ASTRAL_BRIDGE_TTS_API_KEY` | TTS 聊天完成端点使用的 API 密钥。 |
| `ASTRAL_BRIDGE_TTS_BASE_URL` | TTS API 基本 URL,默认为 `https://api.xiaomimimo.com/v1`。 |
| `ASTRAL_BRIDGE_TTS_MODEL` | TTS 模型,默认为 `mimo-v2.5-tts`。 |
| `ASTRAL_BRIDGE_TTS_VOICE` | 配置的机器人语音,对 MCP 工具架构隐藏。 |
| `ASTRAL_BRIDGE_TTS_TIMEOUT_MS` | TTS 请求超时(毫秒)。 |
| `ASTRAL_BRIDGE_MCP_TRANSPORT` | `stdio` 或 `http`。 |
| `ASTRAL_BRIDGE_EVENT_API_ENABLED` | 启用或禁用外部事件 API。 |
| `ASTRAL_BRIDGE_EVENT_API_PATH` | 外部事件 API 路径,默认为 `/api/events`。 |
| `ASTRAL_BRIDGE_EVENT_API_TOKEN` | 外部事件 API 需要的可选 Bearer 令牌。 |
| `ASTRAL_BRIDGE_EVENT_API_DEBOUNCE_MS` | 合并值得注意的外部事件以转发到 Astral 的窗口。 |
| `ASTRAL_BRIDGE_EVENT_API_MAX_BATCH_EVENTS` | 一个合并的 Astral 回合中包含的最大外部事件数。额外的事件在同一窗口中被计数和省略。 |
| `ASTRAL_BRIDGE_EVENT_API_MAX_BATCH_BODY_CHARS` | 发送到 Astral 的最大合并事件正文字符数。较长的批次将被截断。 |
`recordUntriggered` 控制是否存储来自允许的对话的非触发消息。启用它可以让代理在不转发每个群消息到 Astral 的情况下获取周围的上下文。
Telegram 使用长期轮询。启动时,桥接调用 `deleteWebhook`,以便 Telegram 允许 `getUpdates`。在私人聊天,群聊,超级群聊或主题中发送 `/chatid` 以获取确切的 `chat_id`; 此命令即使在聊天未被列入白名单之前也有效。
## Web UI
当启用 HTTP MCP 时,打开:
```http
GET /ui
```
仪表板轮询 `GET /api/dashboard/state` 并显示 NapCat/Astral 连接状态,活动 Astral 回合 ID(如果正在运行),配置的路由,外部事件 API 状态,最近的对话,最近存储的消息和最近的桥接日志。
## Astral MCP 设置
对于 stdio MCP,请先构建并将 Astral 指向编译后的入口点:
```toml
[mcp_servers.qq]
command = "node"
args = ["/path/to/astral-bridge/dist/index.js", "--config", "/path/to/astral-bridge/config.json"]
[mcp_servers.telegram]
command = "node"
args = ["/path/to/astral-bridge/dist/index.js", "--config", "/path/to/astral-bridge/config.json"]
```
直接使用 `node` 而不是包管理器包装器。stdio MCP 要求 stdout 只包含 JSON-RPC 消息。
对于容器或多进程部署,运行 HTTP MCP:
```json
{
"mcp": {
"transport": "http",
"host": "0.0.0.0",
"port": 6710,
"path": "/mcp"
}
}
```
然后配置 Astral:
```toml
[mcp_servers.qq]
url = "http://bridge:6710/mcp"
[mcp_servers.telegram]
url = "http://bridge:6710/mcp"
```
桥接器通过同一个 MCP 端点暴露 QQ 和 Telegram 工具。将在 `qq` 和 `telegram` 下注册端点为代理提供自然的工具名称,例如 `mcp__qq__qq_send_group_message` 和 `mcp__telegram__telegram_send_message`。
## MCP 工具
| QQ 工具 | 目的 |
| --- | --- |
| `qq_send_group_message` | 发送带有文本,图像,有序部分,提及或回复目标的群消息。 |
| `qq_send_private_message` | 发送带有文本,图像,有序部分或回复目标的私人消息。 |
| `qq_send_group_voice` | 生成 TTS 音频并将其作为真正的 QQ 群语音消息发送。 |
| `qq_send_private_voice` | 生成 TTS 音频并将其作为真正的 QQ 私人语音消息发送。 |
| `qq_send_group_file` | 将本地文件或 URL 上传到 QQ 群。 |
| `qq_send_private_file` | 将本地文件或 URL 上传到 QQ 私人聊天。 |
| `qq_get_unread_messages` | 返回最新的 Astral 提示计数的未读批次。 |
| `qq_get_recent_messages` | 返回一个群或私人对话的最近存储的消息。 |
| `qq_get_message` | 通过 OneBot `message_id` 返回存储的消息。 |
| `qq_get_conversation_state` | 返回桥接状态和一个对话的计数。 |
| `qq_download_media` | 将存储的图像/文件附件下载到本地媒体缓存中。 |
| `qq_set_reaction` | 通过 OneBot `message_id` 和 QQ `emoji_id` 对存储的 QQ 群消息做出反应。QQ 反应仅在群聊中有效;工具描述列出了常见的 `emoji_id` 值。 |
| `qq_group_admin_help` | 解释分组的 QQ 管理工具和操作。 |
| `qq_group_member_admin` | 踢,mute/unmute,设置管理员,设置卡片,设置特殊标题,或列出静音成员。 |
| `qq_group_request_admin` | 读取或处理群加入/邀请请求。 |
| `qq_group_settings_admin` | 更改群名称/头像,全群静音,或使机器人离开群。 |
| `qq_group_message_admin` | 召回消息,管理精华消息,检查 @all 配额,或标记群聊已读。 |
| `qq_group_notice_admin` | 发送,列出或删除群公告。 |
| `qq_group_file_admin` | 管理群文件和文件夹。 |
| `qq_group_info_admin` | 读取群聊,成员和荣誉信息。 |
| Telegram 工具 | 目的 |
| --- | --- |
| `telegram_send_message` | 发送带有有序文本/提及部分,可选回复目标和可选主题线程 ID 的文本。 |
| `telegram_send_rich_message` | 发送带有 Telegram Rich Message HTML 或 Markdown 的结构化富文本。 |
| `telegram_send_file` | 发送本地路径,Telegram `file_id` 或 HTTP URL 作为文档/文件。图像也故意使用此工具。 |
| `telegram_send_voice` | 生成 TTS 音频并将其作为真正的 Telegram 语音消息发送。 |
| `telegram_delete_message` | 当 Telegram 权限允许时删除/召回消息。需要 `confirm:true`。 |
| `telegram_set_reaction` | 用一个标准反应表情符号对 Telegram 群或私人消息做出反应;工具描述列出了允许的表情符号值。 |
| `telegram_get_unread_messages` | 返回最新的 Astral 提示计数的未读批次。 |
| `telegram_get_recent_messages` | 返回 Telegram 聊天的最近存储消息。 |
| `telegram_get_message` | 通过 `message_id` 返回存储的 Telegram 消息。 |
| `telegram_get_conversation_state` | 返回 Telegram 聊天的桥接状态和计数。 |
| `telegram_download_media` | 将存储的 Telegram 附件下载到本地媒体缓存中。 |
### 群聊管理
群聊管理工具分组以保持 MCP 工具列表较小。调用 `qq_group_admin_help` 以发现操作名称和必填字段。解散群聊故意不实现;`leave_group` 仅使机器人离开群聊。
所有群聊特定管理工具拒绝 `group_id` 值超出 `qq.allowedGroupIds` 配置的群 ID。修改群聊状态的操作需要 `confirm: true`; 仅在用户明确要求操作后使用。
### 提及,图像和回复
在群消息中,当需要群消息的精确排序时使用 `parts`:
```json
[
{ "type": "text", "text": "请 " },
{ "type": "at", "user_id": "TARGET_QQ" },
{ "type": "text", "text": " 看一下 " },
{ "type": "image", "file": "/workspace/result.png" }
]
```
回复特定 QQ 消息时,请传入来自 inbound 提示的 OneBot `message_id` 或历史记录工具之一。
图像在输出消息中使用 OneBot `image` 分段。非图像文件使用 NapCat 兼容的 `upload_group_file` 和 `upload_private_file` 操作。
Telegram 文本消息使用有序 `parts` 提及:
```json
[
{ "type": "text", "text": "请 " },
{ "type": "mention", "username": "alice" },
{ "type": "text", "text": " 看一下;也同步给 " },
{ "type": "mention", "user_id": "123456789", "text": "Bob" }
]
```
使用 `telegram_send_rich_message` 时,Telegram 回复需要结构化格式,例如标题,列表,表格,可折叠的详细信息,代码块或公式。传递 `html` 或 `markdown`:
```json
{
"chat_id": "-1001234567890",
"html": "<h2>Summary</h2><ul><li>Done</li><li>Next step</li></ul>",
"reply_to_message_id": "123",
"message_thread_id": "456"
}
```
对于 Telegram 文件,请调用 `telegram_send_file` 来发送图片和非图片。 这会通过 Telegram `sendDocument` 发送,从而保留原始文件质量。 Telegram 富文本媒体块适用于 HTTP/HTTPS URL;本地 `/workspace` 和 `/app/media` 文件仍应使用 `telegram_send_file` 发送:
```json
{
"chat_id": "-1001234567890",
"file": "/workspace/result.png",
"caption": "结果图",
"reply_to_message_id": "123",
"message_thread_id": "456"
}
```
## 消息路由
桥接器仅转发来自配置的 QQ 目标或 Telegram 聊天 ID 的消息:
- 群消息:当机器人被提及或消息回复机器人消息时转发。当机器人是消息的发送者时,不转发机器人消息。
- 关键词消息:在任何允许的 QQ/Telegram 群或私人聊天中,包含任何配置的触发关键词的消息将被转发。关键词匹配不区分大小写。
- 始终触发群消息:对于 `qq.alwaysTriggerGroupIds` 中的群 ID,每个非机器人消息都被转发,甚至没有 `@mention` 或回复。
- 私人消息:转发来自配置的私人用户的所有消息。
- 其他允许的对话消息:可选存储,当 `recordUntriggered` 为真时,但不转发到 Astral。
- Telegram `/chatid`:始终直接回答,以便您可以发现当前的 Telegram `chat.id` 然后编辑允许列表。
- Telegram 群/超级群/频道消息:转发当机器人被提及,消息回复机器人消息,或聊天 ID 在 `telegram.alwaysTriggerChatIds` 中。
- Telegram 私人消息:转发来自配置的私人聊天 ID 的所有消息。
- `/stop`:在任何允许的 QQ/Telegram 群或私人聊天中,中断当前的 Astral 回合并在同一对话中回复。
每个转发的回合都包含一个 `conversation_unread` 部分。`unread_count` 是同一群聊/私人对话中自上一 Astral 提示以来存储的消息数量,包括当前的触发消息。代理可以调用 `qq_get_unread_messages` 或 `telegram_get_unread_messages` 当该上下文有帮助时;它不必为每个消息调用。
## 外部事件 API
当启用 HTTP MCP 时,同一个 HTTP 服务器可以接受通用外部事件:
```http
GET /api/events/schema
```
返回带有示例和 curl 用法的机器可读 OpenAPI 3.1 架构。
```http
POST /api/events
Authorization: Bearer REPLACE_WITH_EVENT_API_TOKEN
Content-Type: application/json
```
```json
{
"source": "minecraft:survival-main",
"event_type": "player_join",
"title": "玩家加入",
"body": "Steve 加入了服务器",
"actor": { "id": "uuid", "name": "Steve" },
"metadata": { "world": "world", "x": 120, "y": 64, "z": -33 }
}
```
桥接器会将值得注意的事件队列合并,短暂的突发事件合并为一个有界的 Astral 回合,并使用与 QQ 消息相同的队列将合并的事件提交给固定的 Astral 线程。设置 `wants_agent_attention` 为 `false` 以验证并接受事件,而不将其转发到 Astral。批处理由 `externalEvents.debounceMs`、`externalEvents.maxBatchEvents` 和 `externalEvents.maxBatchBodyChars` 控制。
请求字段:
| 字段 | 是否必填 | 描述 |
| --- | --- | --- |
| `source` | 是 | 系统或集成名称,例如 `minecraft:survival-main` 或 `monitoring`。 |
| `event_type` / `type` | 否 | 事件类型。默认为 `event`。 |
| `title` | 否 | 简短标题。 |
| `body` / `text` | 否 | 主要事件文本。 |
| `severity` | 否 | 严重性标签。默认为 `info`。 |
| `actor` | 否 | 导致事件的实体。 |
| `metadata` | 否 | 结构化事件详情。 |
| `dedupe_key` | 否 | 调用者提供的可选稳定键。 |
| `occurred_at` | 否 | ISO 时间戳、Unix 秒或 Unix 毫秒。 |
| `wants_agent_attention` | 否 | 默认为 `true`;设置为 `false` 表示仅验证事件。 |
| `id` | 否 | 调用者提供的事件 ID。如果省略,将生成 UUID。 |
成功的请求返回 `202 Accepted`,并带有 `{ ok, accepted_for_astral, event }`。当配置了 `externalEvents.authToken` 时,未授权的请求返回 `401`。
## Astral 应用服务器行为
桥接器通过 WebSocket 与 Astral 应用服务器通信,并使用:
- `initialize`
- `thread/resume`
- `turn/start`
- `turn/steer` 当固定线程已经有活动回合时
启动回合时,桥接器请求 `approvalPolicy = "never"` 和 `sandboxPolicy = { type = "dangerFullAccess" }`。如果使用此模式,请在容器、VM 或主机级别隔离 Astral,并仅挂载代理所需的目录。
服务器批准执行命令和文件更改的请求默认会被取消。QQ 发送应通过 MCP 工具和 Astral MCP 工具批准设置,而不是通过桥接器批准任意应用服务器操作。
## Docker 和 OrbStack
通用桥接器映像由 `Dockerfile` 定义。
OrbStack 部署模板位于 `deploy/orbstack` 下。它们包括:
- `bridge`:OneBot 反向 WebSocket 加 Streamable HTTP MCP。
- `astral-code`:基于 Ubuntu 的 Astral 应用服务器运行时,带有映射的配置、 二进制文件、源代码、工作区和构建缓存目录。
- `napcat`:NapCat Docker 服务。
复制 `deploy/orbstack/.env.example` 到 `.env`,填写本地值,然后阅读 `deploy/orbstack/README.md` 中的中文部署指南,然后运行 compose 堆栈。它涵盖了所需的 QQ/Telegram/Astral 设置、持久卷、内存存储和内存 phase2 沙盒选项。
## 安全注意事项
- 不要提交真实的 QQ ID、应用服务器令牌、API 密钥、NapCat WebUI 令牌、SQLite 数据库、下载的媒体或本地配置文件。
- 除非您故意需要 LAN 访问权限,否则请将服务绑定到环回接口。
- 应用服务器的危险完全访问模式非常强大。请使用专用容器或主机帐户,并仅挂载代理所需的 工作区/配置 目录。
- 在将机器人暴露给繁忙的聊天室之前,请查看允许的群组和私人用户。
## 开发
```bash
pnpm install
pnpm typecheck
pnpm build
```
项目布局:
```text
src/ 桥接器源代码
examples/ 示例 JSON 配置
deploy/orbstack/ 自托管 OrbStack 部署模板
data/ 运行时 SQLite 状态,忽略 git
media/ 运行时媒体缓存,忽略 git
```
## 许可证
根据 Apache License, Version 2.0 授权。参见 `LICENSE`。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器