openclaw-mcp-bridge
by rainow
README.md
# openclaw-mcp-bridge
**OpenClaw as MCP** — expose OpenClaw agents to any MCP-compatible agent. Example: Qwen glasses → Qoder → OpenClaw.
把本机 OpenClaw 的 agent 暴露成标准 MCP 工具,任何支持 MCP 的客户端都能调用;典型链路是千问眼镜 → Qoder CN → OpenClaw。
## 功能
- 🎤 **语音控制**: 通过千问眼镜语音下发任务到 OpenClaw agent
- 🔀 **多 Agent 支持**: agent 白名单、别名、权限等级全部在 `config.json` 里配置
- 🎯 **智能路由**: 点名即执行;未点名时使用 `defaultAgent`(建议指向权限最小的 agent)
- 📊 **状态查询**: 实时查询任务状态、读取历史、取消任务
- 🔊 **语音优化**: 自动提取首句播报,长回复标记需要总结
- 🔒 **安全隔离**: 会话锁、限流、审计、无 token 泄漏
## 从零到能用
整条链路是这样的:
```
千问眼镜(语音)
→ Qoder CN 远程控制会话(跑在这台 Mac 上)
→ openclaw-mcp-bridge(stdio MCP)
→ openclaw CLI → OpenClaw Gateway → agent
```
### 1. 在 Mac 上装 Qoder CN
装 **Qoder CN 桌面版**,不是 Qoder 国际版——两者是独立产品,配置目录也不同(CN 是 `~/.qoder-cn/`,国际版是 `~/.qoder/`),本项目只在 CN 桌面端验证过。
登录后确认远程控制 / 远程会话可用:眼镜侧正是以这种会话的形式,把任务发到你这台 Mac 上执行。
会话必须在本机执行。如果 Qoder 把任务委派到云端,云端进程访问不到你本机的 stdio MCP,这条链路就断了。
### 2. 千问眼镜绑定 Qoder CN
在千问 App 或眼镜端按官方引导绑定 Qoder CN 账号(通常是扫码登录授权)。绑定后,你对眼镜说的话会变成一个 Qoder CN 远程控制会话。具体入口和按钮名称可能随版本变化,以千问和 Qoder 当前的引导为准。
绑好后先验证一次:对眼镜说一句话,确认 Qoder CN 里出现了对应会话,并且它是在这台 Mac 上执行的。
### 3. 在这台 Mac 上安装本项目
前置条件:
- Node.js ≥ 20(本项目在 v23 上测试)
- 本机已安装并登录 OpenClaw,且 Gateway 正在运行(`openclaw gateway status`)
- 桥接本身不需要任何 token:它复用 `openclaw` CLI 的本机认证
```bash
git clone https://github.com/rainow/openclaw-mcp-bridge.git
cd openclaw-mcp-bridge
npm install
cp config.example.json config.json # 然后按你的 agent 名单改 config.json
```
`config.json` 至少改这三处:`allowedAgents` 填 `openclaw agents list` 里真实存在的 agent ID;`aliases` 填语音里好念的名字;`defaultAgent` 建议指向权限最小的那个。
装完先手动跑一次,确认配置和 Gateway 都通:
```bash
npm start
```
看到 `[openclaw-mcp-bridge] Server started (OpenClaw …)` 就说明没问题,`Ctrl+C` 退出即可——正常使用时这个进程由 Qoder 拉起,不需要你手动常驻。
`config.json` 写的是你本人的 agent 名单、别名和权限,不要提交到版本库。缺少它时服务器会回退到 `config.example.json` 并在 stderr 提示。
### 4. 在 Qoder CN 里配置 MCP
设置 → 扩展(Extensions)→ 连接器(Connectors)→ 添加自定义 MCP,传输方式选 **stdio**:
- **名称**: `openclaw-mcp-bridge`
- **命令**: `node` 的绝对路径(如 `/opt/homebrew/bin/node`),不要只写 `node`
- **参数**: `/绝对路径/openclaw-mcp-bridge/server.mjs`
- **工作目录**: `/绝对路径/openclaw-mcp-bridge`
参数和工作目录都必须是绝对路径。保存后如果工具里没出现这 5 个工具,对该服务器执行一次 retry,或重开会话。改过 `server.mjs` / `lib/openclaw.mjs` 之后同理,运行中的进程不会热更新。
桥接进程由 Qoder 按需拉起,不需要、也不应该做成系统级自启动任务(stdio 服务器脱离父进程就没有意义)。需要常驻的是 OpenClaw Gateway,是否让它开机自启由你决定。
### 5. 验证
在 Qoder CN 的新会话里说"列出可用的 OpenClaw agent",或直接在眼镜上说"让助手看一下今天的安排"(把"助手"换成你 `aliases` 里的名字)。第一次调用会先读 MCP 工具目录,这是客户端的懒加载行为,正常现象。
### 为什么不用在每个会话里教它
这个桥接不需要你在每个会话里重复解释自己会什么,用法写在两个地方:
- **服务器级 `instructions`**:`initialize` 时就返回,告诉任意 MCP 客户端"先 `oc_agents` 拿本机 agent 名单,再 `oc_run` 派活,`running` 就 `oc_wait`,别重复下发"。
- **每个工具的 `description`**:把同一段协议拆开写进各自工具里,所以客户端只要列出工具就能读全用法。这两处是用法的主来源,改动时要一起更新,别只改其中一边。
实测差异(Qoder CN 桌面端 0.x,2026-10-22):服务器 `instructions` 会正常出现在 `initialize` 响应里,但 Qoder **不会把它注入会话上下文**;它注入的是"服务器名 + 工具名"的延迟目录。真正起作用的是工具 `description` —— 一个全新会话在完全没被提示的情况下,自己走 `mcp_list` / `mcp_get` 读到用法,再调 `oc_agents` 定位到本机的 agent,然后完成任务。所以配完就能用,但你依然可以直接说"让某某去做某事"。
## MCP 工具
### oc_agents
列出所有可用的 OpenClaw agent。
**参数**:
- `refresh` (boolean, 可选): 强制刷新缓存
**返回**:
```json
{
"openclawVersion": "2026.6.11",
"gatewayOk": true,
"agents": [...],
"defaultAgent": "main",
"cached": false
}
```
### oc_run
向指定 agent 下发任务。
**参数**:
- `message` (string, 必填): 任务消息
- `agent` (string, 可选): Agent 名字或 ID,默认取 `config.json` 的 `defaultAgent`
- `wait_seconds` (number, 可选): 同步等待秒数,默认 20,最大 120
- `idempotency_key` (string, 可选): 幂等键,5 秒内相同 key 不重复下发
**返回**:
```json
{
"status": "done",
"runId": "req-xxx",
"agentId": "main",
"sessionKey": "agent:main:glasses",
"reply": "任务回复内容",
"voice": {
"speak": "播报文本(≤150 字符)",
"kind": "text",
"needsSummary": false,
"textLength": 100
},
"replyTruncated": false
}
```
### oc_wait
查询或等待任务状态。
**参数**:
- `runId` (string, 必填): 任务 runId
- `timeout_seconds` (number, 可选): 等待秒数,0 表示立即返回
**返回**:
```json
{
"status": "done",
"runId": "req-xxx",
"stopReason": "stop",
"startedAt": 1700000000000,
"endedAt": 1700000005000,
"voice": {...}
}
```
### oc_last
读取指定 agent 的 glasses 会话历史。
**参数**:
- `agent` (string, 可选): Agent 名字或 ID,默认取 `config.json` 的 `defaultAgent`
- `limit` (number, 可选): 返回消息条数,默认 10
**返回**:
```json
{
"agentId": "main",
"sessionKey": "agent:main:glasses",
"messages": [...],
"reply": "最后一条 assistant 回复",
"voice": {...},
"replyTruncated": false
}
```
### oc_cancel
取消指定任务。
**参数**:
- `runId` (string, 必填): 任务 runId
**返回**:
```json
{
"ok": true,
"status": "aborted",
"abortedRunId": "req-xxx"
}
```
## 配置
`config.example.json` 是最小可用配置,复制成 `config.json` 后按需修改:
```json
{
"defaultAgent": "main",
"allowedAgents": ["main", "coder"],
"disabledAgents": [],
"aliases": { "助手": "main", "coder": "coder" },
"execRisk": { "main": "allow", "coder": "allow" },
"session": { "suffix": "glasses", "maxKeyLength": 200 },
"rateLimit": { "perMinute": 6, "perHour": 60 },
"voice": { "maxSpeakChars": 150, "summaryThreshold": 400 },
"run": { "defaultWaitSeconds": 20, "maxWaitSeconds": 120, "cliTimeoutMs": 30000, "rpcTimeoutMs": 15000 },
"dedupe": { "windowSeconds": 5 },
"audit": { "dir": "~/Library/Application Support/openclaw-mcp-bridge" }
}
```
几个字段的含义:
- `allowedAgents` / `disabledAgents`: 允许、禁止通过桥接下发的 agent ID(ID 来自 `openclaw agents list`)。
- `aliases`: 语音里更好念的名字 → agent ID。前缀匹配也能命中。
- `execRisk`: 仅作提示位,记录该 agent 是否有 exec 权限;桥接不会修改 OpenClaw 的权限策略。
- `run.rpcTimeoutMs` / `maxWaitSeconds`: 每次 `gateway call` 显式带 `--timeout`,等待时长由请求换算(见 DESIGN §超时)。
- `session.suffix`: 会话键后缀,把眼镜侧的会话和其他来源隔离开。
## 环境变量
- `OPENCLAW_BIN`: OpenClaw 可执行文件路径(默认 `/opt/homebrew/bin/openclaw`)
- `OPENCLAW_NODE_BIN`: Node.js 可执行文件路径(默认 `process.execPath`)
- `OPENCLAW_BRIDGE_HOME`: 审计日志目录(默认 `~/Library/Application Support/openclaw-mcp-bridge`)
## 安全
- ✅ 不暴露 Gateway token、exec-approvals token
- ✅ 不修改 OpenClaw agent 权限
- ✅ 审计日志本地存储,权限 0700/0600
- ✅ 无公网入口
## 已知限制
- 会话锁是内存状态:Gateway 重启或桥接进程重启后会丢失,需要重新下发。
- 回复可能被 Gateway 截断(`replyTruncated`)或读取失败(`replyUnavailable`),此时用 `oc_last` 再取一次。
- 语音分类走启发式,极简的无文件头 diff 可能被判成普通文本;`voice.kind` 只作播报提示。
- 真实提交响应丢失时,桥接会用已知的 `runId` 对账并按 `running` 处理——健康的 Gateway 无法区分"从未受理"和"仍在排队",此时用 `oc_cancel` 是可靠的恢复手段。
## 许可证
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues