@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.md
CHANGED
|
@@ -1,3 +1,453 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ricardochen1996/browser-use-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/ricardochen1996/browser-use-mcp/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
**English** · [中文](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
> An MCP server that gives **any agent** a browser it can *see and click*: a page is read as an
|
|
8
|
+
> **indexed action-space table**, and every step is one guarded operation on a target that came
|
|
9
|
+
> from that table.
|
|
10
|
+
|
|
11
|
+
`browser-use-mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server. Claude
|
|
12
|
+
Code, Claude Desktop, Cursor, Codex, a DeepSeek Harness conversation, or any other MCP client
|
|
13
|
+
connects to it, and its agent gets a browser: open a page, read the page as a table of numbered
|
|
14
|
+
targets, then name one operation on one target. The model never writes a selector, a coordinate, or
|
|
15
|
+
a script, and the server never retries a change by itself.
|
|
16
|
+
|
|
17
|
+
It is built on [`dsh-browser-use`](https://github.com/ricardochen1996/dsh-browser-use): the same
|
|
18
|
+
Python engine, the same sidecar protocol, the same page-as-indexed-action-space idea — with the
|
|
19
|
+
DeepSeek Harness plugin host replaced by an MCP host. So there is no browser logic of its own: this
|
|
20
|
+
package is the adapter that lets any MCP client drive the [Jev Ultrafast](https://github.com/ricardochen1996/jev-ultrafast)
|
|
21
|
+
engine. The engine and [`browser-harness`](https://github.com/browser-use/browser-harness) ship as a
|
|
22
|
+
pinned wheel plus hash-pinned requirements, and are installed into a managed Python environment on
|
|
23
|
+
first use. No engine clone, no manual Python setup.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## What it is
|
|
28
|
+
|
|
29
|
+
The browser tools expose one loop:
|
|
30
|
+
|
|
31
|
+
1. **Read.** `browser_open` downloads the page and returns its **action space** as text: one line
|
|
32
|
+
per reachable element, the operations that element supports, and the exact target indices those
|
|
33
|
+
operations accept. The result carries an **observation number**.
|
|
34
|
+
2. **Choose.** The agent picks one operation and one target *from that observation*:
|
|
35
|
+
`browser_act({ observation, operation, target })`.
|
|
36
|
+
3. **Guard.** The server sends the operation together with the fingerprint of the observation it
|
|
37
|
+
came from. The engine re-reads the page first. If the page changed, **nothing is executed** and
|
|
38
|
+
the result starts with `Nothing was executed:` followed by the fresh table. That is a refusal,
|
|
39
|
+
not an error: the agent chooses again from something that is true.
|
|
40
|
+
4. **Verify.** If the page matched, the one operation runs and the server returns the new
|
|
41
|
+
observation. The next step refers to the new number. A tool result is never proof of an
|
|
42
|
+
outcome; the next observation is.
|
|
43
|
+
|
|
44
|
+
Here is a real table, produced by the bundled test fixture (the visible-text line is clipped):
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
Page file:///…/test/fixtures/fixture.html — 🐴 Forma · Find a place to slow down
|
|
48
|
+
Observation 1. Targets below are valid only for this observation.
|
|
49
|
+
Visible text: forma. Find a stay Reading room PLACES WITH CHARACTER THE SLOWER WAY TO STAY …
|
|
50
|
+
[1] link forma. — CLICK
|
|
51
|
+
[2] link Find a stay — CLICK
|
|
52
|
+
[3] link Reading room — CLICK
|
|
53
|
+
[4] searchbox Destination — TYPE_TEXT, CLICK
|
|
54
|
+
[5] button Find stays — CLICK
|
|
55
|
+
[6] combobox Stay category · "All stays" — SELECT
|
|
56
|
+
6:1 → Stay category → Design
|
|
57
|
+
6:2 → Stay category → Nature
|
|
58
|
+
6:3 → Stay category → Coastal
|
|
59
|
+
[7] checkbox Free cancellation · "on" (not checked) — CLICK
|
|
60
|
+
[8] button View Casa Flora — CLICK
|
|
61
|
+
[9] button View The Glasshouse — CLICK
|
|
62
|
+
[10] button View Serra Lodge — CLICK
|
|
63
|
+
Without a target: SCROLL_DOWN, WAIT
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A step against it is one object, not code:
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
{ "observation": 1, "operation": "CLICK", "target": "5" } // button Find stays
|
|
70
|
+
{ "observation": 1, "operation": "TYPE_TEXT", "target": "4", "text": "Lisbon" }
|
|
71
|
+
{ "observation": 1, "operation": "SELECT", "target": "6:1" } // Stay category → Design
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If the page moved between the read and the click, the answer is a refusal that hands back the
|
|
75
|
+
current observation:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Nothing was executed: The page changed since that observation. Choose from the new one.
|
|
79
|
+
|
|
80
|
+
Page … — …
|
|
81
|
+
Observation 3. Targets below are valid only for this observation.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The engine reads a page over a single CDP connection that this server's session owns. Every tool —
|
|
85
|
+
`browser_eval` included — uses that one connection. Starting a second client yourself (a raw
|
|
86
|
+
websocket, a puppeteer/playwright script, another `browser-harness` daemon) is how Chrome 144+'s
|
|
87
|
+
per-connection "Allow remote debugging?" prompt turns into a stack of prompts, and how two loops end
|
|
88
|
+
up driving one page.
|
|
89
|
+
|
|
90
|
+
## How it differs from selector-based browser MCPs
|
|
91
|
+
|
|
92
|
+
| | Selector-based browser MCP | `browser-use-mcp` |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| What the model sends | A selector, a coordinate, or code | One operation on an index from the table it just read |
|
|
95
|
+
| What happens when the page moved | The selector may miss, or hit the wrong element | The action is **refused**, and the fresh table is returned |
|
|
96
|
+
| Retries | Up to the client or the model | **Never** — a refusal is the retry point, not an automatic replay |
|
|
97
|
+
| What the model knows is possible | It has to guess | Each line lists that element's targets and operations, dropdown options included |
|
|
98
|
+
| CDP connections | Depends on the implementation | Exactly one per browser session, shared by every tool |
|
|
99
|
+
| Token cost per step | Often a screenshot or the whole DOM | Structured text: visible text plus the element table |
|
|
100
|
+
|
|
101
|
+
`browser_eval` is the one deliberate exception: it evaluates a single JavaScript expression over
|
|
102
|
+
the same connection, returns a value as data, and produces **no observation** — so the next guarded
|
|
103
|
+
action still refers to the last table. It exists so a model that needs a programmatic read does not
|
|
104
|
+
start a second CDP client. `--no-eval` withdraws it.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quick start
|
|
109
|
+
|
|
110
|
+
**Requires** Node.js `^22.19.0 || >=24.0.0`, Chrome or Chromium, and a way to get Python 3.12+:
|
|
111
|
+
either `python3.12`–`python3.14`, `python3` (3.12 or newer) on `PATH`, or [uv](https://docs.astral.sh/uv/),
|
|
112
|
+
which can fetch the Python itself.
|
|
113
|
+
|
|
114
|
+
**Which browser it drives.** The default mode is **attach**: the server drives the Chrome you already
|
|
115
|
+
use, so its logins and tabs are the ones the agent sees. Enable remote debugging once in that Chrome
|
|
116
|
+
(`chrome://inspect/#remote-debugging` → allow); on macOS the application that runs the server also
|
|
117
|
+
needs **Full Disk Access** (System Settings → Privacy & Security → Full Disk Access). Prefer an
|
|
118
|
+
isolated browser? Pass `--mode launch` and the server starts its own Chrome with a profile under the
|
|
119
|
+
state root instead.
|
|
120
|
+
|
|
121
|
+
The package is **not published to npm yet** — run it from a checkout:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd /Users/ricardo.chen/PersonalGit/browser-use-mcp
|
|
125
|
+
npm install
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`npm link` in the checkout additionally puts a `browser-use-mcp` command on your `PATH` for terminal
|
|
129
|
+
use; GUI clients are more reliable with the absolute path shown below.
|
|
130
|
+
|
|
131
|
+
Then check the machine (on a fresh machine the first browser call builds the environment;
|
|
132
|
+
`install` does it eagerly). Its output looks like this, with machine-specific paths shortened:
|
|
133
|
+
|
|
134
|
+
The example below asks for a dedicated browser (`--mode launch`); with the default attach mode the
|
|
135
|
+
`Connection:` line names the Chrome you browse with instead, and `Status: ready` requires that
|
|
136
|
+
browser to answer its debugging endpoint.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs doctor --mode launch
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
browser-use-mcp v0.1.0 — self-check (Node v22.23.2, darwin-arm64)
|
|
144
|
+
|
|
145
|
+
Transport: stdio (MCP over stdin/stdout)
|
|
146
|
+
Connection: launch Chrome/Chromium, profile "default" at /Users/you/.browser-use-mcp/profiles/default
|
|
147
|
+
State : /Users/you/.browser-use-mcp (managed Python environment)
|
|
148
|
+
Timeouts: 180s per browser request
|
|
149
|
+
Logging : info (stderr)
|
|
150
|
+
Jev : on, typesafe decider: default endpoint (model default), key unset; …
|
|
151
|
+
Tools : browser_open, browser_page, browser_act, browser_eval, browser_screenshot, browser_console, browser_goal, browser_close, browser_doctor
|
|
152
|
+
Session : idle (this transport has no browser session yet)
|
|
153
|
+
|
|
154
|
+
Engine : /Users/you/.browser-use-mcp/env/bin/python (Python 3.12.14, chosen by managed environment)
|
|
155
|
+
jev_ultrafast 0.1.1 at …/env/lib/python3.12/site-packages/jev_ultrafast
|
|
156
|
+
browser-harness 0.1.13
|
|
157
|
+
Install : none in this process; the engine is already importable
|
|
158
|
+
Sidecar : /…/browser-use-mcp/sidecar/bridge.py
|
|
159
|
+
Browser : /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
|
160
|
+
Goal mode: enabled: browser_goal and browser_act intents spend model quota; …
|
|
161
|
+
Status : ready
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`Status: ready` means the browser tools work. `Jev … NOT READY` is expected until a policy key is
|
|
165
|
+
configured: only `browser_goal` and `browser_act`'s `intent` need one; everything else works.
|
|
166
|
+
|
|
167
|
+
To build the environment ahead of time instead of during the first browser call:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs install
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
That creates `<state root>/env` (default `~/.browser-use-mcp/env`) with the bundled engine wheel and
|
|
174
|
+
`browser-harness`, and records what it built in `<state root>/install.json`. It is safe to re-run.
|
|
175
|
+
A browser tool that runs before the install finished waits for it instead of failing.
|
|
176
|
+
|
|
177
|
+
### Put it in front of your agent
|
|
178
|
+
|
|
179
|
+
Every absolute path below is this checkout's — substitute where you cloned it.
|
|
180
|
+
|
|
181
|
+
Claude Code:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Use the Chrome you already browse with (the default attach mode):
|
|
185
|
+
claude mcp add --scope user browser-use \
|
|
186
|
+
-- node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs
|
|
187
|
+
|
|
188
|
+
# Or give the agent a dedicated, unseen browser instead:
|
|
189
|
+
claude mcp add --scope user browser-use \
|
|
190
|
+
--env BROWSER_USE_MCP_MODE=launch --env BROWSER_USE_MCP_HEADLESS=1 \
|
|
191
|
+
-- node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Generic `mcpServers` JSON (Claude Desktop, Cursor, and most hosts accept this shape):
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"mcpServers": {
|
|
199
|
+
"browser-use": {
|
|
200
|
+
"command": "node",
|
|
201
|
+
"args": ["/Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs"],
|
|
202
|
+
"env": { "BROWSER_USE_MCP_HEADLESS": "1" }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Copy-pasteable snippets for Claude Code, Claude Desktop, Cursor, Codex, DSH and Streamable HTTP —
|
|
209
|
+
including the absolute-path substitution rule — are in **[docs/clients.md](docs/clients.md)**.
|
|
210
|
+
|
|
211
|
+
Then ask the agent to `browser_open` a URL. A first call on a fresh machine may take a minute or two
|
|
212
|
+
while the environment is installed; later calls start at once.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## The nine tools
|
|
217
|
+
|
|
218
|
+
| Tool | Parameters | What it does |
|
|
219
|
+
| --- | --- | --- |
|
|
220
|
+
| `browser_open` | `url` (required) | Start (or reuse) the session's browser, open the page, return the first action-space table and observation number. |
|
|
221
|
+
| `browser_page` | — | Re-read the current page under a **new** observation number. Use it when the page changed on its own, or after a refusal. |
|
|
222
|
+
| `browser_act` | `observation` (required), `operation`, `target`, `text`, `intent` | Run one operation on one target from that observation: `CLICK`, `TYPE_TEXT`, `SELECT`, `SCROLL_UP`, `SCROLL_DOWN`, `WAIT`. A stale page is refused with the fresh table. With Jev on, an `intent` (and no operation) delegates that one decision to the policy. |
|
|
223
|
+
| `browser_eval` | `expression` (required), `timeout_ms` | Evaluate one JavaScript expression over the session's CDP connection; returns a value as data and produces no observation. Hidden by `--no-eval`. |
|
|
224
|
+
| `browser_screenshot` | — | The visible viewport as an MCP image (JPEG). Target boxes are not drawn into it. Hidden by `--no-screenshots`. |
|
|
225
|
+
| `browser_console` | — | Console messages, uncaught exceptions, failed requests and `4xx`/`5xx` responses since the last read. Each read drains the buffer. |
|
|
226
|
+
| `browser_goal` | `url`, `goal` | Hand one goal to the policy in its own tab; it chooses every operation and target itself. Spends model quota. Hidden by `--no-jev`. |
|
|
227
|
+
| `browser_close` | — | Close the session's tab. In launch mode the browser stays for the next `browser_open`; in attach mode the attached browser is never closed. |
|
|
228
|
+
| `browser_doctor` | `install` (optional bool) | Self-check: interpreter, engine, browser, attach target, config, live session, and a fix per problem. `install: true` builds a missing environment first. |
|
|
229
|
+
|
|
230
|
+
A missing policy key, an unsupported target, missing `TYPE_TEXT` text and a stale page are
|
|
231
|
+
**refusals** in normal tool results. A broken browser or environment is a failed call (`isError`).
|
|
232
|
+
An agent should report a failure, not retry it blindly.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Transports and concurrency
|
|
237
|
+
|
|
238
|
+
| Transport | What one agent gets | What two agents get |
|
|
239
|
+
| --- | --- | --- |
|
|
240
|
+
| **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 |
|
|
241
|
+
| **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 |
|
|
242
|
+
|
|
243
|
+
Start an HTTP endpoint when several agents should share one installed server:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs \
|
|
247
|
+
--transport http --host 127.0.0.1 --port 7331 --path /mcp
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The endpoint is `http://127.0.0.1:7331/mcp` by default. Each MCP `initialize` session gets its own
|
|
251
|
+
browser session; in the default attach mode all of them drive the Chrome you already use (see
|
|
252
|
+
`--attach-sharing` above), while `--mode launch` gives every session its own tab of a dedicated
|
|
253
|
+
browser. A session that goes quiet for 15 minutes has its browser closed and its anonymous profile
|
|
254
|
+
removed (`BROWSER_USE_MCP_HTTP_IDLE_MS` overrides the timeout). There is no authentication: the
|
|
255
|
+
default bind is loopback, and a non-loopback bind logs a warning at startup. Put it behind a tunnel
|
|
256
|
+
or proxy you control if it must leave the machine.
|
|
257
|
+
|
|
258
|
+
**Named profiles (launch mode).** In launch mode an anonymous HTTP session gets a throwaway profile
|
|
259
|
+
that is deleted when the session ends. A client that wants its logins to survive reconnects names its
|
|
260
|
+
profile on the initialize request:
|
|
261
|
+
|
|
262
|
+
```text
|
|
263
|
+
http://127.0.0.1:7331/mcp?profile=claude (or header X-Browser-Use-Profile: claude)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The named profile lives at `<state root>/profiles/<server profile>-<name>` and is kept when the
|
|
267
|
+
session ends; the next session with the same name reuses it. Different names run at the same time on
|
|
268
|
+
different profiles, and two live sessions with the same name are refused by the profile lock rather
|
|
269
|
+
than silently sharing one browser. Names are 1–32 characters from `[A-Za-z0-9_-]`.
|
|
270
|
+
|
|
271
|
+
Two servers configured for the **same browser profile** are refused rather than silently sharing a
|
|
272
|
+
browser: the first holds a lock in the profile directory and the second gets a tool error naming
|
|
273
|
+
`--profile` and the fix. A lock left behind by a crashed process is reclaimed.
|
|
274
|
+
|
|
275
|
+
### Which browser: attach (default) or launch
|
|
276
|
+
|
|
277
|
+
**`attach` (default)** drives the Chrome you already use:
|
|
278
|
+
|
|
279
|
+
- With no endpoint, the server finds that browser through the `DevToolsActivePort` Chrome publishes
|
|
280
|
+
in its profile. Turn remote debugging on in that Chrome first
|
|
281
|
+
(`chrome://inspect/#remote-debugging`). On macOS that file is privacy-protected, so the
|
|
282
|
+
application running this server needs **Full Disk Access** (System Settings → Privacy & Security →
|
|
283
|
+
Full Disk Access), then must be restarted.
|
|
284
|
+
- **`--cdp-endpoint http://127.0.0.1:9333`** (or a `ws://…` endpoint) drives a specific browser you
|
|
285
|
+
started with remote debugging on.
|
|
286
|
+
- `--attach-sharing shared` (default) lets several MCP sessions — and several server processes —
|
|
287
|
+
drive that browser at once, each in its own tab over its own CDP connection. Chrome 144+ asks
|
|
288
|
+
"Allow remote debugging?" once per connection, so two agents mean at least two prompts; approve
|
|
289
|
+
each once and never retry an unanswered one. `--attach-sharing exclusive` refuses the second live
|
|
290
|
+
holder instead, with a fix, which is the safer choice when two agents must never touch the same
|
|
291
|
+
browser.
|
|
292
|
+
- `browser_close` stops the session's tab and daemon and never closes the attached browser.
|
|
293
|
+
|
|
294
|
+
**`--mode launch`** starts a dedicated Chrome with its own profile under the state root, so your
|
|
295
|
+
everyday browser and its logins stay out of automation. `browser_close` keeps that browser for the
|
|
296
|
+
next `browser_open`; the server closes it when the connection ends. It is the right mode for a
|
|
297
|
+
headless CI machine, or for an agent that should not see your real sessions.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Configuration highlights
|
|
302
|
+
|
|
303
|
+
Every server setting is a flag with an environment-variable twin (the policy uses
|
|
304
|
+
`TYPESAFE_*`/`TEXT_MODEL_*`; a couple of extras are environment-only), and the flag wins when both
|
|
305
|
+
are present. The exhaustive tables, parsing rules, defaults and exit codes are in
|
|
306
|
+
**[docs/config.md](docs/config.md)**.
|
|
307
|
+
|
|
308
|
+
| Setting | Flag(s) | Env | Default |
|
|
309
|
+
| --- | --- | --- | --- |
|
|
310
|
+
| Transport | `--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` |
|
|
311
|
+
| Own or attach | `--mode`, `--cdp-endpoint` | `BROWSER_USE_MCP_MODE`, `BROWSER_USE_MCP_CDP_ENDPOINT` | `attach` |
|
|
312
|
+
| Attach sharing | `--attach-sharing` | `BROWSER_USE_MCP_ATTACH_SHARING` | `shared` (attach only) |
|
|
313
|
+
| Browser window | `--headless` | `BROWSER_USE_MCP_HEADLESS` | visible window |
|
|
314
|
+
| Profile | `--profile`, `--user-data-dir` | `BROWSER_USE_MCP_PROFILE`, `BROWSER_USE_MCP_USER_DATA_DIR` | `default` under the state root |
|
|
315
|
+
| State root | `--home` | `BROWSER_USE_MCP_HOME` | `~/.browser-use-mcp` |
|
|
316
|
+
| Interpreter | `--python` | `BROWSER_USE_MCP_PYTHON` | managed environment |
|
|
317
|
+
| Tool surface | `--no-eval`, `--no-screenshots`, `--no-jev` | `BROWSER_USE_MCP_NO_EVAL`, `…_NO_SCREENSHOTS`, `…_NO_JEV` | all tools offered |
|
|
318
|
+
| Policy | `--jev-decider` and the `--typesafe-*` / `--text-model-*` flags | `JEV_DECIDER`, `TYPESAFE_*`, `TEXT_MODEL_*` | `typesafe` decider |
|
|
319
|
+
| Request budget | `--request-timeout-ms` | `BROWSER_USE_MCP_REQUEST_TIMEOUT_MS` | `180000` ms |
|
|
320
|
+
| Logging | `--log-level` | `BROWSER_USE_MCP_LOG_LEVEL` | `info`, always stderr |
|
|
321
|
+
|
|
322
|
+
State root layout:
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
~/.browser-use-mcp/
|
|
326
|
+
├── env/ managed Python virtualenv (engine + browser-harness)
|
|
327
|
+
├── install.json what was installed, with which strategy and Python
|
|
328
|
+
├── profiles/<name>/ launched Chrome profiles (logins, tabs); named HTTP profiles too
|
|
329
|
+
└── attachments/<hash>.lock exclusive attach-mode claims, one per DevTools endpoint
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### The Jev policy
|
|
333
|
+
|
|
334
|
+
`browser_goal` runs a whole decision loop inside the engine; `browser_act` with an `intent` (and no
|
|
335
|
+
operation) delegates exactly one decision to the same policy. Both spend model quota. Who answers
|
|
336
|
+
is configuration:
|
|
337
|
+
|
|
338
|
+
- **`--jev-decider typesafe`** (default): Jev at the TypeSafe endpoint. Needs `TYPESAFE_API_KEY`;
|
|
339
|
+
optional `TYPESAFE_BASE_URL`, `TYPESAFE_MODEL`, `TYPESAFE_FALLBACK_URL`.
|
|
340
|
+
- **`--jev-decider chat`**: the engine serves its System One protocol on loopback and a chat model
|
|
341
|
+
answers it. Needs `TEXT_MODEL_BASE_URL` and `TEXT_MODEL` (plus `TEXT_MODEL_API_KEY` if the
|
|
342
|
+
endpoint needs one).
|
|
343
|
+
|
|
344
|
+
`TEXT_MODEL_*` is also what writes a `TYPE_TEXT` value when the agent gives an intent but no text.
|
|
345
|
+
Without either setup the rest of the tool surface is unchanged; the policy tools refuse with one
|
|
346
|
+
sentence naming the variable to set. `--no-jev` withdraws `browser_goal` and the `intent` parameter
|
|
347
|
+
entirely.
|
|
348
|
+
|
|
349
|
+
Put credentials in the client's `env` block (or the flags), never in `args`:
|
|
350
|
+
|
|
351
|
+
```json
|
|
352
|
+
"env": {
|
|
353
|
+
"TYPESAFE_API_KEY": "…",
|
|
354
|
+
"TEXT_MODEL_BASE_URL": "https://your-endpoint/v1",
|
|
355
|
+
"TEXT_MODEL": "your-model",
|
|
356
|
+
"TEXT_MODEL_API_KEY": "…"
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The server passes those variables only with the request that spends quota, never leaves them in the
|
|
361
|
+
sidecar's environment, and the doctor prints `set`/`unset` instead of a value.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Troubleshooting
|
|
366
|
+
|
|
367
|
+
**Start with `browser_doctor`.** The tool and the CLI run the same self-check; each problem line
|
|
368
|
+
carries the command that fixes it.
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
node /Users/ricardo.chen/PersonalGit/browser-use-mcp/bin/browser-use-mcp.mjs doctor
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
- **A profile is already in use.** The message names the profile and `--profile`: stop the other
|
|
375
|
+
server, or give this one its own `--profile <name>`. Two servers driving one browser profile is
|
|
376
|
+
the one configuration the server refuses on purpose.
|
|
377
|
+
- **Attach mode cannot find the browser.** This is the default mode: open
|
|
378
|
+
`chrome://inspect/#remote-debugging` in the Chrome you use and allow remote debugging, or start the
|
|
379
|
+
server with `--mode launch` for a dedicated browser. The doctor reports `attach mode drives …` with
|
|
380
|
+
the reason.
|
|
381
|
+
Start that browser with remote debugging on (`chrome://inspect/#remote-debugging`), correct
|
|
382
|
+
`--cdp-endpoint`, or use `--mode launch`. On macOS, allowing this app Full Disk Access is what lets
|
|
383
|
+
it read your Chrome's profile.
|
|
384
|
+
- **The engine is not installed yet.** The first use installs it into the state root; run
|
|
385
|
+
`node bin/browser-use-mcp.mjs install` from the checkout to do it eagerly, or `browser_doctor` with
|
|
386
|
+
`install: true` from the agent. Behind a proxy, set `HTTPS_PROXY` for the process that runs the
|
|
387
|
+
server.
|
|
388
|
+
- **`Jev … NOT READY`.** Set `TYPESAFE_API_KEY` (or use `--jev-decider chat` with `TEXT_MODEL_*`).
|
|
389
|
+
The browser tools do not need it.
|
|
390
|
+
- **No tools in the client.** The client's `env`/`args` did not reach the server, or it was not
|
|
391
|
+
restarted. Run the same command in a terminal and read stderr; a configuration error exits with
|
|
392
|
+
code `2` and names the field.
|
|
393
|
+
- **Chrome asks "Allow remote debugging?".** Something started a second CDP client. Approve that one
|
|
394
|
+
prompt once, never retry, and use `browser_eval` instead of another driver.
|
|
395
|
+
- **`browser_eval('document.body')` returns `{}`.** A DOM node cannot cross the CDP boundary by
|
|
396
|
+
value. Return something serialisable instead, for example `document.body.innerText` or
|
|
397
|
+
`JSON.stringify(document.body.getAttribute('class'))`.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Security notes
|
|
402
|
+
|
|
403
|
+
- **Page content is untrusted data.** Text the table quotes is something to read, never an
|
|
404
|
+
instruction to follow. The server never executes anything a page returns on the agent's behalf.
|
|
405
|
+
- **`browser_eval` is code execution by design.** It runs one expression in the page, over the
|
|
406
|
+
session's own connection, with no user gesture. Withdraw it with `--no-eval` for a surface where
|
|
407
|
+
model output can never become code; the rest of the tools still work.
|
|
408
|
+
- **The HTTP transport has no authentication.** Bind it to loopback (the default). Any non-loopback
|
|
409
|
+
bind is logged as a warning; only expose it on a trusted network or behind a tunnel you control.
|
|
410
|
+
- **Keys stay out of logs and tools.** The doctor prints only `set`/`unset`; credentials are handed
|
|
411
|
+
to the sidecar per model-spending request and are not inherited by unrelated calls.
|
|
412
|
+
- **The default mode drives your own Chrome**, logins included — that is what attach mode is for.
|
|
413
|
+
Use `--mode launch` when an agent should work in an isolated browser whose profile lives under the
|
|
414
|
+
state root instead.
|
|
415
|
+
- **One connection, one driver.** Do not start another CDP client against the same browser; Chrome
|
|
416
|
+
approves each connection separately and two drivers race each other.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## License and attribution
|
|
421
|
+
|
|
422
|
+
MIT. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
423
|
+
|
|
424
|
+
This project is built on [`dsh-browser-use`](https://github.com/ricardochen1996/dsh-browser-use)
|
|
425
|
+
(MIT): `sidecar/bridge.py` is vendored from it, and the action-space rendering, the freshness rule,
|
|
426
|
+
the one-connection rule and the direct-mode tool descriptions follow it. The browser engine is
|
|
427
|
+
[`jev-ultrafast`](https://github.com/ricardochen1996/jev-ultrafast) (MIT), bundled as a pinned wheel;
|
|
428
|
+
browser access is [`browser-harness`](https://github.com/browser-use/browser-harness) (MIT). The
|
|
429
|
+
design and rationale are in [docs/architecture.md](docs/architecture.md).
|
|
430
|
+
|
|
431
|
+
## Development
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
npm test # fast unit tests (node --test)
|
|
435
|
+
npm run test:e2e # real headless Chrome through the real MCP protocol
|
|
436
|
+
# (stdio, HTTP, attach mode, Jev intent against a loopback stub,
|
|
437
|
+
# and a hand-written JSON-RPC client)
|
|
438
|
+
npm run release:check # manifest + changelog + the exact packed file list
|
|
439
|
+
npm run release:pack # build the tarball, verify it and record its integrity in dist/
|
|
440
|
+
npm run doctor # the CLI self-check
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
The default suites spend no model calls and need no credentials; the e2e tests install the engine
|
|
444
|
+
into a test state root on their first run. Release history is in [CHANGELOG.md](CHANGELOG.md).
|
|
445
|
+
|
|
446
|
+
**Releases** are tagged `vX.Y.Z` on `main`; the [release workflow](.github/workflows/release.yml)
|
|
447
|
+
validates the annotated tag against `package.json`, reruns CI (unit plus real-browser e2e), publishes
|
|
448
|
+
to npm with provenance using the `NPM_TOKEN` repository secret, and creates the GitHub Release. Bump
|
|
449
|
+
the version, add its `CHANGELOG.md` section, then `git tag -a vX.Y.Z -m "vX.Y.Z"` and push the tag.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|