Skip to main content
Glama
Shitsuki4
by Shitsuki4
README.md
# tmwd-browser-mcp

**让 AI agent 直接操作你真实的浏览器。** 一个 MCP server + 一个极小的浏览器扩展,把
**你自己在用的 Microsoft Edge / Google Chrome** 交给 agent —— 同一个 profile、同一批
cookie、同一份登录态。

不用 Selenium,不用 Playwright,不开 `--remote-debugging-port`,也不启动任何额外浏览器。

**English:** [README.en.md](README.en.md)

```
┌──────────────┐   MCP (stdio)   ┌────────────────────┐   WebSocket    ┌──────────────────┐
│  AI agent /  │ ◄─────────────► │  tmwd-bridge-mcp   │ ◄───────────── │  你的 Edge /     │
│  MCP client  │  browser_*      │  (本项目的 server) │  127.0.0.1:   │  Chrome +        │
└──────────────┘   工具          │  + WebSocket 主端  │  18765        │  这个扩展        │
                                 └────────────────────┘               └──────────────────┘
                                                                        chrome.debugger
                                                                        (标签页级 CDP)
```

扩展是**主动外连**到 server 的,所以本机没有任何调试端口在监听,浏览器也不会跑在一个
"自动化专用 profile" 里。

## 为什么不用调试端口?

因为 **Chromium 已经把它在默认 profile 上禁掉了**。当浏览器用的是正常用户目录时,
`--remote-debugging-port` 会被直接忽略 —— 这是官方为防 cookie 窃取刻意做的限制。想用它,
就只能另开一个一次性 profile,代价是**丢掉所有登录态**。

本项目走另一条路:一个带 `debugger` 权限的扩展,在**你本来就在用的 profile 里**拿到
标签页级的 CDP。已在 Edge 154 上端到端验证。

## 安装

### 1. 装扩展

- **Edge:** `edge://extensions`
- **Chrome:** `chrome://extensions`

打开**开发者模式** → **加载解压缩的扩展** → 选 `assets/tmwd_cdp_bridge/`。

装好后每个页面右下角会出现一个小徽章:绿色 = 已连上 server,橙色 = 重连中,红色 = 断开。

### 2. 注册 MCP server

```json
{
  "mcpServers": {
    "tmwd": {
      "type": "stdio",
      "command": "node",
      "args": ["/改成绝对路径/tmwd-browser-mcp/mcp/tmwd-bridge-mcp.mjs"]
    }
  }
}
```

改完**要重启 MCP 客户端** —— server 是启动时发现的。server 会占用 `127.0.0.1:18765`,
并且对普通 `GET /` 返回 200(扩展就是靠这个探活的)。

扩展自己会重试(`chrome.alarms` 每 ~5 秒探一次,每 ~24 秒发一次保活让 MV3 service worker
不被挂起),所以**谁先启动都无所谓**。

## 工具

| 工具 | 作用 |
|---|---|
| `browser_status` | 连接状态、已知标签页、当前解析到的活动标签页 |
| `browser_tabs` | 列出可脚本化的标签页(仅 http/https) |
| `browser_cookies` | 读取 cookie(含 partitioned),按标签页或 URL |
| `browser_eval` | 在页面里跑 JS(world=MAIN;CSP 拦 eval 时自动回落 CDP) |
| `browser_cdp` | 通过 `chrome.debugger` 发原始 CDP 命令(protocol 1.3) |
| `browser_batch` | 一次往返跑多条命令,懒 attach,支持 `$N.path` 引用 |
| `browser_screenshot` | `Page.captureScreenshot` 存成 PNG(后台标签页也能截) |
| `browser_click` | 完整鼠标生命周期 + 首次 attach 的坐标预热 |
| `browser_type` | `Input.insertText` |
| `browser_upload` | DataTransfer 上传文件(不依赖 CDP) |
| `browser_content_settings` | 例如 `automaticDownloads` → `allow` |

每个工具的 `tabId` 都是**可选的** —— 不传就落到当前活动标签页。

## 这个项目真正编码进去的坑

价值大半不在管道,而在失败模式。有几条我直接写进了工具实现里:

- **`browser_click` 一定会先发 `mouseMoved` 再按下去** —— 漏掉它,所有依赖 hover 的组件
  (MUI Tooltip、Ant Design Dropdown)会静默失效。
- **首次 attach 的信息栏偏移:** 第一次 `chrome.debugger.attach` 会弹出一条约 20px 高的
  "正在受自动化控制" 提示栏,把页面内容往下推。**attach 之前**量的坐标会整体偏掉这么多。
  `browser_click` 首次使用时会先用 `mouseMoved(0,0)` 预热。这条正是上游一次下拉框点歪的
  真实根因。
- **`browser_eval` 里用了 `await` 却不写 `return` 会拿到 `null`** —— 代码被包在 async IIFE 里。
- **导航和后续操作必须拆成两次调用** —— 写在同一段 JS 里会报
  `Inspected target navigated or closed`。
- **只有标签页级的 CDP 域可用。** `Browser.*`(`getVersion`、`setDownloadBehavior`)和
  `Target.getTargets` / `Target.attachToTarget` 都不可用 —— debugger 会话是按标签页的。
  命令报 "wasn't found" 时,**先怀疑域,别怀疑浏览器**。
- **`browser_batch`: 前面一条失败,后面所有 `$N` 引用会静默变成 `undefined`。**
  必须逐条看 `results` 里的 `ok`,不能只看外层 `ok`。
- **上传走 DataTransfer** —— 因为这个桥里 `DOM.setFileInputFiles` 的 nodeId 跨调用会失效。
- **跨域 iframe:** `Page.getFrameTree` → `Page.createIsolatedWorld` →
  用返回的 `executionContextId` 调 `Runtime.evaluate`。
- **autofill 的值只在前台标签页释放** —— 先 `Page.bringToFront`。

完整清单(batch 链式引用、穿透 Shadow DOM、验证码截图、多文件下载对话框绕法)在
[`SKILL.md`](SKILL.md)。

## 当作 Claude Code skill 用

`SKILL.md` 就是按 Claude Code skill 格式写的:把仓库放到 `~/.claude/skills/tmwd-browser/`,
agent 就会按触发词自动认出来(`用我的 Chrome`、`保留登录态`、`browser automation` …)。

## 配置

| 环境变量 | 默认 | 含义 |
|---|---|---|
| `TMWD_PORT` | `18765` | WebSocket + HTTP 端口。**必须**和扩展里的 `WS_URL` 一致。 |
| `TMWD_HOST` | `127.0.0.1` | 绑定地址。 |
| `TMWD_TIMEOUT_MS` | `60000` | 单条命令超时。 |

改端口要同时改 `assets/tmwd_cdp_bridge/background.js` 里**两处**硬编码:`WS_URL` 和
`isServerAlive()` 里的 fetch。

## 环境要求

- Node.js ≥ 18(server **零 npm 依赖** —— WebSocket 服务端和 MCP stdio 传输都是手写的,
  裸 `node` 就能跑)
- Microsoft Edge 或 Google Chrome,且能开开发者模式
- 扩展需要 `debugger` 权限,这会导致每个标签页**首次 attach** 时出现一条"正在受自动化控制"
  的信息栏。这是预期行为,也正是坐标必须在 attach **之后**测量的原因。

## 致谢

浏览器扩展,以及蒸馏进 `SKILL.md` 的操作知识,来自
[`lsdefine/GenericAgent`](https://github.com/lsdefine/GenericAgent)(MIT):
`assets/tmwd_cdp_bridge/` 和 `memory/tmwebdriver_sop.md`。扩展是**逐字再分发**并保留其
原始许可;MCP server、工具层和文档属于本项目。

上游有一处矛盾值得知道:SOP 里写主端监听 **18766**,而扩展代码里是 **18765**。
本 server **以代码为准**。

## 许可证

MIT —— 见 [LICENSE](LICENSE)。其中再分发的扩展为 MIT © 2025 lsdefine
([其原始许可](assets/tmwd_cdp_bridge/LICENSE))。