Skip to main content
Glama
Reshwell
by Reshwell

OneBot Agent Bridge

把 QQ 的 OneBot v11 收发能力封装成 CLI、JSON HTTP 接口和 stdio MCP 工具。面向需要自己决定如何处理消息的 AI Agent,适配 NapCat / SnowLuma 的标准 OneBot 接口。

本项目参考 wechat-ilink-bridge 的 CLI + 本地状态 + MCP 设计,独立实现 QQ 传输层。微信版通过 iLink 长轮询收消息;QQ 版需要一个常驻进程接收 OneBot 事件,并把事件保存到 SQLite,再交给 Agent 拉取。

QQ ↔ NapCat / SnowLuma ↔ OneBot v11 ↔ qbridge 常驻进程
                                        ↕ SQLite 消息队列
                                    CLI / HTTP / MCP
                                        ↕
                                你的 AI Agent 或应用

部署后可以收发 QQ 消息。自动生成回复需要另一个 Agent 或应用调用这些工具;此桥接器不内置模型或模型 API Key。MCP 工具提供调用能力,本身不会唤醒关闭的客户端,也不会把 QQ 消息自动注入当前聊天。持续自动回复需要一个持续运行的消费循环,见 Agent 接入说明。

如果使用有独立 Computer 和定时任务的 Muse,见 Muse 拉取与定时查询说明。真实连接凭证需单独提供,不包含在这个仓库里。

首版范围

能力

状态

OneBot v11 正向通用 WebSocket

已实现,断线自动重连

OneBot v11 反向通用 WebSocket

已实现,支持 Bearer / query token

HTTP API + HTTP POST 事件

已实现,支持 HMAC-SHA1 签名验证

QQ 私聊 / 群聊收发文本

已实现

CQ 字符串 / 数组格式入站消息

已实现

@ / 引用回复 / 群聊只处理 @bot

已实现;默认只 @ 触发,普通消息仅作上下文

被 @ 时附带最近群聊上下文

已实现;默认此前 20 条,可配置 0–100

标准 OneBot 图片发送

已实现,本地图片转 base64

文件发送

扩展接口;要求后端支持和共享路径

入站图片

已实现按引用读取;MCP 原生图片 / CLI 保存图片 / HTTP base64

入站语音、视频、文件

仅返回类型和元数据,不下载 / 转写

本地消息历史、去重、处理确认

已实现,SQLite 持久化

发送 requestId 去重

已实现;无法确认的发送不会自动补发

CLI / stdio MCP / JSON HTTP

已实现

Docker / Compose 配置

已提供

多 QQ 账号

每个账号单独运行一个桥接进程和状态目录

QQ 扫码登录

由 NapCat / SnowLuma 完成

标准文本、图片不依赖 NapCat 专属接口。文件使用 upload_private_file / upload_group_file,不属于 OneBot v11 的标准 API,实际可用性由你部署的后端版本决定。

Related MCP server: SnowLuma Remote MCP Server

从源码运行

需要 Node.js 24+,使用内置 SQLite。

git clone https://github.com/Reshwell/onebot-agent-bridge.git
cd onebot-agent-bridge
npm ci
cp .env.example .env
mkdir -p shared

编辑 .env,至少设置这三项:

QBRIDGE_TOKEN=填入你自己的随机桥接密钥至少16字符
ONEBOT_WS_URL=ws://127.0.0.1:3001/
ONEBOT_ACCESS_TOKEN=填入NapCat或SnowLuma的OneBot密钥

QBRIDGE_TOKEN 用来保护本工具的 CLI / HTTP / MCP 接口;ONEBOT_ACCESS_TOKEN 是 QQ 协议端的密钥,两者用途不同。桥接器不会接受示例占位密钥。

然后启动常驻接收进程:

npm start

保持这个进程运行。在另一终端进入同一个目录:

node src/cli.js status
node src/cli.js fetch --wait 10000
node src/cli.js send --user 123456789 --text "收到" --request-id reply-001
node src/cli.js send --group 987654321 --text "群内回复" --request-id reply-002
node src/cli.js ack --ids 1,2

示例 QQ 号、群号和序号需要换成你自己的。status.data.accountReady: true 表示已经通过 get_login_info 校验当前 QQ 账号。没有连接成功时,检查 OneBot 服务是否开启、地址、端口和密钥是否一致。

可选安装本地全局命令:

npm install -g .
qbridge status
qbridge-mcp

这只是从本地源码安装;本项目尚未发布到 npm,不能直接使用 npx onebot-agent-bridge 从 registry 安装。

NapCat / SnowLuma 配置

首选正向 WebSocket。后端打开一个同时提供 API 和事件的通用 WebSocket 服务,把地址和 token 填入 .env。标准 OneBot 路径通常是 /,仅 /event 的连接无法调用 API;仅 /api 的连接无法收新消息。如果使用事件专用 WS,则还须配置 ONEBOT_HTTP_URL 作为 API 出口。

SnowLuma 文档默认 OneBot WS 端口为 3001;NapCat 使用你在管理界面里配置的 WS 端口,不必设成相同的数字。

使用反向 WebSocket 时设置:

ONEBOT_MODE=reverse
ONEBOT_ACCESS_TOKEN=与QQ协议端一致的密钥

然后在 NapCat / SnowLuma 填通用反向 WS 地址:

ws://桥接器地址:8787/onebot/v11/ws

首版只接受通用连接(X-Client-Role: Universal),不接受分开的 API / Event 双连接。反向连接由 OneBot 端负责重连。需要从另一台机器接入时,设置监听地址为 QBRIDGE_HOST=0.0.0.0,并让该机器能够访问配置的端口。

HTTP 方式设置:

ONEBOT_MODE=http
ONEBOT_HTTP_URL=http://QQ协议端地址:3000/
ONEBOT_ACCESS_TOKEN=QQ协议端密钥
ONEBOT_HTTP_SECRET=事件上报签名密钥

OneBot 事件上报地址设置为 http://桥接器地址:8787/events,使用相同签名 secret。若后端仅支持带 Bearer token 的 HTTP 事件,则留空 ONEBOT_HTTP_SECRET,用 ONEBOT_ACCESS_TOKEN 鉴权。HTTP API token 与 WS / 事件 token 不同时,通过 ONEBOT_HTTP_ACCESS_TOKEN 单独配置。

Docker 部署

先编辑 .env,准备 shared/。如果 QQ 协议端运行在宿主机,Docker 内的 127.0.0.1 指向容器,应改成:

ONEBOT_WS_URL=ws://host.docker.internal:3001/

QQ 协议端须监听容器能访问的地址。Docker Desktop 通常支持 host.docker.internal;在 Linux 上只监听 127.0.0.1 的后端通常不能通过 host-gateway 访问,需要改为可访问的监听地址。

docker compose up -d --build
docker compose logs -f qbridge
docker compose exec qbridge node src/cli.js status
docker compose exec qbridge node src/cli.js fetch --wait 10000

Compose 把本地 8787 映射到容器并用命名卷保存 SQLite,重建容器保留消息。QBRIDGE_URL 默认 http://127.0.0.1:8787,适用于容器内 CLI 和宿主机客户端。

如果 NapCat / SnowLuma 也在 Docker 中,把它和 qbridge 加入同一个 Docker network,并设置 ONEBOT_WS_URL=ws://napcat:端口/ 或 ws://snowluma:3001/。不同 Compose 项目之间需要显式共享 network;服务名不能自动跨 network 解析。

Compose 默认只把端口映射到宿主机回环地址。来自其他容器的反向 WS 可走共享 network 到 qbridge:8787;来自其他机器的反向 WS 需要按你的网络环境调整端口绑定。

接入 MCP

把 examples/mcp-config.json 中的绝对路径和桥接器 token 改成你的值。MCP 进程只充当工具客户端,常驻接收进程需要另行启动。

{
  "mcpServers": {
    "qq-bridge": {
      "command": "node",
      "args": ["/absolute/path/to/onebot-agent-bridge/src/mcp.js"],
      "env": {
        "QBRIDGE_URL": "http://127.0.0.1:8787",
        "QBRIDGE_TOKEN": "与常驻进程一致的桥接密钥"
      }
    }
  }
}

具体导入方式由你的 MCP 客户端决定。这里只提供通用 stdio 配置,不保证每个 AI 产品都允许本地 stdio MCP 或后台自动轮询。

工具包括 fetchMessages、getImage、ackMessages、sendText、sendImage、sendFile、listUsers、listGroups、listConversations、history、status、getDelivery。

消息处理与防重复

fetchMessages / qbridge fetch 读取待处理消息,不自动消费。回复完成后调用 ackMessages / qbridge ack。只取走消息就自动确认,可能在 Agent 中断时遗漏未处理内容。

{
  "ok": true,
  "data": {
    "messages": [
      {
        "seq": 1,
        "conversationId": "group:987654321",
        "messageId": "456",
        "userId": "123456789",
        "groupId": "987654321",
        "text": "你好",
        "pending": true
      }
    ],
    "ackIds": [1]
  }
}

群消息应回到 conversationId / groupId,而不是把发言者 userId 当成私聊目的地。每条回复使用稳定 requestId,例如 reply-10001-1:重试同一 ID 和内容不会发送第二遍。相同 ID 配不同内容会被拒绝。

如果返回 SEND_UNCERTAIN,表示断线、超时或响应异常导致无法确定消息是否已经发送。先在 QQ 检查,不要直接换一个 ID 重发。getDelivery 可查看本地记录,但不能证明远端最终是否收到。程序崩溃留下的 sending 记录同样需要人工核实。

同一消息队列默认面向一个消费循环;多个 Agent 同时读取会得到相同未确认消息,首版没有消费者租约。已确认及发送历史最多保留 QBRIDGE_MAX_HISTORY 条;未确认消息不会被这个上限清理。去重键和发送记录持续保留,需要为长期运行的状态目录预留磁盘空间。

群聊:只 @ 触发,同时保留上下文

默认配置:

QBRIDGE_GROUP_MENTION_ONLY=true
QBRIDGE_GROUP_CONTEXT_LIMIT=20

普通群消息保存在有界历史中,contextOnly: true、pending: false,不进入待处理队列,也不会结束 fetch --wait 的等待。明确 @ 当前 QQ 机器人的消息才会进入群聊待处理队列;@all 不算 @bot。私聊不受这个开关影响。

查询到群聊任务时,messages[i].context 会自动附带该条 @ 消息之前、同一群聊的最近 20 条消息(含本桥接器的已发送回复),按接收顺序排列。后续群聊不会混入这条任务的上下文,其他群也不会串入。QBRIDGE_GROUP_CONTEXT_LIMIT 可设 0–100,0 表示不附带上下文。正文和图片都可作为背景,但上下文不是新的回复任务,也不在本轮 ackIds 中。

QBRIDGE_ALLOWED_GROUPS 限定保存哪些群的消息;为完整上下文,这些群内其他发言者的消息也会保存为背景。QBRIDGE_ALLOWED_USERS 限定允许触发处理的发言者和私聊对象,不再阻止保留允许群内的其他发言者背景。建议部署时填写群白名单,避免收集无关群聊。

从 0.1.0 升级时,若旧 .env 写了 QBRIDGE_GROUP_MENTION_ONLY=false,必须改为 true;环境变量也可覆盖 .env。启动后,旧队列中不符合当前触发规则的普通群消息会降为上下文,保留历史而不继续作为任务。

桥接器只区分“待处理消息”和“背景”,不控制 Muse 的调度器。两分钟定时任务仍会按时运行;这一轮没有私聊或 @ 消息时,应安静结束,不因普通群聊发通知或自动回复。

OneBot v11 的事件没有可靠历史回放协议。桥接器离线期间的 QQ 消息、已经发出但未写入 SQLite 就断电的事件,不保证补齐。这个桥接器提供收到并落盘后的本地持久化,不承诺端到端恰好一次投递。

图片和文件

接收图片并让 AI 看图

fetchMessages、history 和群聊 context 中的图片附件会提供 imageRef,例如:

{"kind":"image","segmentIndex":1,"imageRef":{"seq":17,"attachmentIndex":0}}
  • MCP:调用 getImage({ seq: 17, attachmentIndex: 0 }),返回文字元数据 + 原生 MCP image 内容。客户端必须支持把图片交给有视觉能力的模型。

  • CLI / Muse Computer:执行下面的命令,把图片保存在消费端,再使用 Computer 的图片查看能力打开;不是让 AI 阅读一大段 base64。

  • HTTP:POST /rpc,请求 {"method":"getImage","params":{"seq":17,"attachmentIndex":0}},返回 mimeType、bytes、sha256 和 base64;应用需要将实际图片作为视觉输入交给模型。

mkdir -p work
node src/cli.js image --seq 17 --attachment 0 --output work/incoming-17.png

seq 和 attachmentIndex 应使用 imageRef 的实际值。CLI 输出保存后的绝对路径,已有文件不会被覆盖。读取图片不会自动确认消息;看图和回复完成后再 ack。桥接器不会自己生成图片描述或 OCR,也不能让纯文本模型拥有视觉能力。

读取时优先用 get_image 刷新后端图片信息,再使用 base64、QQ CDN 下载链接或明确配置的共享图片缓存。临时签名链接、后端文件路径不会在查询结果中返回。默认接受 PNG/JPEG/GIF/WebP,单张上限由 QBRIDGE_MAX_MEDIA_BYTES 控制(默认 10 MiB);网页、JSON、SVG 和超大内容会被拒绝。

下载只允许 QBRIDGE_IMAGE_ALLOWED_HOSTS 指定的域名及其子域(默认 QQ 图片 CDN);每次重定向和实际 DNS 地址均校验,默认拒绝回环、内网和保留地址,不携带 OneBot token、桥接 token 或 Cookie。仅对明确可信的 LAN 图片服务同时配置允许域名及 QBRIDGE_IMAGE_ALLOW_PRIVATE_NETWORK=true;不要为普通 QQ 图片关闭内网保护。

若后端仅返回本地文件路径(OneBot 标准允许这种情况),需要共享图片缓存目录并设置:

QBRIDGE_IMAGE_LOCAL_ROOT=/read-only-qq-images
ONEBOT_IMAGE_LOCAL_ROOT=/backend/cache/images

这是目录映射:后端 /backend/cache/images/a.png 对应桥接器 /read-only-qq-images/a.png。应只读挂载具体的图片缓存,不要配置成 / 或包含凭证的目录;越界路径和跳出目录的符号链接会被拒绝。SnowLuma 提供可用 URL 时通常不需要目录共享。

发送图片和文件

图片放进 shared/ 后发送,协议端无需访问桥接器本地路径,因为桥接器会转成 base64:

node src/cli.js send --user 123456789 --image photo.png --request-id image-001

普通文件上传使用后端扩展接口,后端读取 file 对应的文件路径。需要把同一个文件夹挂到两个容器,并用 ONEBOT_FILE_ROOT 配置后端看到的目录。例如两边都挂到 /shared:

docker compose exec qbridge node src/cli.js send --group 987654321 --file report.pdf --request-id file-001

仅桥接器能访问该文件、而 QQ 协议端看不到对应路径时,文件上传无法成功。sendImage / sendFile 的 filePath 都是桥接器服务端的路径,不是远程 MCP 客户端的路径。

HTTP JSON 接口

所有 Agent 操作统一为 POST /rpc,Bearer token 为 QBRIDGE_TOKEN:

POST /rpc
Authorization: Bearer YOUR_BRIDGE_TOKEN
Content-Type: application/json

{"method":"fetchMessages","params":{"limit":20,"waitMs":10000}}

方法名与 MCP 工具一致。GET /healthz 仅检查进程能否应答,不等同于 QQ 账号在线;连接状态应查询 status。如通过跨机器网络使用 HTTP,请按你的部署环境配置 TLS 或私有网络。

验证

npm test
npm run check
npm run pack:dry-run

测试使用模拟 OneBot 服务,覆盖三种传输、重连、签名、持久化、路由、发送去重、媒体发送和官方 MCP 客户端 stdio 调用。已通过真实 SnowLuma 正向 WS 的私聊收发联调,见 验证记录。其他后端、群聊、媒体和具体部署环境仍需按 真实环境验收清单 验证。

协议依据:OneBot v11、NapCat 请求接口、SnowLuma 接入指南。

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    Enables MCP hosts to bridge with QQ via NapCat/OneBot 11, allowing whitelisted private messages to reach an agent with full tool access while group mentions are answered by a sandboxed pure LLM without system access.
    8
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables mobile AI agents and remote clients to connect to SnowLuma OneBot v11 over Streamable HTTP/SSE, providing messaging, group management, reaction emojis, and full OneBot v11 action pass-through.
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables interaction with QQ through official bot APIs or personal accounts bridged via NapCat/OneBot v11, including status checks, target listing, bounded context retrieval, and plain-text sending to allowlisted conversations.
    5
    MIT