Skip to main content
Glama
compnew2006

Browser Controller

by compnew2006

这个项目解决了什么问题

你提交了一个修复。你的智能体说"完成了,请验证。" 你切换到 Chrome,导航到页面,登录,点击各处,找到 bug。

你的智能体刚刚写了代码。它也可以验证。 你的浏览器已经为它打开着。它只是看不到。

现在它可以了。 Browser Controller 让任何兼容 MCP 的 AI 智能体(Cursor、Claude Desktop、Windsurf……)直接控制你已经打开的浏览器——你真实的会话、你的登录状态、你的 cookie。无需无头浏览器,无需全新配置文件,无需重新认证。

Related MCP server: Tabryn

核心能力

  • 同时支持多个智能体。 Cursor 可以驱动标签页 10,而 Claude 驱动标签页 11——两者通过同一个共享守护进程,互不阻塞。

  • 按标签页定位,而非"当前活动标签页"。 每个操作都指定一个 tabId。移动鼠标、切换标签页、看 YouTube——智能体始终在你指定的标签页上工作。它永远不会劫持你正在阅读的页面。

  • 按标签页隔离。 元素引用、控制台日志和网络缓冲区都按标签页隔离。来自标签页 10 的引用永远无法点击标签页 20 中的内容。

  • 按标签页并发。 对同一个标签页的两个操作会串行化(无竞争);对不同标签页的操作并行执行。

  • 标签页锁定。 智能体可以认领一个标签页,让其他智能体排队等待而不是竞争(browser_tabs { action: "lock" })。锁在 Chrome 的 service-worker 回收后依然有效(chrome.storage.session)。

  • 智能体控制防护罩。 当智能体在某个标签页上工作时,你会看到一个半透明的蓝色内框,并且你在该标签页上的输入会被阻止(鼠标、键盘、滚轮)——徽章显示 agent <name> controlling the tab,操作完成后消失。锁定标签页会在锁的存续期间保持一个普通边框。

  • 同源 iframe 穿透。 位于 iframe 内的旧式/企业界面(例如 iframe#mainFrame 中的 ONT 控制台)可以访问:所有定位工具都会搜索 iframe 文档,find/click_text 会遍历每个框架。

  • 原生对话框救援。 原生的 alert/confirm/prompt 会冻结页面的 JS 线程——browser_handle_dialog 通过 CDP 带外解除它,无需页面 JS,这也会解除该标签页上所有其他工具的阻塞。browser_tabs close/focus 始终有效,即使在冻结的标签页上也是如此。

  • 经过认证的本地连接。 令牌 + 一次性注册密钥,因此没有其他本地进程可以静默驱动你的浏览器。一切都在 localhost 上——无云端,无遥测。

  • 无调试器横幅。 browser_evaluate 通过 chrome.scripting 在页面的 MAIN 世界中运行——没有黄色的"此标签页正在被调试"横幅,真实值可以跨 MV3 世界边界返回。

  • 诚实的错误。 每个工具失败都会以真实的 isError 结果连同完整负载到达你的智能体——不会用"成功"响应来掩盖工作流中途的失败。


工作原理

三个部分,全部在你的机器上。没有任何内容离开 localhost。

  Agent (Cursor / Claude / Windsurf)        ── other agents connect too ──┐
                  │ stdio (MCP protocol)                                   │
                  ▼                                                        ▼
  ┌─────────────────────────────┐   ┌─────────────────────────────────────────┐
  │  thin MCP client            │   │  thin MCP client                        │
  │  (node mcp-server/dist/     │   │  (node mcp-server/dist/                 │
  │   index.js)                 │   │   index.js)                             │
  │  - speaks MCP over stdio    │   │  - spawns daemon if not running         │
  │  - forwards calls to daemon │   │  - gets its own sessionId               │
  └──────────────┬──────────────┘   └────────────────────┬───────────────────┘
                 │ local IPC socket (AF_UNIX / named pipe, token-auth)     │
                 ▼                                                          ▼
  ┌──────────────────────────────────────────────────────────────────────────┐
  │  DAEMON (single long-running process, owns port 7225)                     │
  │  - multiplexes N clients → 1 extension                                    │
  │  - tags every call with the client's sessionId                            │
  │  - heartbeat eviction, per-session rate limiting                          │
  └──────────────────────────────┬───────────────────────────────────────────┘
                                 │ WebSocket ws://127.0.0.1:7225 (token-auth)
                                 ▼
  ┌──────────────────────────────────────────────────────────────────────────┐
  │  Chrome Extension (Manifest V3 service worker)                            │
  │  - resolves the target tabId (never "the active tab" implicitly)          │
  │  - serializes same-tab actions, parallelizes cross-tab actions            │
  │  - executes click/type/snapshot/evaluate against the named tab            │
  └──────────────────────────────────────────────────────────────────────────┘

关键思路: 任何智能体首次运行时,瘦客户端会启动一个后台守护进程,它拥有端口 7225 和扩展连接。之后每个智能体(即使来自不同的 MCP 客户端)都通过本地 IPC 套接字连接到同一个守护进程,并获得自己的 sessionId。扩展看到的是一个稳定的连接,并将每个调用路由到调用者指定的确切标签页。


快速开始

该项目不在 Chrome 网上应用店或 npm 上——你需要从这个仓库安装。两个部分:MCP 服务器(在你的机器上运行,与你的 AI 智能体通信)和 Chrome 扩展(位于你的浏览器中,执行命令)。

前置条件: Node.js ≥ 20 和 Chrome/Chromium/Edge。

1. 克隆并构建

git clone https://github.com/compnew2006/browser-controller.git
cd browser-controller
npm install
npm run build        # compiles TypeScript → mcp-server/dist/

2. 加载 Chrome 扩展

  1. 打开 chrome://extensions 并启用开发者模式(右上角的开关)

  2. 点击加载已解压的扩展程序,选择克隆仓库中的 extension/ 文件夹

  3. 将 Browser Controller 图标固定到工具栏

灰点 = 等待守护进程。绿色 = 已连接。

3. 将 MCP 服务器添加到你的客户端

Cursor:设置 → MCP → "添加新的 MCP 服务器"。Claude Desktop:编辑 claude_desktop_config.json。Windsurf:设置 → MCP。任何兼容 MCP 的客户端都可以。

将 /path/to/browser-controller 替换为你克隆的绝对路径(Windows:使用 C:\\path\\to\\browser-controller\\mcp-server\\dist\\index.js):

{
  "mcpServers": {
    "browser-controller": {
      "command": "node",
      "args": ["/path/to/browser-controller/mcp-server/dist/index.js"]
    }
  }
}

默认情况下,守护进程会根据其父级 IDE("Cursor"、"Claude"……)为每个连接命名。要覆盖此设置——例如当多个智能体共享一个 IDE 时,或按项目标记它们——在参数中传入 --agent <name>。它优先于所有自动检测:

{
  "mcpServers": {
    "browser-controller": {
      "command": "node",
      "args": ["/path/to/browser-controller/mcp-server/dist/index.js", "--agent", "My Project Agent"]
    }
  }
}

该名称会出现在弹窗的已连接智能体列表中。(你也可以设置 MCP_AGENT_NAME 环境变量——效果相同。)使用相同名称重新连接会替换旧条目,因此 IDE 重启不会堆积重复项。

4. 将扩展与守护进程配对

守护进程使用两个密钥,均在首次运行时生成到 ~/.browser-controller/(Windows:%USERPROFILE%\.browser-controller\)。先让智能体"列出我的浏览器标签页"来启动它一次,然后:

  1. 读取密钥:

    cat ~/.browser-controller/enrollment.json   # one-time pairing secret
    cat ~/.browser-controller/token.json        # WebSocket auth token

    (注册密钥在首次运行时也会打印到 MCP 客户端的日志中。)

  2. 点击扩展图标 → 设置标签页 → 粘贴注册密钥和认证令牌(除非你更改了 WS_PORT,否则端口保持 7225)。

绿点 = 你已连接。你的智能体现在可以看到你的浏览器了。

这些密钥可防止任何其他本地进程打开 WebSocket 并驱动你经过认证的浏览器会话。要轮换它们,请停止你的 MCP 客户端,删除该文件夹,下次运行时会重新创建两个密钥。完整的威胁模型请参阅 SECURITY.md。


使用方法

模型是标签页优先的:智能体总是说明要对哪个标签页执行操作。它从不假设是"当前活动标签页"。

基本工作流

  1. 列出标签页以获取 tabId:

    browser_tabs { action: "list" }
    → [{ id: 15, url: "...", title: "...", active: true, lockedBy: null }, ...]
  2. 快照该标签页以查看其结构并获取元素引用:

    browser_snapshot { tabId: 15 }
    → { tree: [ { ref: "e3", role: "button", name: "Sign in" }, ... ] }

    引用仅对此 tabId 有效。如果你导航或 DOM 发生变化,请重新快照。自上次快照以来的新元素会被标记为 isNew: true——在操作打开覆盖层/下拉菜单后,智能体可以只关注这些元素,而无需重新读取整个树。

  3. 交互,使用引用和相同的 tabId:

    browser_click { tabId: 15, ref: "e3" }
    browser_type  { tabId: 15, ref: "e5", text: "hello@example.com" }
    browser_press_key { tabId: 15, key: "Enter" }

    如果引用已过期但元素仍然存在,会通过稳健的选择器 + 文本/角色扫描自动找到它(响应携带 via: "fallback")。如果元素被完全滚动出视野(虚拟化信息流),响应会携带 freshRefs: [...] 及内联的新快照——在同一个步骤中用这些新引用之一重试,无需单独快照。

  4. 验证——操作后再次快照或读取文本。

多智能体协调(两个智能体,两个标签页)

  1. 智能体 A 列出标签页,选择标签页 10,可选地锁定它:browser_tabs { action: "lock", tabId: 10 }

  2. 智能体 B 列出标签页,选择标签页 11,锁定它:browser_tabs { action: "lock", tabId: 11 }

  3. 两者并行工作。每个智能体的调用针对自己的标签页串行化;两个标签页互不干扰。

  4. 完成后:browser_tabs { action: "unlock", tabId: 10 }。

弹窗就是你的控制面板

一个固定高度的标签式外壳(主体从不滚动,只有列表滚动):

  • 标签页 — 每个打开的标签页及其锁所有者,工具栏中还有全部解锁,用于在智能体锁定中途崩溃时一键释放。

  • 智能体 — 每个已连接的智能体及其名称、会话 ID、运行时间,以及一个 ✕ 可立即断开连接(清除心跳尚未回收的僵尸进程)。

  • 设置 — WebSocket 端口、认证令牌、注册密钥。

  • 活动栏 — 底部一个可折叠的条带,显示最新的工具活动;展开可查看滚动日志。

需要了解的事项

  • 忘记 tabId 了? 你会收到明确的错误:tabId is required. Call browser_tabs list first.

  • 受保护的页面(chrome://、网上应用店、devtools)无法被脚本化——你会收到 Cannot access protected page (chrome://...),而不是静默挂起。

  • browser_navigate 是唯一一个 tabId 可选的工具(默认为活动标签页)——但为了多智能体安全,请显式传入。仅哈希变化(例如 /page → /page#section)在 URL 设置后立即解析,无需等待 complete 事件(SPA 在哈希变化时不会重新加载,因此该事件永远不会触发)。

  • browser_evaluate 在页面的 MAIN 世界中运行(无调试器横幅,CSP 安全)并返回真实值(跨世界边界进行 JSON 序列化)。它很强大但非幂等——超时后不会自动重试。

  • 滚动虚拟化信息流(Facebook/Instagram/Twitter):browser_scroll 返回 refsMayBeStale: true,因为这些站点会回收 DOM 节点。下次交互前请重新快照。

  • 重复元素:当多个元素共享文本+角色时(例如 3 个"点赞"按钮),回退解析器按序号(nth)选择正确的那个,而不仅仅是第一个匹配项。

  • 冻结的标签页(原生对话框阻塞)不会让你死锁:browser_handle_dialog 通过 CDP 解除它,browser_tabs { action: "close" } 始终有效,是保证退出的方式。


🧠 教你的智能体

智能体开箱即可使用全部 22 个工具,但当它了解标签页优先的工作流时会表现更好。从仓库根目录:

npm run setup:cursor   # or: node mcp-server/dist/index.js --setup cursor

这会安装:

  • ~/.cursor/rules/browser-controller.mdc — 标签页定位工作流、下拉菜单处理、何时锁定标签页

  • ~/.cursor/commands/check-browser.md — 为你的 Cursor 聊天添加 /check-browser

之后,在任何聊天中输入 /check-browser。或者直接说"在我的浏览器中检查结果",智能体就知道该做什么。

npm run setup:claude

在你的项目根目录添加一个 AGENTS.md。Claude Code 会自动发现它。

手动安装或自定义规则请参阅 agent-config/。


它能做什么

22 个工具。每个页面交互工具都接受一个 tabId(唯一的例外是 browser_navigate,其中它是可选的)。

查看

工具

功能

browser_snapshot

带元素引用的无障碍树。紧凑模式(默认)仅返回可交互元素。遍历 shadow DOM 和 iframe。

browser_screenshot

将标签页捕获为图像(先激活标签页再捕获)

browser_text

从页面或元素提取原始文本

browser_find

通过自然语言查询元素——也会遍历同源 iframe

交互

工具

功能

browser_click

通过引用或 CSS 选择器点击——穿透同源 iframe

browser_click_text

按可见文本点击。可穿透 React portals 和覆盖层

browser_type

在输入框和 contenteditable 字段中键入

browser_press_key

按键组合(Enter、Escape、Ctrl+A)

browser_scroll

滚动页面和虚拟容器

browser_hover

触发工具提示和下拉菜单

browser_select

从原生 <select> 下拉菜单中选择

browser_wait

等待元素出现或消失

browser_fill_form

一次调用填充多个表单字段(React/Vue 安全的 setter)

browser_drag

元素到元素的拖拽(使用 CDP 保证可靠性)

browser_upload_file

通过 <input type="file"> 上传文件(使用 CDP,严格 CSP 安全)

browser_upload_file 将本地文件注入 <input type="file">,就像用户选择了它们一样:原生对话框永远不会打开,之后会触发 input/change 事件,因此 React/Vue 表单会做出响应。

browser_upload_file { tabId: 15, selector: "#resume", filePath: "/Users/me/resume.pdf" }
browser_upload_file { tabId: 15, ref: "e12", files: ["/tmp/a.png", "/tmp/b.png"] }

路径是绝对的,并且位于运行浏览器的机器本地。省略 ref/selector 会自动定位页面上的第一个文件输入;一次上传多个文件需要带有 multiple 的输入。

导航

工具

功能

browser_navigate

在标签页中转到 URL(tabId 可选,默认为活动标签页)

browser_tabs

列出 / 创建 / 关闭 / 聚焦 / 锁定 / 解锁 标签页

调试与高级

工具

功能

browser_console

控制台输出(log、warn、error)——按标签页,上限 200 条

browser_network

带状态码的 XHR/fetch 请求——按标签页,可选 limit

browser_evaluate

在页面的 MAIN world 中运行 JavaScript(无横幅,CSP 安全)

browser_handle_dialog

通过 CDP 关闭/接受打开的 alert/confirm/prompt(在冻结页面上也可用)

browser_run_action

通过 CDP 运行自包含的 JS action 对象


与其他方案对比

Browser Controller

Playwright MCP

Chrome DevTools MCP

使用你现有的浏览器

是

否,会启动新的

部分,需要调试端口

会话和 Cookie

已经存在

全新配置文件

手动设置

支持企业 SSO 登录

是

否

视情况而定

多代理、多标签页

是

否

否

标签页定位(不会劫持活动标签页)

是

不适用

否

经过身份验证的本地连接

是

不适用

否

设置

从源码构建 + 扩展

无头浏览器

带 --remote-debugging-port 的 Chrome


配置

环境变量

默认值

功能

WS_PORT

7225

守护进程用于扩展连接的 WebSocket 端口

BROWSER_CONTROLLER_PROGRESSIVE

(未设置)

设置为 1 以启用渐进式工具披露:启动时仅显示 browser_tools 元工具(约 150 个 token,而不是全部 22 个定义的约 4200 个)。代理通过 browser_tools {action:"list"/"search"} 发现工具,并通过 {action:"details", tool:"…"} 激活它们。默认(未设置)会预先显示所有工具——对于指令直接调用工具的代理来说是安全的。

MCP_AGENT_NAME

(自动:IDE 名称)

覆盖弹出窗口中显示的代理名称(与 --agent 相同)

守护进程状态文件

守护进程将所有内容保存在 ~/.browser-controller/(Windows:%USERPROFILE%\.browser-controller\):

文件

用途

enrollment.json

扩展的一次性配对密钥(模式 0600)

token.json

扩展在每次 WebSocket 连接时必须提供的认证令牌(模式 0600)

daemon.sock

瘦客户端连接的 IPC 套接字(mac/linux 上为 AF_UNIX;Windows 上为命名管道)

daemon.json

守护进程元数据(pid、端口、启动时间)——用于检测正在运行的守护进程

daemon.log

客户端启动守护进程时的 stdout/stderr

要完全重置:停止你的 MCP 客户端,删除该文件夹,下次运行时会用新的密钥重新创建。

可靠性

  • 守护进程在任何客户端首次运行时自动启动,并保持分离运行。

  • 连接断开时使用指数退避(1 秒 → 30 秒),每 10 秒进行 ping/pong 健康检查;错过 3 次 pong 的客户端将被驱逐。

  • 每会话 120 次/分钟的速率限制保护守护进程免受失控的代理循环影响。

  • 每个工具都有超时(大多数操作 5–15 秒,导航 60 秒),与每个工具的定义放在一起,因此不会与注册表脱节。

  • 幂等读取工具(snapshot、screenshot、text、find)在超时时会重试;有副作用的工具(click、type、navigate、evaluate)——以及 console/network(在 clear:true 时会修改状态)——绝不重试,因此点击不会触发两次。

  • 如果另一个进程已经占用了端口 7225,守护进程会拒绝启动,而不是杀死它没有启动的进程——它会报告冲突,以便你有意识地解决它。

通过为每个客户端设置 WS_PORT,在不同的端口上运行两个守护进程:

{
  "mcpServers": {
    "browser-work": {
      "command": "node",
      "args": ["/path/to/browser-controller/mcp-server/dist/index.js"]
    },
    "browser-personal": {
      "command": "node",
      "args": ["/path/to/browser-controller/mcp-server/dist/index.js"],
      "env": { "WS_PORT": "9333" }
    }
  }
}

更新每个扩展弹出窗口中的端口以匹配。


架构

一切都在你的机器上。扩展通过 localhost 上的经过身份验证的 WebSocket 连接到守护进程;MCP 客户端通过本地 IPC 套接字连接到守护进程。没有云、没有代理,没有任何东西离开你的浏览器。

browser-controller/
├── mcp-server/          MCP server (TypeScript)
│   └── src/
│       ├── daemon.ts        Single multi-client daemon (owns WS :7225)
│       ├── daemon-config.ts IPC protocol, paths, auth/enrollment tokens
│       ├── index.ts         Thin stdio MCP client (spawns daemon, multiplexes)
│       ├── bridge.ts        Extension WS server + cross-platform port probe
│       ├── register-tools.ts Progressive-disclosure wiring
│       └── tools/           One file per tool (22), registry pattern
├── extension/           Chrome extension (Manifest V3, plain JS, ES modules)
│   ├── background.js        Wiring only (~30 lines): inject router, register events, connect
│   ├── lib/                 state (buffers/locks/persistence), connection (WS lifecycle),
│   │                        router (dispatch + mutex/locks + control shield), page-exec,
│   │                        overlay, lock-ops, tab-concurrency (pure, unit-tested)
│   ├── handlers/            Tool implementations: navigation, interaction, inspection, tabs, cdp
│   ├── utils/               navigation + smart-selector fallback resolution
│   ├── events.js            chrome.* listeners (console capture, popup, webRequest, lifecycle)
│   ├── content.js           Console capture
│   └── popup/               Fixed tabbed shell (Tabs · Agents · Settings) + collapsible activity bar
├── agent-config/        Pre-built configs for Cursor + Claude Code
│   ├── cursor/              Rules and commands
│   ├── skills/              Browser automation skill
│   └── setup.mjs            One-command installer
└── tests/               15 suites / 215 tests

技术栈: TypeScript(严格模式)· MCP SDK · WebSocket · Chrome 扩展 Manifest V3 · Vitest

开发

git clone https://github.com/compnew2006/browser-controller.git
cd browser-controller
npm install
npm run build
npm test

命令

功能

npm run build

将 TypeScript 编译到 mcp-server/dist/

npm run dev

监视模式

npm test

运行完整测试套件(215 个测试)

npm run typecheck

仅进行类型检查,不输出

npm run setup:cursor

安装 Cursor 规则 + 命令

npm run setup:claude

安装 Claude Code AGENTS.md

该套件涵盖 WebSocket 桥接(包括令牌认证拒绝和统一错误通道)、工具注册表、守护进程生命周期(心跳驱逐、速率限制、IPC 认证)、每标签页并发(同标签页串行化 + 跨标签页并行),以及通过模拟的 chrome API 测试扩展行为(路由分发、shield 语义、evaluate 往返、iframe 穿透、对话框救援)。CI 在 Node 20 和 22 上运行该套件,外加 CodeQL 和 Scorecard 扫描。

更新现有安装

git pull
npm install
npm run build

然后有两个手动步骤:在 chrome://extensions 中重新加载扩展(运行中的 service worker 不会自行拾取文件更改),以及重启守护进程——它是长期运行的,也不会重新加载 dist/(杀掉它,或者直接重启你的 MCP 客户端,下次运行会在新构建上重新启动它)。


常见问题

这正是它的意义所在。扩展运行在你实际的 Chrome 中——相同的 Cookie、相同的会话、相同的本地存储。无需重新认证。

不会。MCP 客户端、守护进程和扩展都通过 localhost(IPC 套接字 + WebSocket)通信。没有任何东西离开你的机器。没有分析、没有遥测、没有云组件。有关威胁模型、认证设计和首次接触 TOFU 窗口,请参阅 SECURITY.md。

任何兼容 MCP 的客户端。Cursor、Claude Desktop、Claude Code、Windsurf、Cline,以及任何其他支持 MCP 协议的客户端。多个客户端可以同时连接同一个守护进程运行。

可以。每个代理连接到共享守护进程,获取自己的 sessionId,并针对特定的 tabId。同一标签页上的操作通过每标签页互斥锁串行化;不同标签页上的操作并行运行。代理可以选择 lock 一个标签页以声明独占访问权;其他代理会在锁后面排队,而不是失败。

它不会——不会静默发生。每个页面交互工具都需要 tabId,如果缺少,你会收到明确的 tabId is required 错误。代理永远不会意外操作你正在查看的标签页。(唯一的例外是不带 tabId 的 browser_navigate,它使用活动标签页——但在多代理场景下,你应该始终传递 tabId。)

没有它们,你机器上的任何本地进程都可以打开到端口 7225 的 WebSocket,并驱动你已认证的浏览器会话(你的银行、你的邮箱、你公司的 SSO)。enrollment 密钥将扩展与守护进程恰好配对一次(带外,在任何 WebSocket 存在之前);然后 auth token 对每个连接进行认证。两者都存放在 ~/.browser-controller/ 中,权限为 0600。

它们会从零启动一个新的浏览器实例——没有状态、没有 cookie、没有会话。你每次都必须重放完整的登录流程。而这个是连接到你已经打开的浏览器,所有内容都已加载完毕。


贡献

欢迎在问题跟踪器提交错误报告、功能请求和 PR。较大的更改请先打开一个 issue。

安全

参见 SECURITY.md——仅限 localhost 的架构、token + enrollment 设计、威胁模型和报告指南。

许可证

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables CLI coding agents to interact with your live browser tabs via MCP, using your real sessions and cookies without a sandbox.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Connects AI agents to your Chrome browser via MCP, enabling real-time control of existing tabs, sessions, and application state for development workflows.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables any MCP-compatible AI agent to drive your own Chrome browser with your existing login state, filling forms, clicking elements, fetching data, and handling captchas without API keys or re-authentication.
    402 npm
    545
    MIT