@ricardochen1996/browser-use-mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +165 -0
- package/LICENSE +27 -0
- package/NOTICE +34 -0
- package/README.md +452 -2
- package/README.zh-CN.md +328 -0
- package/bin/browser-use-mcp.mjs +83 -0
- package/docs/architecture.md +210 -0
- package/docs/clients.md +425 -0
- package/docs/config.md +260 -0
- package/lib/cli.js +146 -0
- package/lib/config.js +341 -0
- package/lib/doctor.js +62 -0
- package/lib/engine.js +717 -0
- package/lib/http.js +222 -0
- package/lib/jev.js +105 -0
- package/lib/log.js +32 -0
- package/lib/provision.js +585 -0
- package/lib/python.js +470 -0
- package/lib/render.js +75 -0
- package/lib/server.js +193 -0
- package/lib/session.js +484 -0
- package/lib/sidecar.js +472 -0
- package/lib/tools.js +352 -0
- package/package.json +69 -4
- package/sidecar/bridge.py +982 -0
- package/vendor/jev-ultrafast.json +6 -0
- package/vendor/jev_ultrafast-0.1.1-py3-none-any.whl +0 -0
- package/vendor/requirements.txt +74 -0
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# @ricardochen1996/browser-use-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ricardochen1996/browser-use-mcp/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
[English](README.md) · **中文**
|
|
6
|
+
|
|
7
|
+
> 一个 MCP 服务器,给**任何 agent** 一双能看、能点的浏览器:页面被读成一张**带索引的动作空间表**,模型每步只做一个操作、只对一个来自那张表的目标。
|
|
8
|
+
|
|
9
|
+
`browser-use-mcp` 是一个 [Model Context Protocol](https://modelcontextprotocol.io) 服务器。Claude Code、Claude Desktop、Cursor、Codex、DeepSeek Harness 会话,或者任何其他 MCP 客户端连上它,它的 agent 就得到一个浏览器:打开页面,把页面读成一张带编号目标的表,然后指明一个操作和一个目标。模型永远不用写选择器、坐标或脚本,服务器也永远不会自己重试一次改动。
|
|
10
|
+
|
|
11
|
+
它建立在 [`dsh-browser-use`](https://github.com/ricardochen1996/dsh-browser-use) 之上:同一个 Python 引擎、同一套 sidecar 协议、同一个“页面即带索引动作空间”的想法——只是把 DeepSeek Harness 插件宿主换成了 MCP 宿主。所以它本身不含浏览器逻辑:这个包是适配层,让任何 MCP 客户端都能驱动 [Jev Ultrafast](https://github.com/ricardochen1996/jev-ultrafast) 引擎。引擎以固定版本的 wheel 随包发布,sidecar 的依赖按版本和每个产物的哈希锁定;首次使用时它们被装进一个由本服务器管理的 Python 环境。不用克隆引擎,不手动配 Python。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 这是什么
|
|
16
|
+
|
|
17
|
+
浏览器工具只暴露一条循环:
|
|
18
|
+
|
|
19
|
+
1. **读。** `browser_open` 打开页面,把它的**动作空间**作为文本返回:每个可达元素一行,写明该元素支持哪些操作,以及这些操作接受的确切目标索引。结果里带一个**观测号**。
|
|
20
|
+
2. **选。** agent 从**那个观测**里挑一个操作和一个目标:`browser_act({ observation, operation, target })`。
|
|
21
|
+
3. **守。** 服务器把操作连同那个观测的指纹一起发给引擎。引擎先重读页面。页面变了就**什么都不执行**,结果以 `Nothing was executed:` 开头,并附上新的表。这是一次拒绝,不是错误:agent 从一件仍然为真的事上重新选。
|
|
22
|
+
4. **验。** 页面没变,那一个操作才执行,服务器返回新的观测号。下一步引用新的编号。工具结果本身从来不是结果的证明;下一次观测才是。
|
|
23
|
+
|
|
24
|
+
下面是一张真实的表,由随包的测试页面产生(可见文本那一行做了截断):
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Page file:///…/test/fixtures/fixture.html — 🐴 Forma · Find a place to slow down
|
|
28
|
+
Observation 1. Targets below are valid only for this observation.
|
|
29
|
+
Visible text: forma. Find a stay Reading room PLACES WITH CHARACTER THE SLOWER WAY TO STAY …
|
|
30
|
+
[1] link forma. — CLICK
|
|
31
|
+
[2] link Find a stay — CLICK
|
|
32
|
+
[3] link Reading room — CLICK
|
|
33
|
+
[4] searchbox Destination — TYPE_TEXT, CLICK
|
|
34
|
+
[5] button Find stays — CLICK
|
|
35
|
+
[6] combobox Stay category · "All stays" — SELECT
|
|
36
|
+
6:1 → Stay category → Design
|
|
37
|
+
6:2 → Stay category → Nature
|
|
38
|
+
6:3 → Stay category → Coastal
|
|
39
|
+
[7] checkbox Free cancellation · "on" (not checked) — CLICK
|
|
40
|
+
[8] button View Casa Flora — CLICK
|
|
41
|
+
[9] button View The Glasshouse — CLICK
|
|
42
|
+
[10] button View Serra Lodge — CLICK
|
|
43
|
+
Without a target: SCROLL_DOWN, WAIT
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
对这张表的一步是一个对象,不是代码:
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{ "observation": 1, "operation": "CLICK", "target": "5" } // button Find stays
|
|
50
|
+
{ "observation": 1, "operation": "TYPE_TEXT", "target": "4", "text": "Lisbon" }
|
|
51
|
+
{ "observation": 1, "operation": "SELECT", "target": "6:1" } // Stay category → Design
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
如果页面在读和点之间变了,回答是一次拒绝,并直接把当前观测交回来:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
Nothing was executed: The page changed since that observation. Choose from the new one.
|
|
58
|
+
|
|
59
|
+
Page … — …
|
|
60
|
+
Observation 3. Targets below are valid only for this observation.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
引擎通过本服务器会话持有的一条 CDP 连接读页面。每个工具——包括 `browser_eval`——都用这一条连接。自己再起第二个客户端(裸 websocket、puppeteer/playwright 脚本、另一个 `browser-harness` daemon)时,Chrome 144+ 的 “Allow remote debugging?” 提示(每个连接都要点一次)就会变成一堆弹窗,页面也会被两套循环同时驱动。
|
|
64
|
+
|
|
65
|
+
## 与基于选择器的浏览器 MCP 有什么不同
|
|
66
|
+
|
|
67
|
+
| | 基于选择器的浏览器 MCP | `browser-use-mcp` |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| 模型发送什么 | 选择器、坐标或代码 | 对刚读到的那张表里的一个索引做一个操作 |
|
|
70
|
+
| 页面变了会怎样 | 选择器可能点空,或点到错的元素 | 操作被**拒绝**,并返回最新的表 |
|
|
71
|
+
| 重试 | 由客户端或模型决定 | **从不**——拒绝就是重新选择的起点,不是自动重放 |
|
|
72
|
+
| 模型怎么知道能做什么 | 得猜 | 每一行写明该元素的目标与操作,下拉选项也在内 |
|
|
73
|
+
| CDP 连接 | 取决于实现 | 每个浏览器会话恰好一条,所有工具共用 |
|
|
74
|
+
| 每步 token 成本 | 常常是一张截图或整棵 DOM | 结构化文本:可见文本加元素表 |
|
|
75
|
+
|
|
76
|
+
`browser_eval` 是唯一一处有意的例外:它在同一条连接上求值一个 JavaScript 表达式,把结果当数据返回,并且**不产生观测**——所以下一次受保护的操作仍然指向上一张表。它存在的原因,是需要程序化读取的模型不必再起第二个 CDP 客户端。`--no-eval` 可以把它收回。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 快速开始
|
|
81
|
+
|
|
82
|
+
**需要** Node.js `^22.19.0 || >=24.0.0`、Chrome 或 Chromium,以及一个能拿到 Python 3.12+ 的途径:`PATH` 上的 `python3.12`–`python3.14`、`python3`(3.12 或更新),或者 [uv](https://docs.astral.sh/uv/)(它能自己下载 Python)。
|
|
83
|
+
|
|
84
|
+
**驱动哪个浏览器。** 默认是 **attach** 模式:服务器直接驱动你日常用的 Chrome,agent 看到的登录态和标签页就是你自己的。先在那个 Chrome 里打开一次远程调试(`chrome://inspect/#remote-debugging` → 允许);macOS 上还需要给运行服务器的应用**完全磁盘访问权限**(系统设置 → 隐私与安全性 → 完全磁盘访问权限)。想要一个隔离的浏览器,就加 `--mode launch`,服务器会在状态根下用自己的 profile 启动一个专属 Chrome。
|
|
85
|
+
|
|
86
|
+
这个包**还没有发布到 npm**——从 checkout 直接运行:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd /Users/ricardo.chen/PersonalGit/browser-use-mcp
|
|
90
|
+
npm install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
在 checkout 里执行 `npm link` 还会把 `browser-use-mcp` 命令放进 `PATH`,方便在终端里用;
|
|
94
|
+
GUI 客户端用下面给出的绝对路径更可靠。
|
|
95
|
+
|
|
96
|
+
然后检查这台机器(新机器上首次浏览器调用会构建环境;`install` 可以提前做)。下面的例子用
|
|
97
|
+
`--mode launch` 看专属浏览器;默认 attach 模式下 `Connection:` 行会写成你所连的 Chrome,且
|
|
98
|
+
`Status: ready` 要求那个浏览器能应答调试端点。输出大致如下,与机器相关的路径做了省略:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs doctor --mode launch
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
browser-use-mcp v0.1.0 — self-check (Node v22.23.2, darwin-arm64)
|
|
106
|
+
|
|
107
|
+
Transport: stdio (MCP over stdin/stdout)
|
|
108
|
+
Connection: launch Chrome/Chromium, profile "default" at /Users/you/.browser-use-mcp/profiles/default
|
|
109
|
+
State : /Users/you/.browser-use-mcp (managed Python environment)
|
|
110
|
+
Timeouts: 180s per browser request
|
|
111
|
+
Logging : info (stderr)
|
|
112
|
+
Jev : on, typesafe decider: default endpoint (model default), key unset; …
|
|
113
|
+
Tools : browser_open, browser_page, browser_act, browser_eval, browser_screenshot, browser_console, browser_goal, browser_close, browser_doctor
|
|
114
|
+
Session : idle (this transport has no browser session yet)
|
|
115
|
+
|
|
116
|
+
Engine : /Users/you/.browser-use-mcp/env/bin/python (Python 3.12.14, chosen by managed environment)
|
|
117
|
+
jev_ultrafast 0.1.1 at …/env/lib/python3.12/site-packages/jev_ultrafast
|
|
118
|
+
browser-harness 0.1.13
|
|
119
|
+
Install : none in this process; the engine is already importable
|
|
120
|
+
Sidecar : /…/browser-use-mcp/sidecar/bridge.py
|
|
121
|
+
Browser : /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
122
|
+
Goal mode: enabled: browser_goal and browser_act intents spend model quota; …
|
|
123
|
+
Status : ready
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`Status: ready` 表示浏览器工具可用。`Jev … NOT READY` 在没配策略密钥时是正常的:只有 `browser_goal` 和 `browser_act` 的 `intent` 需要它,其余功能照常。
|
|
127
|
+
|
|
128
|
+
想提前构建环境、而不是等第一次浏览器调用时再装:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs install
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
它会在 `<state root>/env`(默认 `~/.browser-use-mcp/env`)里建好环境,装上随包的引擎 wheel 与 `browser-harness`,并把构建记录写进 `<state root>/install.json`。可以放心重跑。安装在后台进行时,先到的浏览器调用会等它,而不是直接失败。
|
|
135
|
+
|
|
136
|
+
### 接到你的 agent 上
|
|
137
|
+
|
|
138
|
+
下面的绝对路径都来自这份 checkout——换成你自己克隆的位置。
|
|
139
|
+
|
|
140
|
+
Claude Code:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
claude mcp add --scope user browser-use \
|
|
144
|
+
--env BROWSER_USE_MCP_HEADLESS=1 \
|
|
145
|
+
-- node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
通用的 `mcpServers` JSON(Claude Desktop、Cursor 以及多数宿主都认这个形状):
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"browser-use": {
|
|
154
|
+
"command": "node",
|
|
155
|
+
"args": ["/Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs"],
|
|
156
|
+
"env": { "BROWSER_USE_MCP_HEADLESS": "1" }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Claude Code、Claude Desktop、Cursor、Codex、DSH 以及 Streamable HTTP 的可直接复制片段——包括“换成你自己的绝对路径”这条规则——都在 **[docs/clients.md](docs/clients.md)**。
|
|
163
|
+
|
|
164
|
+
然后让 agent 去 `browser_open` 一个 URL。新机器上的第一次调用可能要等一两分钟来装环境,之后的调用立即开始。
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 九个工具
|
|
169
|
+
|
|
170
|
+
| 工具 | 参数 | 做什么 |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `browser_open` | `url`(必填) | 启动(或复用)本会话的浏览器,打开页面,返回第一张动作空间表和观测号。 |
|
|
173
|
+
| `browser_page` | — | 重读当前页面,给出**新的**观测号。页面自己变了、或者操作被拒绝之后用它。 |
|
|
174
|
+
| `browser_act` | `observation`(必填)、`operation`、`target`、`text`、`intent` | 在那个观测上对一个目标做一个操作:`CLICK`、`TYPE_TEXT`、`SELECT`、`SCROLL_UP`、`SCROLL_DOWN`、`WAIT`。页面已变时返回拒绝和最新的表。Jev 开启时,只给 `intent`(不给 operation)就把这一步的决策交给策略。 |
|
|
175
|
+
| `browser_eval` | `expression`(必填)、`timeout_ms` | 在本会话的 CDP 连接上求值一个 JavaScript 表达式;把值当数据返回,不产生观测。`--no-eval` 时隐藏。 |
|
|
176
|
+
| `browser_screenshot` | — | 把可见视口作为 MCP 图片(JPEG)返回;不画目标框。`--no-screenshots` 时隐藏。 |
|
|
177
|
+
| `browser_console` | — | 上一次读取以来的 console 消息、未捕获异常、失败请求和 `4xx`/`5xx` 响应。每次读取都会清空缓冲。 |
|
|
178
|
+
| `browser_goal` | `url`、`goal` | 把整个目标交给策略,在它自己的标签页里跑;策略自己选每一步的操作和目标。会消耗模型配额。`--no-jev` 时隐藏。 |
|
|
179
|
+
| `browser_close` | — | 关闭本会话的标签页。launch 模式下浏览器留着给下一次 `browser_open`;attach 模式下绝不关闭你所连的浏览器。 |
|
|
180
|
+
| `browser_doctor` | `install`(可选布尔值) | 自检:解释器、引擎、浏览器、attach 目标、配置、会话,每个问题都带修法。`install: true` 会先构建缺失的环境。 |
|
|
181
|
+
|
|
182
|
+
缺策略密钥、目标不支持、`TYPE_TEXT` 没给文本、页面已变,这些都是**正常工具结果里的拒绝**。浏览器或环境坏了才是失败调用(`isError`)。agent 应该报告失败,而不是盲目重试。
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 传输与并发
|
|
187
|
+
|
|
188
|
+
| 传输 | 一个 agent 得到什么 | 两个 agent 得到什么 |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| **stdio**(默认) | 一个服务器进程、一个浏览器会话 | 两个进程:attach 模式下各开一个标签页驱动同一个浏览器,launch 模式下是两个浏览器 |
|
|
191
|
+
| **Streamable HTTP** | 一个 MCP 会话、一个浏览器会话 | 一个常驻服务器:attach 模式共用正在运行的浏览器,launch 模式下每个会话在专属浏览器里各用一个标签页 |
|
|
192
|
+
|
|
193
|
+
想多个 agent 共用一个装好的服务器时,起一个 HTTP 端点:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs \
|
|
197
|
+
--transport http --host 127.0.0.1 --port 7331 --path /mcp
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
端点默认是 `http://127.0.0.1:7331/mcp`。每个 MCP `initialize` 会话都有自己的浏览器会话;默认 attach 模式下它们都驱动你日常用的 Chrome(见下面的 `--attach-sharing`),`--mode launch` 则让每个会话在专属浏览器里各用一个标签页。静默 15 分钟的会话会被关掉浏览器,并删除它的匿名 profile(`BROWSER_USE_MCP_HTTP_IDLE_MS` 可以改这个时限)。它没有认证:默认只绑回环地址,绑到非回环地址时启动日志会给出警告。确实要离开本机,就放在你自己控制的隧道或反向代理后面。
|
|
201
|
+
|
|
202
|
+
**命名 profile(launch 模式)。** launch 模式下,匿名的 HTTP 会话拿到的是临时 profile,会话结束就删除。希望登录态跨重连保留的客户端,在 initialize 请求上给自己的 profile 起个名字:
|
|
203
|
+
|
|
204
|
+
```text
|
|
205
|
+
http://127.0.0.1:7331/mcp?profile=claude (或请求头 X-Browser-Use-Profile: claude)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
命名 profile 位于 `<状态根>/profiles/<服务器 profile>-<名字>`,会话结束后保留,同名的新会话会复用它。不同名字可以同时跑在各自 profile 上;两个同时存活的同名会话会被 profile 锁拒绝,而不是悄悄共用一个浏览器。名字是 1–32 个 `[A-Za-z0-9_-]` 字符。
|
|
209
|
+
|
|
210
|
+
### 浏览器:attach(默认)还是 launch
|
|
211
|
+
|
|
212
|
+
**`attach`(默认)**驱动你日常用的 Chrome:
|
|
213
|
+
|
|
214
|
+
- 不带端点时,服务器通过 Chrome 写进 profile 的 `DevToolsActivePort` 找到它。先在那个 Chrome 里打开远程调试(`chrome://inspect/#remote-debugging`)。macOS 上这个文件受隐私保护,所以运行本服务器的应用需要**完全磁盘访问权限**(系统设置 → 隐私与安全性 → 完全磁盘访问权限),然后重启它。
|
|
215
|
+
- **`--cdp-endpoint http://127.0.0.1:9333`**(或 `ws://…` 端点)驱动一个你指定、开着远程调试的浏览器。
|
|
216
|
+
- `--attach-sharing shared`(默认)允许同时有多个 MCP 会话——甚至多个服务器进程——驱动同一个浏览器,各自一个标签页、各自一条 CDP 连接。Chrome 144+ 对每条连接都会问一次 “Allow remote debugging?”,所以两个 agent 至少会有两次弹窗;每个只批准一次,没被批准的弹窗不要重试。`--attach-sharing exclusive` 则拒绝第二个持有者,并给出修法——当两个 agent 绝不能碰同一个浏览器时,这是更安全的选择。
|
|
217
|
+
- `browser_close` 只停本会话的标签页和 daemon,从不关闭所连的浏览器。
|
|
218
|
+
|
|
219
|
+
**`--mode launch`** 会在状态根下用自己的 profile 启动一个专属 Chrome,日常浏览器和登录态不进入自动化。`browser_close` 把这个浏览器留给下一次 `browser_open`,连接结束时服务器关闭它。无头 CI 机器,或者不该看到你真实会话的 agent,适合用这个模式。
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 配置要点
|
|
224
|
+
|
|
225
|
+
服务器设置都是一个 flag 配一个环境变量(策略用的是 `TYPESAFE_*`/`TEXT_MODEL_*`;少数几项只有环境变量);两者同时出现时 flag 生效。完整的表格、解析规则、默认值和退出码在 **[docs/config.md](docs/config.md)**。
|
|
226
|
+
|
|
227
|
+
| 设置 | Flag | 环境变量 | 默认值 |
|
|
228
|
+
| --- | --- | --- | --- |
|
|
229
|
+
| 传输 | `--transport`、`--host`、`--port`、`--path` | `BROWSER_USE_MCP_TRANSPORT`、`BROWSER_USE_MCP_HOST`、`BROWSER_USE_MCP_PORT`、`BROWSER_USE_MCP_PATH` | `stdio`、`127.0.0.1`、`7331`、`/mcp` |
|
|
230
|
+
| 自己启动或接入 | `--mode`、`--cdp-endpoint` | `BROWSER_USE_MCP_MODE`、`BROWSER_USE_MCP_CDP_ENDPOINT` | `attach` |
|
|
231
|
+
| attach 共享 | `--attach-sharing` | `BROWSER_USE_MCP_ATTACH_SHARING` | `shared`(仅 attach) |
|
|
232
|
+
| 浏览器窗口 | `--headless` | `BROWSER_USE_MCP_HEADLESS` | 显示窗口 |
|
|
233
|
+
| Profile | `--profile`、`--user-data-dir` | `BROWSER_USE_MCP_PROFILE`、`BROWSER_USE_MCP_USER_DATA_DIR` | 状态根下的 `default` |
|
|
234
|
+
| 状态根 | `--home` | `BROWSER_USE_MCP_HOME` | `~/.browser-use-mcp` |
|
|
235
|
+
| 解释器 | `--python` | `BROWSER_USE_MCP_PYTHON` | 托管环境 |
|
|
236
|
+
| 工具面 | `--no-eval`、`--no-screenshots`、`--no-jev` | `BROWSER_USE_MCP_NO_EVAL`、`…_NO_SCREENSHOTS`、`…_NO_JEV` | 全部提供 |
|
|
237
|
+
| 策略 | `--jev-decider` 及 `--typesafe-*` / `--text-model-*` | `JEV_DECIDER`、`TYPESAFE_*`、`TEXT_MODEL_*` | `typesafe` |
|
|
238
|
+
| 请求时限 | `--request-timeout-ms` | `BROWSER_USE_MCP_REQUEST_TIMEOUT_MS` | `180000` ms |
|
|
239
|
+
| 日志 | `--log-level` | `BROWSER_USE_MCP_LOG_LEVEL` | `info`,始终走 stderr |
|
|
240
|
+
|
|
241
|
+
状态根目录结构:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
~/.browser-use-mcp/
|
|
245
|
+
├── env/ 托管 Python 虚拟环境(引擎 + browser-harness)
|
|
246
|
+
├── install.json 装了什么,用的哪种策略、哪个 Python
|
|
247
|
+
├── profiles/<name>/ 启动的 Chrome profile(登录态、标签页);HTTP 命名 profile 也在这里
|
|
248
|
+
└── attachments/<hash>.lock attach 模式的 claim,每个 DevTools 端点一份
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Jev 策略
|
|
252
|
+
|
|
253
|
+
`browser_goal` 在引擎里跑完整个决策循环;`browser_act` 只给 `intent`(不给 operation)时,把恰好一步决策交给同一个策略。两者都会消耗模型配额。由谁回答是配置决定的:
|
|
254
|
+
|
|
255
|
+
- **`--jev-decider typesafe`**(默认):TypeSafe 端点上的 Jev。需要 `TYPESAFE_API_KEY`;可选 `TYPESAFE_BASE_URL`、`TYPESAFE_MODEL`、`TYPESAFE_FALLBACK_URL`。
|
|
256
|
+
- **`--jev-decider chat`**:引擎在回环地址上提供自己的 System One 协议,由一个聊天模型来回答。需要 `TEXT_MODEL_BASE_URL` 和 `TEXT_MODEL`(端点需要的话再加 `TEXT_MODEL_API_KEY`)。
|
|
257
|
+
|
|
258
|
+
`TEXT_MODEL_*` 也负责在 agent 只给 intent、没给文本时写出 `TYPE_TEXT` 的值。两种都不配,其余工具面完全不变;策略工具会以一句话拒绝,并点名要设的变量。`--no-jev` 会彻底收回 `browser_goal` 和 `intent` 参数。
|
|
259
|
+
|
|
260
|
+
把凭据放在客户端的 `env` 块(或 flag)里,不要放在 `args` 里:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
"env": {
|
|
264
|
+
"TYPESAFE_API_KEY": "…",
|
|
265
|
+
"TEXT_MODEL_BASE_URL": "https://your-endpoint/v1",
|
|
266
|
+
"TEXT_MODEL": "your-model",
|
|
267
|
+
"TEXT_MODEL_API_KEY": "…"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
服务器只在消耗配额的那一次请求里带上这些变量,不会把它们留在 sidecar 的环境里;doctor 只打印 `set`/`unset`,不打印值。
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 故障排查
|
|
276
|
+
|
|
277
|
+
**先跑 `browser_doctor`。** 工具和 CLI 跑的是同一份自检;每条问题都带着修它的命令。
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs doctor
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- **profile 已被占用。** 报错会点名 profile 和 `--profile`:停掉另一个服务器,或者给这个服务器一个自己的 `--profile <name>`。两个服务器驱动同一个浏览器 profile,是服务器会主动拒绝的唯一一种配置。
|
|
284
|
+
- **attach 模式找不到浏览器。** 这是默认模式:在你日常的 Chrome 里打开 `chrome://inspect/#remote-debugging` 并允许远程调试,或者用 `--mode launch` 让服务器起一个专属浏览器。doctor 会打印 `attach mode drives …` 和原因。
|
|
285
|
+
- **引擎还没装。** 首次使用会把它装进状态根;想提前装,就在 checkout 里跑 `node bin/browser-use-mcp.mjs install`,或者在 agent 里调 `browser_doctor` 并带上 `install: true`。走代理的话,给运行服务器的进程设 `HTTPS_PROXY`。
|
|
286
|
+
- **`Jev … NOT READY`。** 设 `TYPESAFE_API_KEY`(或用 `--jev-decider chat` 配 `TEXT_MODEL_*`)。浏览器工具不需要它。
|
|
287
|
+
- **客户端里看不到工具。** 客户端的 `env`/`args` 没送到服务器,或者改完没重启。在终端里跑同一条命令看 stderr;配置错误会以退出码 `2` 结束并点名出错的字段。
|
|
288
|
+
- **Chrome 弹 “Allow remote debugging?”。** 有东西起了第二个 CDP 客户端。只批准那一个弹窗一次,绝不重试;程序化的工作改用 `browser_eval`。
|
|
289
|
+
- **`browser_eval('document.body')` 返回 `{}`。** DOM 节点无法按值跨过 CDP 边界。改为返回可序列化的东西,比如 `document.body.innerText` 或 `JSON.stringify(document.body.getAttribute('class'))`。
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 安全说明
|
|
294
|
+
|
|
295
|
+
- **页面内容是不可信数据。** 表里引用的文本是拿来读的,不是拿来执行的指令。服务器绝不会替 agent 执行页面返回的任何东西。
|
|
296
|
+
- **`browser_eval` 按设计就是代码执行。** 它在页面里跑一个表达式,走本会话自己的连接,没有用户手势。想让模型输出永远不可能变成代码,就用 `--no-eval` 收回它;其余工具照常工作。
|
|
297
|
+
- **HTTP 传输没有认证。** 把它绑在回环地址上(默认如此)。任何非回环绑定都会在日志里给出警告;只在可信网络或你自己控制的隧道后面暴露它。
|
|
298
|
+
- **密钥不进日志、不进工具结果。** doctor 只打印 `set`/`unset`;凭据只交给消耗配额的那一次 sidecar 请求,不会被无关调用继承。
|
|
299
|
+
- **默认模式驱动的是你自己的 Chrome**,包括登录态——attach 模式就是为了这个。当 agent 应该在隔离浏览器里工作、profile 只属于状态根时,用 `--mode launch`。
|
|
300
|
+
- **一条连接,一个驱动。** 不要对同一个浏览器再起一个 CDP 客户端;Chrome 对每个连接单独授权,两个驱动会互相抢页面。
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## 许可证与致谢
|
|
305
|
+
|
|
306
|
+
MIT。见 [LICENSE](LICENSE) 和 [NOTICE](NOTICE)。
|
|
307
|
+
|
|
308
|
+
本项目建立在 [`dsh-browser-use`](https://github.com/ricardochen1996/dsh-browser-use)(MIT)之上:`sidecar/bridge.py` 来自它,动作空间渲染、新鲜度规则、“一条连接”规则和 direct mode 的工具描述也都沿袭自它。浏览器引擎是 [`jev-ultrafast`](https://github.com/ricardochen1996/jev-ultrafast)(MIT),以固定版本的 wheel 随包发布;浏览器接入是 [`browser-harness`](https://github.com/browser-use/browser-harness)(MIT)。设计与取舍见 [docs/architecture.md](docs/architecture.md)。
|
|
309
|
+
|
|
310
|
+
## 开发
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
npm test # 快速单元测试(node --test)
|
|
314
|
+
npm run test:e2e # 真实 headless Chrome 跑真实 MCP 协议
|
|
315
|
+
# (stdio、HTTP、attach 模式、用回环 stub 验证的 Jev intent、
|
|
316
|
+
# 以及手写 JSON-RPC 客户端)
|
|
317
|
+
npm run release:check # 校验版本、CHANGELOG 与真正会被打包的文件清单
|
|
318
|
+
npm run release:pack # 构建 tarball、校验内容并把 integrity 记录到 dist/
|
|
319
|
+
npm run doctor # CLI 自检
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
默认测试不花模型调用、不需要任何凭据;e2e 测试首次运行会把引擎装进一个测试用的状态根。版本历史见 [CHANGELOG.md](CHANGELOG.md)。
|
|
323
|
+
|
|
324
|
+
**发布**:在 `main` 上打标注 tag `vX.Y.Z`,[release 工作流](.github/workflows/release.yml) 会校验 tag 与 `package.json` 一致、重跑 CI(单元 + 真浏览器 e2e)、用仓库 secret `NPM_TOKEN` 带 provenance 发布到 npm,并创建 GitHub Release。流程是幂等的:已发布的版本会被跳过,不会让重跑失败。
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `browser-use-mcp` — the executable.
|
|
4
|
+
*
|
|
5
|
+
* One command line, then one of three things: serve MCP (stdio by default, Streamable HTTP on
|
|
6
|
+
* request), print the doctor's self-check, or build the managed Python environment. Only the last two
|
|
7
|
+
* and `--help/--version` write to stdout; while serving MCP, stdout is the protocol and every log
|
|
8
|
+
* line goes to stderr.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ConfigError, resolveConfig } from '../lib/config.js'
|
|
12
|
+
import { packageInfo, parseCommandLine, USAGE } from '../lib/cli.js'
|
|
13
|
+
import { doctorReport } from '../lib/doctor.js'
|
|
14
|
+
import { ensureEngine, reportText } from '../lib/engine.js'
|
|
15
|
+
import { serveHttp } from '../lib/http.js'
|
|
16
|
+
import { createLogger } from '../lib/log.js'
|
|
17
|
+
import { serveStdio } from '../lib/server.js'
|
|
18
|
+
import { toolNames } from '../lib/tools.js'
|
|
19
|
+
|
|
20
|
+
const info = packageInfo()
|
|
21
|
+
|
|
22
|
+
/** CLI output; never called while MCP is being served. */
|
|
23
|
+
function print(line) {
|
|
24
|
+
process.stdout.write(`${line}\n`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Run one command line and return the process exit code. */
|
|
28
|
+
async function main(argv) {
|
|
29
|
+
const parsed = parseCommandLine(argv)
|
|
30
|
+
if (parsed.help) {
|
|
31
|
+
print(USAGE)
|
|
32
|
+
return 0
|
|
33
|
+
}
|
|
34
|
+
if (parsed.version) {
|
|
35
|
+
print(info.version)
|
|
36
|
+
return 0
|
|
37
|
+
}
|
|
38
|
+
const config = resolveConfig({ command: parsed.command, flags: parsed.flags })
|
|
39
|
+
const logger = createLogger(config.logLevel)
|
|
40
|
+
|
|
41
|
+
if (config.command === 'install') {
|
|
42
|
+
logger.info(`building the browser engine environment in ${config.browserUseMcpHome}`)
|
|
43
|
+
const report = await ensureEngine(config, {
|
|
44
|
+
install: true,
|
|
45
|
+
fresh: true,
|
|
46
|
+
onOutput: line => logger.info(line),
|
|
47
|
+
})
|
|
48
|
+
print(reportText(report))
|
|
49
|
+
return report.ok ? 0 : 1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (config.command === 'doctor') {
|
|
53
|
+
const result = await doctorReport({ config, tools: toolNames(config), version: info.version, fresh: true })
|
|
54
|
+
print(result.text)
|
|
55
|
+
return result.ok ? 0 : 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.transport === 'stdio') {
|
|
59
|
+
await serveStdio(config, logger)
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
await serveHttp(config, logger)
|
|
63
|
+
return 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// An unhandled rejection must not vanish: the browser is a child process, and a silent one is how a
|
|
67
|
+
// tool call "hangs" with no explanation. It is logged to stderr (stdout is the protocol) and the
|
|
68
|
+
// server keeps serving — a rejected background task is not a reason to drop every other connection.
|
|
69
|
+
process.on('unhandledRejection', error => {
|
|
70
|
+
process.stderr.write(`browser-use-mcp unhandled rejection: ${error?.stack ?? error}\n`)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
process.exitCode = await main(process.argv.slice(2))
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof ConfigError) {
|
|
77
|
+
process.stderr.write(`browser-use-mcp: configuration error: ${error.message}\n`)
|
|
78
|
+
process.exitCode = 2
|
|
79
|
+
} else {
|
|
80
|
+
process.stderr.write(`browser-use-mcp: ${error?.stack ?? error}\n`)
|
|
81
|
+
process.exitCode = 1
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
`browser-use-mcp` is an MCP server. An agent — Claude Code, Codex, Cursor, a DeepSeek Harness
|
|
4
|
+
conversation, or any other MCP client — connects to it, and the agent gets a browser it can see and
|
|
5
|
+
click. This document explains how the pieces fit, what travels on the wire, and why the design is
|
|
6
|
+
shaped the way it is.
|
|
7
|
+
|
|
8
|
+
It is built on [`dsh-browser-use`](https://github.com/ricardochen1996/dsh-browser-use): the same
|
|
9
|
+
Python engine, the same sidecar protocol, the same page-as-indexed-action-space idea, with the
|
|
10
|
+
DeepSeek Harness plugin host replaced by an MCP host.
|
|
11
|
+
|
|
12
|
+
## The shape
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
┌──────────────────┐ MCP (stdio or Streamable HTTP) ┌──────────────────────────────────┐
|
|
16
|
+
│ agent / client │ ◄────────────────────────────────────────────► │ browser-use-mcp (Node) │
|
|
17
|
+
│ (any MCP host) │ tools/list, tools/call, content blocks │ lib/server.js lib/tools.js │
|
|
18
|
+
└──────────────────┘ │ lib/session.js lib/http.js │
|
|
19
|
+
└──────────┬───────────────────────┘
|
|
20
|
+
│ one JSON object per line
|
|
21
|
+
│ over stdin/stdout
|
|
22
|
+
┌──────────▼───────────────────────┐
|
|
23
|
+
│ sidecar/bridge.py (Python, │
|
|
24
|
+
│ vendored from dsh-browser-use) │
|
|
25
|
+
└──────────┬───────────────────────┘
|
|
26
|
+
│ imports
|
|
27
|
+
┌──────────▼───────────────────────┐
|
|
28
|
+
│ jev_ultrafast (bundled wheel) │
|
|
29
|
+
│ dynamic indexed action space, │
|
|
30
|
+
│ guards, executor │
|
|
31
|
+
└──────────┬───────────────────────┘
|
|
32
|
+
│ browser_harness
|
|
33
|
+
┌──────────▼───────────────────────┐
|
|
34
|
+
│ Chrome / Chromium over CDP │
|
|
35
|
+
└──────────────────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Four processes at most: the agent, this MCP server, one Python sidecar per browser session, and the
|
|
39
|
+
browser. The sidecar is the only process that imports the engine; this server only speaks to it, and
|
|
40
|
+
the browser only ever sees the single CDP connection the sidecar's daemon owns.
|
|
41
|
+
|
|
42
|
+
## One step, end to end
|
|
43
|
+
|
|
44
|
+
1. The agent calls `browser_open` (or `browser_page`) and receives the page as text:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Page https://example.com/search — Example
|
|
48
|
+
Observation 3. Targets below are valid only for this observation.
|
|
49
|
+
Visible text: Find a place to slow down
|
|
50
|
+
[1] textbox Destination · "Lisbon" — TYPE_TEXT
|
|
51
|
+
[2] checkbox Free cancellation (partly checked) — CLICK
|
|
52
|
+
[3] combobox Category — SELECT
|
|
53
|
+
3:1 → Design
|
|
54
|
+
3:2 → Wellness
|
|
55
|
+
[4] button Find stays (collapsed) — CLICK
|
|
56
|
+
Without a target: SCROLL_UP, SCROLL_DOWN, WAIT
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. The agent calls `browser_act({ observation: 3, operation: "CLICK", target: "4" })`. The target is
|
|
60
|
+
an index into observation 3 — never a selector, a coordinate, or code.
|
|
61
|
+
|
|
62
|
+
3. This server looks up the fingerprint it recorded for observation 3 and sends
|
|
63
|
+
`{operation, target, fingerprint}` to the sidecar.
|
|
64
|
+
|
|
65
|
+
4. The engine re-reads the page. If the fingerprint changed, it executes **nothing** and answers
|
|
66
|
+
`{kind: "stale", page: <new observation>}`. This server turns that into a tool result that starts
|
|
67
|
+
with `Nothing was executed:` and includes the fresh table — the agent chooses again from something
|
|
68
|
+
that is true. No change is ever retried automatically, so there are no double clicks or double
|
|
69
|
+
submits.
|
|
70
|
+
|
|
71
|
+
5. If the fingerprint matches, the engine executes the one operation, waits for the page to settle,
|
|
72
|
+
and answers with the new observation. This server numbers it, records its fingerprint, and returns
|
|
73
|
+
the new table. The agent's next step refers to the new number.
|
|
74
|
+
|
|
75
|
+
`browser_goal` is the same loop run to completion inside the engine: the policy picks every operation
|
|
76
|
+
and target itself until it reports `DONE` or `BLOCKED`. `browser_act` with an `intent` and no
|
|
77
|
+
operation delegates exactly one decision to the same policy.
|
|
78
|
+
|
|
79
|
+
## Why an action space instead of selectors
|
|
80
|
+
|
|
81
|
+
Playwright-style tools make the model write selectors. A selector can be wrong before it is used,
|
|
82
|
+
cannot be validated against the page it will run on, and fails silently when the page changes. Here
|
|
83
|
+
the model can only name something the server just observed, and the engine refuses a name whose
|
|
84
|
+
observation no longer matches. The trade is that every step needs an observation in the model's
|
|
85
|
+
context; the gain is that a model mistake is a refused index rather than a click on the wrong button.
|
|
86
|
+
|
|
87
|
+
`browser_eval` is the deliberate exception: one JavaScript expression, run over the same single CDP
|
|
88
|
+
connection, whose result is data and never a decision. It produces no observation, so the next
|
|
89
|
+
guarded action still refers to the last observation. It can be withdrawn with `--no-eval`.
|
|
90
|
+
|
|
91
|
+
## One connection, one driver
|
|
92
|
+
|
|
93
|
+
Chrome 144+ asks "Allow remote debugging?" once per CDP connection. Every client that connects is
|
|
94
|
+
another prompt, and an unanswered prompt is only kept alive by the client that raised it — retrying
|
|
95
|
+
drops the sheet and raises a new one. That is why nothing in this package starts a second client:
|
|
96
|
+
the sidecar's Browser Harness daemon is the only one. `browser_eval` runs through it too. The tool
|
|
97
|
+
descriptions and the server instructions repeat the rule, because a model that "cannot find a tool"
|
|
98
|
+
for a programmatic read is exactly the model that starts a websocket.
|
|
99
|
+
|
|
100
|
+
The default mode is **attach**: the server drives the Chrome you already use (or the browser a
|
|
101
|
+
`--cdp-endpoint` names). That is one session, one daemon, one connection — the rule still holds. The
|
|
102
|
+
opt-in in the other direction is `--mode launch`, which starts a dedicated Chrome with its own
|
|
103
|
+
profile directory, so the browser you use is never touched and no prompt appears at all.
|
|
104
|
+
|
|
105
|
+
## Sessions and isolation
|
|
106
|
+
|
|
107
|
+
An MCP connection is a session, and a session owns a browser session: its own sidecar, its own
|
|
108
|
+
DevTools connection, its own tab, and — in launch mode — its own profile directory.
|
|
109
|
+
|
|
110
|
+
| Transport | What one agent gets | What two agents get |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| stdio (default) | one server process, one browser session | two processes; in attach mode both drive the same browser in their own tabs, in launch mode two browsers |
|
|
113
|
+
| Streamable HTTP | one MCP session, one browser session | one long-running server; attach mode shares the running browser, launch mode gives each session its own tab of a dedicated browser |
|
|
114
|
+
|
|
115
|
+
**Attach mode (default)** means every session drives the same browser you already use. That browser
|
|
116
|
+
is shared by default (`--attach-sharing shared`): each session gets its own sidecar, daemon and tab,
|
|
117
|
+
and Chrome asks "Allow remote debugging?" once per connection, so N live sessions mean N prompts.
|
|
118
|
+
`--attach-sharing exclusive` claims the DevTools endpoint through a lock file under
|
|
119
|
+
`<state root>/attachments/` and refuses the second live holder with a fix.
|
|
120
|
+
|
|
121
|
+
**Launch mode** gives every session a browser this server owns. Two servers configured with the
|
|
122
|
+
**same profile** are refused rather than silently sharing a browser: the first holds a lock file in
|
|
123
|
+
the profile directory, and the second gets a tool error naming `--profile`. A lock left by a crashed
|
|
124
|
+
process is reclaimed automatically.
|
|
125
|
+
|
|
126
|
+
**Named profiles.** In launch mode an anonymous HTTP session's profile
|
|
127
|
+
(`profiles/<profile>-<session suffix>`) is removed when the session ends. A client that names its
|
|
128
|
+
profile (`/mcp?profile=<name>` or `X-Browser-Use-Profile: <name>` on the initialize request) gets the
|
|
129
|
+
stable `profiles/<profile>-<name>` instead: it survives reconnects, its logins with it, and two live
|
|
130
|
+
sessions with the same name are refused by the profile lock rather than silently sharing one
|
|
131
|
+
browser. This is how several agents share one HTTP endpoint while each keeps its own logins.
|
|
132
|
+
|
|
133
|
+
The HTTP transport closes a session's browser after 15 minutes of silence
|
|
134
|
+
(`BROWSER_USE_MCP_HTTP_IDLE_MS` overrides that), so a client that disappears does not leave Chrome
|
|
135
|
+
running forever. Named profiles are kept; only anonymous session directories are removed.
|
|
136
|
+
|
|
137
|
+
## The Python environment
|
|
138
|
+
|
|
139
|
+
The engine is a Python package with Python dependencies; `npm install` cannot build one. So this
|
|
140
|
+
package carries what it needs:
|
|
141
|
+
|
|
142
|
+
- `vendor/jev_ultrafast-0.1.1-py3-none-any.whl` — the engine, built from a pinned upstream revision
|
|
143
|
+
(`vendor/jev-ultrafast.json`).
|
|
144
|
+
- `vendor/requirements.txt` — the sidecar's dependencies (`browser-harness`, `cdp-use`, …), pinned by
|
|
145
|
+
version *and* by the hash of every artifact.
|
|
146
|
+
|
|
147
|
+
On first use it finds a Python 3.12+ on the machine (or uses `uv`, which can fetch one), creates
|
|
148
|
+
`<state root>/env`, and installs the requirements with `uv pip` (or `pip --require-hashes` without
|
|
149
|
+
uv). One install at a time, across processes, via a lock in the state root. The environment is probed
|
|
150
|
+
by importing the engine, and `install.json` records what was built.
|
|
151
|
+
|
|
152
|
+
The state root is `${BROWSER_USE_MCP_HOME:-~/.browser-use-mcp}`:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
~/.browser-use-mcp/
|
|
156
|
+
├── env/ managed Python virtualenv (engine + browser-harness)
|
|
157
|
+
├── install.json what was installed, with which strategy and Python
|
|
158
|
+
├── profiles/<name>/ launched and named profiles (logins, tabs)
|
|
159
|
+
└── attachments/<hash>.lock exclusive attach-mode claims, one per DevTools endpoint
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`browser-use-mcp install` builds the environment eagerly; `browser-use-mcp doctor` reports it without
|
|
163
|
+
building anything. Serve mode starts the check in the background while the agent reads its
|
|
164
|
+
instructions, so a first call rarely waits for it.
|
|
165
|
+
|
|
166
|
+
## The Jev policy (optional)
|
|
167
|
+
|
|
168
|
+
`browser_goal` and `browser_act`'s `intent` spend model quota. Who answers them is configuration:
|
|
169
|
+
|
|
170
|
+
- `--jev-decider typesafe` (default): Jev, the System One policy at the TypeSafe endpoint, needs
|
|
171
|
+
`TYPESAFE_API_KEY` (and optionally `TYPESAFE_BASE_URL` / `TYPESAFE_MODEL`).
|
|
172
|
+
- `--jev-decider chat`: the engine serves the System One protocol on loopback and a chat model
|
|
173
|
+
answers it; needs `TEXT_MODEL_BASE_URL` and `TEXT_MODEL` (plus `TEXT_MODEL_API_KEY` for the
|
|
174
|
+
endpoint).
|
|
175
|
+
|
|
176
|
+
`TEXT_MODEL_*` is also what writes a `TYPE_TEXT` value when the agent gives an intent but no text.
|
|
177
|
+
Without any of this configured, the rest of the tool surface works exactly the same; the policy
|
|
178
|
+
tools refuse with one sentence naming the variable to set.
|
|
179
|
+
|
|
180
|
+
The engine variables travel with each individual request that spends quota and are never left in the
|
|
181
|
+
server's environment or the sidecar's, so a stale key cannot leak into an unrelated call.
|
|
182
|
+
|
|
183
|
+
## Failure semantics
|
|
184
|
+
|
|
185
|
+
- A **refusal** is a normal tool result: nothing executed, plus the table to choose again from.
|
|
186
|
+
Stale pages, unsupported targets, missing text and unconfigured policy are refusals.
|
|
187
|
+
- A **failed call** (`isError: true`) is used when the browser or its environment is the problem:
|
|
188
|
+
engine not installed, profile busy, sidecar died, browser error. A model should report these
|
|
189
|
+
rather than retry them blindly.
|
|
190
|
+
- A **cancelled** call is text saying the browser may still finish what it started: the page may have
|
|
191
|
+
changed, so the next step must read it again.
|
|
192
|
+
- Page content is untrusted data. The server instructions say so, and nothing returned from a page
|
|
193
|
+
is executed on the server's behalf.
|
|
194
|
+
|
|
195
|
+
## What is reused, and what is not
|
|
196
|
+
|
|
197
|
+
Reused from `dsh-browser-use` (MIT, see [NOTICE](../NOTICE)):
|
|
198
|
+
|
|
199
|
+
- `sidecar/bridge.py`, the line-delimited JSON sidecar — behaviour unchanged.
|
|
200
|
+
- The engine (`jev_ultrafast`) and its `browser-harness` connection layer, as the bundled wheel.
|
|
201
|
+
- The action-space rendering, the freshness rule, the "one connection" rule, the exact operation set,
|
|
202
|
+
and the direct-mode tool descriptions.
|
|
203
|
+
- The provisioning approach: bundled wheel + hash-pinned requirements into a managed virtualenv.
|
|
204
|
+
|
|
205
|
+
Not part of this project, because an MCP client already is the agent:
|
|
206
|
+
|
|
207
|
+
- the delegated browser subagent (`browser_task`) and its progress feed;
|
|
208
|
+
- the DSH inspector/side panel and the session-level browser ownership of agents;
|
|
209
|
+
- the DSH configuration system (Cordis overlay, settings page, credentials service) — replaced by
|
|
210
|
+
flags and environment variables that any MCP client can pass.
|