reasonix-cdp-relay 是一个面向 Reasonix / MCP Agent 的浏览器控制中继层:
它把 Chrome DevTools Protocol(CDP)封装成标准 MCP 工具,让 Agent 可以稳定地完成导航、截图、快照、点击、输入、等待等操作。
核心目标:
- 🧩 零侵入接入:作为外置 MCP Server 运行,不需要改 Reasonix 源码
- 🌐 支持真实浏览器控制:支持独立 Chrome、附着已有 Chrome、浏览器插件三种模式
- 🔒 更稳的会话与标签页语义:Extension 模式下,
sessionId与tabId绑定明确 - 🧪 测试覆盖较完整:包含 contract / driver / extension / concurrency / session 测试
stdioMCP Server,适合本地 Agent / Reasonix 接入- 3 种驱动模式:
launch:启动隔离的 headless Chromeattach:附着到已有 Chrome 的 CDP 端口extension:通过 Chrome 扩展控制你正在使用的浏览器
- 14 个浏览器工具,覆盖:
- 会话管理
- 导航
- DOM 快照
- 截图
- 鼠标键盘交互
- JavaScript 执行
- 原始 CDP 逃生舱
- Extension 模式支持:
- tab 复用 / 收编现有页面
- 同一
sessionId强绑定同一 tab - 同 tab CDP 命令串行执行,减少竞态
- 内置排障能力:
- 结构化错误返回
- 可选日志看板(本地 log server)
当前实现是三层:
MCP Server (stdio)
-> dispatcher
-> actions
-> driver (launch / attach / extension)
-> Chrome DevTools Protocol
在 extension 模式下,链路会进一步变成:
MCP -> ws server -> Chrome extension service worker -> chrome.debugger -> Chrome
关键设计点:
dispatcher只负责路由,不做复杂决策ExtensionDriver是唯一的sessionId <-> tabId真相源- 同一 tab 的 CDP 命令按队列串行执行
- wait 事件(如
domcontentloaded)按 tab 隔离,不会被错误 tab 的事件误触发
npm install
npm run build开发模式:
npm run dev运行测试:
npm test本地排障日志面板:
npm run log:dev然后打开:
http://127.0.0.1:19999
reasonix-cdp-relay等价于:
reasonix-cdp-relay --driver launchreasonix-cdp-relay --driver attach --port 9222
attach模式要求 Chrome 已开启远程调试端口。
如果浏览器已打开但没带 CDP 端口,默认不会强制重启用户浏览器。
如需允许 relay 重启 Chrome 以补上 CDP 端口,可显式设置:
REASONIX_ATTACH_RESTART_CHROME=1 reasonix-cdp-relay --driver attach --port 9222reasonix-cdp-relay --driver extension --port 19223特点:
- 不需要重启你的日常 Chrome
- 更适合接入已经登录的站点
- 更适合真实用户浏览器自动化
扩展源码在:
extension/
安装步骤(Chrome):
- 打开
chrome://extensions - 开启“开发者模式”
- 选择“加载已解压的扩展程序”
- 选中本仓库的
extension/目录 - 启动 relay:
reasonix-cdp-relay --driver extension --port 19223扩展连接成功后,会主动连到:
ws://127.0.0.1:19223
~/.reasonix/config.json
{
"mcpServers": {
"cdp-relay": {
"type": "stdio",
"command": "reasonix-cdp-relay",
"args": ["--driver", "extension", "--port", "19223"],
"env": {
"RELAY_LOG_LEVEL": "info"
}
}
}
}{
"mcpServers": {
"cdp-relay": {
"type": "stdio",
"command": "node",
"args": [
"/absolute/path/to/reasonix-relay/dist/index.js",
"--driver",
"extension",
"--port",
"19223"
]
}
}
}| 模式 | 适合场景 | 优点 | 注意事项 |
|---|---|---|---|
launch |
测试、CI、纯自动化 | 隔离、稳定 | 登录态与日常浏览器隔离 |
attach |
已有 Chrome 已开 CDP 端口 | 共享真实浏览器状态 | 依赖 CDP 端口 |
extension |
已登录站点、真实浏览器任务 | 不重启浏览器、接近真实使用 | 需要安装扩展 |
如果你要操作:
- 已登录的站点(如后台、工作台、控制台)👉 优先
extension - 自动化回归测试 👉 优先
launch - 已有自动化 Chrome 环境 👉 可用
attach
当前共 14 个工具:
| Tool | 说明 |
|---|---|
browser_session_open |
打开浏览器会话 |
browser_session_close |
关闭浏览器会话 |
browser_session_list |
列出当前会话 |
| Tool | 说明 |
|---|---|
browser_navigate |
跳转到 URL |
browser_snapshot |
抓取 DOM 快照,返回 rid 树 |
browser_screenshot |
返回 base64 截图 |
browser_screenshot_save |
直接把截图保存到本地文件 |
| Tool | 说明 |
|---|---|
browser_click |
点击元素 |
browser_type |
输入文本 |
browser_keypress |
发送按键 |
browser_scroll |
页面或元素滚动 |
browser_waitFor |
等待 selector 出现 |
| Tool | 说明 |
|---|---|
browser_evaluate |
执行 JS |
browser_cdp |
原始 CDP 指令逃生舱 |
- 一个 driver 实例本质上对应一个浏览器上下文
launch/attach当前采用 单 session 语义- 重复
browser_session_open会复用已有会话,而不是开多个独立 tab
这是最重要的一部分:
sessionId与tabId绑定- 如果你已经拿到了一个
sessionId,后续browser_navigate(sessionId, url)会在这个 tab 上导航 - 不会再偷偷新开第二个 tab(除非你没有传
sessionId)
browser_session_open
-> browser_navigate
-> browser_snapshot / browser_screenshot / browser_click / browser_type ...
如果你在 extension 模式下 不传 sessionId 直接 navigate,系统会:
- 优先匹配现有 URL
- 再决定复用 / 收编 / 新开 tab
如果你传了 sessionId,则会优先复用这个会话绑定的 tab。
如果你不传 waitUntil:
{ "waitUntil": "domcontentloaded" }也就是说,默认等价于:
- DOM 建立完成
- 但不保证所有资源都加载完
| 值 | 含义 |
|---|---|
domcontentloaded |
DOM 建好即可继续(默认) |
load |
等待 Page.loadEventFired / readyState=complete |
networkidle |
等待网络安静下来,更适合截图或 SPA 稳定后操作 |
当前 navigate 在返回 status=ok 前,会额外确认页面“可用”,至少包括:
- URL 不是
about:blank readyState与当前waitUntil语义相符- 页面不是空壳
bodyChildCount > 0,或outerHTML.length > 80,或title非空
- URL 连续读取两次一致
这能避免“只收到一个事件就误报成功”。
返回 content 数组,通常至少两段文本:
- 人类可读文本
- 页面状态 JSON
- (extension 模式)会再追加 session 元信息
示例:
[
{ "type": "text", "text": "Navigated to https://www.baidu.com" },
{
"type": "text",
"text": "{\"status\":\"ok\",\"requestedUrl\":\"https://www.baidu.com\",\"finalUrl\":\"https://www.baidu.com/\",\"readyState\":\"interactive\",\"title\":\"百度一下,你就知道\",\"bodyChildCount\":12,\"htmlLength\":53821,\"tabId\":123}"
},
{
"type": "text",
"text": "{\"sessionId\":\"sess_xxx\",\"reused\":true,\"driver\":\"extension\"}"
}
]
⚠️ Agent 端不要只看第一段文本,要解析后面的 JSON。
返回 JSON:
{
"generation": 2,
"tabId": 123,
"tree": {
"rid": "r1",
"tag": "html",
"children": []
}
}说明:
generation:快照代际;新快照会让旧rid失效tabId:extension 模式下用于确认落点tree:可用于后续browser_click/browser_type
返回:
- 第 1 段:图片
- 第 2 段:结构化页面状态 JSON
返回:
- 第 1 段:保存路径文本
- 第 2 段:结构化页面状态 JSON
所有工具失败时,都会返回:
- MCP 层:
isError: true - 内容:结构化 JSON
格式:
{ "error": "E_...", "message": "...", "details": {} }示例错误码:
E_DRIVER_UNAVAILABLEE_CHROME_NOT_FOUNDE_TAB_BUSYE_NAV_TIMEOUTE_SELECTOR_NOT_FOUNDE_CDP_RAW_FAILEDE_SESSION_EXPIRED
✅ Agent 端必须优先检查
isError,不要只解析content[0].text
优先用:
--driver extension
推荐 navigate 时显式传:
{ "waitUntil": "networkidle" }这样通常比默认的 domcontentloaded 更适合截图与信息抓取。
因为新快照会让旧 rid 失效。
后续所有操作都继续传同一个 sessionId,这样才能稳定复用同一个 tab。
项目内置了一个轻量日志服务,方便排查:
- ws 服务有没有把命令发到正确 tab
- 插件有没有成功 attach
navigate/screenshot/snapshot是否落在同一 tab
npm run log:dev打开页面:
http://127.0.0.1:19999
日志会显示:
ws_startext_connectedcdp_sendcdp_resultcdp_eventsw_cdp_recvsw_attach_oksw_cdp_oksw_tab_open
非常适合排查“为什么明明 navigate 成功,但 screenshot 还是空白页”这类问题。
常用命令:
npm run dev
npm run build
npm test
npm run log:dev测试覆盖包括:
- contract
- driver
- extension
- concurrency
- session
- tool-result
当前测试会序列化执行,以避免多个 Chrome 实例并发导致不稳定。
src/
actions/ # navigate / snapshot / screenshot / click / type ...
common/ # logger / errors / cdp queue / debug helpers
drivers/ # launch / attach / extension
mcp-server/ # MCP server / dispatcher / schemas
session/ # session state / rid mapping / queue routing
extension/ # Chrome MV3 扩展
scripts/ # 调试脚本 / 日志服务
test/ # 自动化测试
dist/ # 编译产物
当前版本:0.1.0
这是一个偏工程实用型的 MCP relay,重点在于:
- 接入真实浏览器
- 稳定管理 session / tab
- 给 Agent 提供结构化页面状态
- 能快速定位“工具成功但页面不对”的链路问题
如果你准备把它接入自己的 Agent,建议优先从 extension 模式开始。👍