@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/docs/config.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Configuration reference
|
|
2
|
+
|
|
3
|
+
Everything the server can be told, where it can be told it, and what wins when it is told twice.
|
|
4
|
+
The authoritative source is [`lib/config.js`](../lib/config.js) and [`lib/cli.js`](../lib/cli.js);
|
|
5
|
+
this page is the readable copy. If this page and the code ever disagree, the code is right and this
|
|
6
|
+
page is a bug.
|
|
7
|
+
|
|
8
|
+
`browser-use-mcp` has no configuration file. Settings arrive as command-line flags in the `args` a
|
|
9
|
+
client passes, as environment variables in the `env` block a client passes, or as defaults in code.
|
|
10
|
+
That is deliberate: an MCP client is already a configuration system, and one resolved config object
|
|
11
|
+
is what every other module reads.
|
|
12
|
+
|
|
13
|
+
## Precedence
|
|
14
|
+
|
|
15
|
+
**A flag wins over an environment variable, which wins over the built-in default.**
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
flag > BROWSER_USE_MCP_* / Jev environment > default
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A flag that is not passed is `undefined`, so it never shadows an environment variable. The three
|
|
22
|
+
one-way switches (`--no-eval`, `--no-screenshots`, `--no-jev`) can only withdraw a tool; their
|
|
23
|
+
positive environment aliases (`BROWSER_USE_MCP_EVAL=0`, …) say the same thing positively.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
browser-use-mcp [serve] [options] # default: speak MCP on stdio, or Streamable HTTP
|
|
29
|
+
browser-use-mcp doctor [options] # look: interpreter, engine, browser, config, live session
|
|
30
|
+
browser-use-mcp install [options] # build the managed Python environment now
|
|
31
|
+
browser-use-mcp --help | -h # the flag list below
|
|
32
|
+
browser-use-mcp --version | -v # the package version
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Command | What it does | Exit code |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `serve` (default) | Speaks MCP on stdio (default) or Streamable HTTP. Logs go to stderr because stdout is the protocol. | `0` after a clean shutdown; `1` on an unexpected error; `2` on a configuration error |
|
|
38
|
+
| `doctor` | Reports interpreter, engine versions, managed environment, browser / attach target, the resolved config, the tool list, the live session and a fix per problem. Never installs anything, never opens a page. | `0` when ready (`Status: ready`); `1` when it found a problem |
|
|
39
|
+
| `install` | Builds the managed environment eagerly. Safe to re-run: an environment whose interpreter still imports the engine is reused. | `0` when the engine is usable; `1` when the install failed |
|
|
40
|
+
| `--help`, `--version` | Print to stdout and exit. | `0` |
|
|
41
|
+
|
|
42
|
+
A configuration error (a bad enum, port, timeout, profile name, URL, boolean env value, or
|
|
43
|
+
`--mode launch` together with `--cdp-endpoint`) prints
|
|
44
|
+
`browser-use-mcp: configuration error: …` on stderr and exits with code `2`. The server never starts
|
|
45
|
+
in a half-valid state.
|
|
46
|
+
|
|
47
|
+
## Flags and environment variables
|
|
48
|
+
|
|
49
|
+
The column **Env** shows the variable that mirrors the flag. In an MCP client that passes an `env`
|
|
50
|
+
block, these are the names to use. `—` means there is no flag or no env spelling.
|
|
51
|
+
|
|
52
|
+
### Transport (Streamable HTTP)
|
|
53
|
+
|
|
54
|
+
| Flag | Env | Default | Meaning |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| `--transport` | `BROWSER_USE_MCP_TRANSPORT` | `stdio` | `stdio` or `http`. |
|
|
57
|
+
| `--host` | `BROWSER_USE_MCP_HOST` | `127.0.0.1` | HTTP bind address. Must not be empty. |
|
|
58
|
+
| `--port` | `BROWSER_USE_MCP_PORT` | `7331` | HTTP port, `1`–`65535`. |
|
|
59
|
+
| `--path` | `BROWSER_USE_MCP_PATH` | `/mcp` | HTTP route. A missing leading `/` is added; whitespace is refused. |
|
|
60
|
+
| — | `BROWSER_USE_MCP_HTTP_IDLE_MS` | `900000` (15 min) | HTTP only. Milliseconds of silence after which one MCP session's browser is closed and its profile directory removed. Resolved by `lib/config.js` (range 1000–86400000), used by the HTTP transport and reported by the doctor; there is no flag. |
|
|
61
|
+
|
|
62
|
+
### Browser connection
|
|
63
|
+
|
|
64
|
+
| Flag | Env | Default | Meaning |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `--mode` | `BROWSER_USE_MCP_MODE` | `attach` | `attach` drives the browser that is already running (the default: the Chrome you use, or the one `--cdp-endpoint` names); `launch` starts a dedicated browser this server owns. `--mode launch` together with a CDP endpoint is refused rather than silently ignoring one of them. |
|
|
67
|
+
| `--attach-sharing` | `BROWSER_USE_MCP_ATTACH_SHARING` | `shared` | Attach only. `shared` lets several MCP sessions — and several server processes — drive that one browser at once, each in its own tab and over its own CDP connection. `exclusive` claims the DevTools endpoint for one live holder and refuses the next with a fix. Resolved to `exclusive` in launch mode, which has nothing to share; `shared` together with `--mode launch` is refused. |
|
|
68
|
+
| `--cdp-endpoint` | `BROWSER_USE_MCP_CDP_ENDPOINT` | unset | DevTools endpoint to attach to: an `http(s)` or `ws(s)` URL without whitespace. Setting it without `--mode` implies attach (attach is the default anyway). |
|
|
69
|
+
| `--executable` | `BROWSER_USE_MCP_EXECUTABLE_PATH` | discovered Chrome/Chromium | Browser executable to launch. A path that does not exist falls through to discovery. |
|
|
70
|
+
| `--profile` | `BROWSER_USE_MCP_PROFILE` | `default` | Profile name: `[A-Za-z0-9._-]{1,64}`, and not `.` or `..`. The directory is `<state root>/profiles/<profile>` unless `--user-data-dir` overrides it. Also the prefix of an HTTP session's profile label (`<profile>-<name>` when the client names one). |
|
|
71
|
+
| `--user-data-dir` | `BROWSER_USE_MCP_USER_DATA_DIR` | `<state root>/profiles/<profile>` | Launch this browser profile directory instead of the managed one. Not used by HTTP sessions, which always use `<state root>/profiles/…`: an anonymous session gets `<profile>-<session suffix>` (removed when it ends), a named one `<profile>-<name>` (kept). |
|
|
72
|
+
| `--headless` | `BROWSER_USE_MCP_HEADLESS` | `false` | Launch without a visible window. The env value accepts `1/true/yes/on` and `0/false/no/off`. |
|
|
73
|
+
| `--headed` | — | — | Explicit inverse of `--headless`; `--headless` wins if both are passed. |
|
|
74
|
+
| `--home` | `BROWSER_USE_MCP_HOME` | `~/.browser-use-mcp` | State root: managed environment, install record, browser profiles, attach locks. `~` expands; a relative path resolves against the working directory. |
|
|
75
|
+
| `--python` | `BROWSER_USE_MCP_PYTHON` | unset | Use exactly this Python 3.12+ interpreter, and do not install anything. See [Managed or explicit Python](#managed-or-explicit-python). |
|
|
76
|
+
|
|
77
|
+
### Tool surface
|
|
78
|
+
|
|
79
|
+
| Flag | Env | Default | Meaning |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| `--no-eval` | `BROWSER_USE_MCP_NO_EVAL=1`, or `BROWSER_USE_MCP_EVAL=0` | eval on | Withdraw `browser_eval`. |
|
|
82
|
+
| `--no-screenshots` | `BROWSER_USE_MCP_NO_SCREENSHOTS=1`, or `BROWSER_USE_MCP_SCREENSHOTS=0` | screenshots on | Withdraw `browser_screenshot`. |
|
|
83
|
+
| `--no-jev` | `BROWSER_USE_MCP_NO_JEV=1`, or `BROWSER_USE_MCP_JEV=off` (also `false`, `0`, `no`) | Jev on | Withdraw `browser_goal` and `browser_act`'s `intent` parameter. The rest of the surface works identically. |
|
|
84
|
+
|
|
85
|
+
### Jev policy
|
|
86
|
+
|
|
87
|
+
The policy variables use the engine's own names (`TYPESAFE_*`, `TEXT_MODEL_*`), not the
|
|
88
|
+
`BROWSER_USE_MCP_*` prefix, because that is the convention an MCP client's `env` block already
|
|
89
|
+
uses for model credentials. The server copies them into the one sidecar request that spends model
|
|
90
|
+
quota; they are never left in the server's environment or the sidecar's.
|
|
91
|
+
|
|
92
|
+
| Flag | Env | Default | Meaning |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| `--jev-decider` | `JEV_DECIDER` | `typesafe` | `typesafe`: Jev answers the policy questions. `chat`: a text model answers them through the engine's loopback System One endpoint. |
|
|
95
|
+
| `--typesafe-base-url` | `TYPESAFE_BASE_URL` | engine default | TypeSafe endpoint. |
|
|
96
|
+
| `--typesafe-model` | `TYPESAFE_MODEL` | engine default | TypeSafe model name. |
|
|
97
|
+
| `--typesafe-api-key` | `TYPESAFE_API_KEY` | unset | TypeSafe key. **Required** for `--jev-decider typesafe`. |
|
|
98
|
+
| — | `TYPESAFE_FALLBACK_URL` | unset | Env only; forwarded to the engine as a fallback endpoint. |
|
|
99
|
+
| `--text-model-base-url` | `TEXT_MODEL_BASE_URL` | unset | OpenAI-compatible chat endpoint. |
|
|
100
|
+
| `--text-model` | `TEXT_MODEL` | unset | Text model name. |
|
|
101
|
+
| `--text-model-api-key` | `TEXT_MODEL_API_KEY` | unset | Text model key, when the endpoint needs one. |
|
|
102
|
+
| `--text-model-reasoning` | `TEXT_MODEL_REASONING` | `thinking-disabled` | Reasoning mode as the engine should set it: `none` or `thinking-disabled` (an empty value means "leave it to the engine"). |
|
|
103
|
+
| — | `TEXT_MODEL_HEADERS` | `{}` | Env only. Extra HTTP headers for the text model, as a JSON object of string values, e.g. `{"X-Org":"team-a"}`. |
|
|
104
|
+
|
|
105
|
+
With `--jev-decider chat`, the engine serves its System One protocol on loopback and the text model
|
|
106
|
+
answers it; `TYPESAFE_*` values are deliberately not sent so a stale key cannot win.
|
|
107
|
+
`TEXT_MODEL_*` is also what writes a `TYPE_TEXT` value when the agent gives an intent but no text,
|
|
108
|
+
including with the `typesafe` decider.
|
|
109
|
+
|
|
110
|
+
### Timeouts and logging
|
|
111
|
+
|
|
112
|
+
| Flag | Env | Default | Meaning |
|
|
113
|
+
| --- | --- | --- | --- |
|
|
114
|
+
| `--request-timeout-ms` | `BROWSER_USE_MCP_REQUEST_TIMEOUT_MS` | `180000` | Longest single browser request, `1000`–`3600000` ms. `browser_eval` gets at least 125 s; `browser_goal` is deliberately not bounded by it, because a goal run decides its own length. |
|
|
115
|
+
| `--log-level` | `BROWSER_USE_MCP_LOG_LEVEL` | `info` | `silent`, `error`, `info`, `debug`. Every log line goes to **stderr**; stdout belongs to the stdio transport. |
|
|
116
|
+
|
|
117
|
+
## Parsing rules
|
|
118
|
+
|
|
119
|
+
These are the rules `lib/config.js` applies to both spellings, so a flag and its env variable can
|
|
120
|
+
never mean two different things:
|
|
121
|
+
|
|
122
|
+
- **Booleans** (`BROWSER_USE_MCP_HEADLESS`, the positive tool-surface aliases): `1`, `true`, `yes`,
|
|
123
|
+
`on` are true; `0`, `false`, `no`, `off` are false; anything else is a configuration error.
|
|
124
|
+
- **Integers** (`port`, `request-timeout-ms`): whole numbers only, range-checked as above.
|
|
125
|
+
- **Enums** (`transport`, `mode`, `jev-decider`, `log-level`, `text-model-reasoning`): matched
|
|
126
|
+
case-insensitively; an unknown value is an error listing the accepted set.
|
|
127
|
+
- **URLs** (`cdp-endpoint`, TypeSafe and text-model URLs): an absolute `http(s)` or `ws(s)` URL
|
|
128
|
+
without whitespace; an empty string means "unset".
|
|
129
|
+
- **Paths** (`--home`, `--user-data-dir`, `--executable`, `--python`): `~` and `~/…` expand to the
|
|
130
|
+
home directory, and a relative path resolves against the process's working directory at startup.
|
|
131
|
+
- **Profile names**: `[A-Za-z0-9._-]{1,64}`, not `.` or `..` — a profile name becomes a directory
|
|
132
|
+
name under the state root.
|
|
133
|
+
- **`TEXT_MODEL_HEADERS`**: must parse as a JSON object whose values are strings.
|
|
134
|
+
|
|
135
|
+
## State root
|
|
136
|
+
|
|
137
|
+
`--home` / `BROWSER_USE_MCP_HOME` names one directory. Default: `~/.browser-use-mcp`.
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
~/.browser-use-mcp/
|
|
141
|
+
├── env/ managed Python 3.12 virtualenv: jev_ultrafast + browser-harness
|
|
142
|
+
├── install.json what was installed, with which strategy and Python version
|
|
143
|
+
├── install.lock/ transient cross-process install lock (owner pid inside)
|
|
144
|
+
├── profiles/<name>/ launched Chrome profiles; logins and tabs live here
|
|
145
|
+
│ └── .browser-use-mcp.lock held while a server drives this profile
|
|
146
|
+
└── attachments/<hash>.lock one attach-mode claim per DevTools endpoint
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Only reading a report creates nothing; the directories appear when an install, a launch or an
|
|
150
|
+
attach first needs them.
|
|
151
|
+
|
|
152
|
+
## The Jev policy, exactly
|
|
153
|
+
|
|
154
|
+
`browser_goal` and `browser_act`'s `intent` spend model quota. Before the engine is asked, the tool
|
|
155
|
+
checks that a model call could actually run and refuses with one sentence naming what to set:
|
|
156
|
+
|
|
157
|
+
| Decider | Required | Optional |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| `typesafe` (default) | `TYPESAFE_API_KEY` | `TYPESAFE_BASE_URL`, `TYPESAFE_MODEL`, `TYPESAFE_FALLBACK_URL`, and the whole `TEXT_MODEL_*` group for generated `TYPE_TEXT` values |
|
|
160
|
+
| `chat` | `TEXT_MODEL_BASE_URL` and `TEXT_MODEL` | `TEXT_MODEL_API_KEY`, `TEXT_MODEL_REASONING`, `TEXT_MODEL_HEADERS` |
|
|
161
|
+
|
|
162
|
+
Notes:
|
|
163
|
+
|
|
164
|
+
- `--no-jev` removes the policy surface entirely: no `browser_goal`, and `browser_act` has no
|
|
165
|
+
`intent` parameter. Everything else works the same.
|
|
166
|
+
- Missing credentials are a **refusal in a normal tool result**, not a failed call: the text names
|
|
167
|
+
the variable to set. The doctor adds `Jev: … — NOT READY: …` and reports keys only as
|
|
168
|
+
`set`/`unset`.
|
|
169
|
+
- `browser_goal` also needs `TEXT_MODEL_*` only when the policy has to generate a typed value; a
|
|
170
|
+
goal made of clicks does not need the text model.
|
|
171
|
+
|
|
172
|
+
## Managed or explicit Python
|
|
173
|
+
|
|
174
|
+
The engine is a Python package, so the server needs a Python 3.12+ interpreter that can import
|
|
175
|
+
`jev_ultrafast` and `browser_harness`.
|
|
176
|
+
|
|
177
|
+
**Managed (default).** With no `--python`, the server owns `<state root>/env`:
|
|
178
|
+
|
|
179
|
+
- It is probed first. When it exists and imports both packages, it is used.
|
|
180
|
+
- When it does not exist, `serve` prewarms the check in the background and a browser call (or the
|
|
181
|
+
doctor's fix) waits for it; `browser-use-mcp install` builds it eagerly.
|
|
182
|
+
- The install looks for a builder in this order: `uv` (which can also fetch a pinned Python 3.12),
|
|
183
|
+
then `python3.14`, `python3.13`, `python3.12`, `python3` on `PATH` (`python.exe` on Windows) —
|
|
184
|
+
each probed, and each must be 3.12 or newer.
|
|
185
|
+
- `uv` builds the virtualenv and installs `vendor/requirements.txt` plus the bundled wheel with
|
|
186
|
+
`uv pip`; without `uv`, `pip` is called with `--require-hashes --no-deps --only-binary=:all:`
|
|
187
|
+
and the same wheel. One install at a time, per state root, via `install.lock/`; a lock left by a
|
|
188
|
+
dead process is reclaimed.
|
|
189
|
+
- `install.json` is written on success: strategy, environment path, Python version, install time,
|
|
190
|
+
engine version, browser-harness version.
|
|
191
|
+
- If no managed environment exists but another Python on `PATH` already imports the engine, that
|
|
192
|
+
interpreter is used; the server does not install when the engine is already importable.
|
|
193
|
+
|
|
194
|
+
**Explicit (`--python PATH`).** That interpreter is the only candidate: it is either usable or the
|
|
195
|
+
problem is reported. The server never installs into it and never falls back to another Python. This
|
|
196
|
+
is the option for a development environment where the engine is already installed.
|
|
197
|
+
|
|
198
|
+
## Launch and attach
|
|
199
|
+
|
|
200
|
+
**Attach** (default). The server drives a browser that is already running — normally the Chrome you
|
|
201
|
+
browse with, found through the `DevToolsActivePort` file Chrome publishes in its profile. Turn
|
|
202
|
+
remote debugging on in that Chrome first (`chrome://inspect/#remote-debugging`). On macOS that file
|
|
203
|
+
is privacy-protected, so the application running this server needs **Full Disk Access** (System
|
|
204
|
+
Settings → Privacy & Security → Full Disk Access), then be restarted. When the engine cannot read
|
|
205
|
+
the profile, the doctor reports `no_permission` / `no_browser` with the fix rather than guessing.
|
|
206
|
+
|
|
207
|
+
**Attach with `--cdp-endpoint`.** The server drives a browser someone else started with remote
|
|
208
|
+
debugging on. An `http(s)` endpoint is asked for `/json/version` (the way the first browser call
|
|
209
|
+
asks); a `ws(s)` endpoint is only asked whether something is listening.
|
|
210
|
+
|
|
211
|
+
**Sharing an attached browser.** `--attach-sharing shared` (default) is what makes one endpoint
|
|
212
|
+
usable by several MCP sessions at once: each session gets its own sidecar, its own daemon and its
|
|
213
|
+
own tab on that browser. Chrome 144+ asks "Allow remote debugging?" once per connection, so N live
|
|
214
|
+
sessions mean N prompts — approve each once and never retry an unanswered one. With
|
|
215
|
+
`--attach-sharing exclusive`, a claim lives at `<state root>/attachments/<hash>.lock` (keyed by the
|
|
216
|
+
DevTools endpoint) and the second live holder is refused with a fix. In shared mode no such lock is
|
|
217
|
+
taken, and two separate server processes may also drive the browser.
|
|
218
|
+
|
|
219
|
+
**Launch** (`--mode launch`). The server starts Chrome/Chromium with its own profile directory under
|
|
220
|
+
the state root. It never touches the browser you browse with, so no Chrome consent prompt appears
|
|
221
|
+
and your logins stay out of automation. Each profile is held by one server at a time: the lock file
|
|
222
|
+
is `<profile dir>/.browser-use-mcp.lock`, and a second server on the same profile gets a tool error
|
|
223
|
+
naming `--profile` with the fix. A lock left by a crashed process is reclaimed.
|
|
224
|
+
|
|
225
|
+
In attach mode `browser_close` closes the session's tab and daemon; the browser you attached to is
|
|
226
|
+
never closed. In launch mode the launched browser stays up for the next `browser_open` and is closed
|
|
227
|
+
when the MCP connection ends.
|
|
228
|
+
|
|
229
|
+
## Streamable HTTP details
|
|
230
|
+
|
|
231
|
+
- Default endpoint: `http://127.0.0.1:7331/mcp`. The host and path are configurable, the route is
|
|
232
|
+
exact.
|
|
233
|
+
- Each MCP session (`initialize`) gets its own browser session: its own sidecar process, its own
|
|
234
|
+
tab, and (in launch mode) its own profile.
|
|
235
|
+
- In launch mode a session can name its profile on the initialize request —
|
|
236
|
+
`/mcp?profile=<name>` or header `X-Browser-Use-Profile: <name>`, 1–32 characters from
|
|
237
|
+
`[A-Za-z0-9_-]` — and then the directory `<profile>-<name>` is kept when the session ends, so a
|
|
238
|
+
reconnect with the same name keeps its logins. Different names run concurrently on different
|
|
239
|
+
profiles; two live sessions with the same name are refused by the profile lock. An anonymous
|
|
240
|
+
session gets `<profile>-<session suffix>` and the directory is removed when it ends. An invalid
|
|
241
|
+
name is refused at initialize with `400`.
|
|
242
|
+
- A session that stays silent longer than `BROWSER_USE_MCP_HTTP_IDLE_MS` (15 minutes by default) has
|
|
243
|
+
its tab and browser closed and its anonymous profile directory removed. An explicit session
|
|
244
|
+
termination is cleaned up immediately. Named profiles are never removed by the server.
|
|
245
|
+
- Requests to another path get `404`; a known session id is required for follow-up requests; an
|
|
246
|
+
unknown or expired one gets `404` with "send initialize again"; a `GET` without a session gets
|
|
247
|
+
`400` with the same guidance. Plain JSON responses are enabled, and an SSE-capable client still
|
|
248
|
+
gets its stream on `GET`.
|
|
249
|
+
- There is **no authentication**. The default bind is loopback; binding a non-loopback address logs
|
|
250
|
+
a warning at startup. Put it behind a tunnel or reverse proxy you control if it must leave the
|
|
251
|
+
machine.
|
|
252
|
+
- `SIGINT`, `SIGTERM` and `SIGHUP` close every session's browser before the process exits.
|
|
253
|
+
|
|
254
|
+
## See also
|
|
255
|
+
|
|
256
|
+
- [Client wiring](clients.md) — copy-pasteable snippets for Claude Code, Claude Desktop, Cursor,
|
|
257
|
+
Codex, DSH and plain `mcpServers` JSON.
|
|
258
|
+
- [Architecture](architecture.md) — how the pieces fit and why.
|
|
259
|
+
- [README](../README.md) — what the server is and how to use it.
|
|
260
|
+
- [CHANGELOG](../CHANGELOG.md) — what changed in each release.
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command line: `serve` (the default), `doctor`, `install`.
|
|
3
|
+
*
|
|
4
|
+
* Words become flags here and nothing else: validation and resolution live in `./config.js`, so the
|
|
5
|
+
* same rules apply whether a value came from the command line, an MCP client's `env` block, or the
|
|
6
|
+
* shell the server was started from.
|
|
7
|
+
*
|
|
8
|
+
* @module browser-use-mcp/cli
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs'
|
|
12
|
+
import { parseArgs } from 'node:util'
|
|
13
|
+
|
|
14
|
+
/** The value of the package's own manifest, read once. */
|
|
15
|
+
export function packageInfo() {
|
|
16
|
+
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
17
|
+
return { name: manifest.name, version: manifest.version, description: manifest.description }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const COMMANDS = ['serve', 'doctor', 'install']
|
|
21
|
+
|
|
22
|
+
export const USAGE = `browser-use-mcp — an MCP server that gives any agent a browser it can see and click
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
browser-use-mcp [serve] [options] speak MCP on stdio (default) or Streamable HTTP
|
|
26
|
+
browser-use-mcp doctor [options] check interpreter, engine, browser and configuration
|
|
27
|
+
browser-use-mcp install [options] build the managed Python environment now
|
|
28
|
+
|
|
29
|
+
Options (each can also come from the environment; a flag wins over it):
|
|
30
|
+
--transport <stdio|http> transport to serve (default: stdio)
|
|
31
|
+
--host <addr> HTTP bind address (default: 127.0.0.1)
|
|
32
|
+
--port <number> HTTP port (default: 7331)
|
|
33
|
+
--path <route> HTTP route (default: /mcp)
|
|
34
|
+
--mode <launch|attach> drive the browser you use (default: attach), or launch a dedicated one
|
|
35
|
+
--attach-sharing <exclusive|shared> attach mode: let several sessions share the browser (default: shared)
|
|
36
|
+
--cdp-endpoint <url> DevTools endpoint for attach mode (http(s) or ws(s))
|
|
37
|
+
--executable <path> browser to launch (default: Chrome/Chromium)
|
|
38
|
+
--profile <name> profile directory name under the state root (default: default)
|
|
39
|
+
--user-data-dir <path> launch this profile directory instead of the managed one
|
|
40
|
+
--headless launch without a visible window
|
|
41
|
+
--headed show the window (overrides a headless environment)
|
|
42
|
+
--python <path> use this Python 3.12+ interpreter instead of a managed one
|
|
43
|
+
--home <dir> state root (default: ~/.browser-use-mcp)
|
|
44
|
+
--no-eval do not offer browser_eval
|
|
45
|
+
--no-screenshots do not offer browser_screenshot
|
|
46
|
+
--no-jev do not offer browser_goal or browser_act's intent
|
|
47
|
+
--jev-decider <typesafe|chat> who answers the policy's questions (default: typesafe)
|
|
48
|
+
--typesafe-base-url <url> TypeSafe endpoint (env TYPESAFE_BASE_URL)
|
|
49
|
+
--typesafe-model <name> TypeSafe model (env TYPESAFE_MODEL)
|
|
50
|
+
--typesafe-api-key <key> TypeSafe key (env TYPESAFE_API_KEY)
|
|
51
|
+
--text-model-base-url <url> OpenAI-compatible endpoint (env TEXT_MODEL_BASE_URL)
|
|
52
|
+
--text-model <name> text model (env TEXT_MODEL)
|
|
53
|
+
--text-model-api-key <key> text model key (env TEXT_MODEL_API_KEY)
|
|
54
|
+
--text-model-reasoning <mode> none | thinking-disabled (env TEXT_MODEL_REASONING)
|
|
55
|
+
--request-timeout-ms <number> longest single browser request (default: 180000)
|
|
56
|
+
--log-level <silent|error|info|debug> stderr logging (default: info)
|
|
57
|
+
-h, --help show this help
|
|
58
|
+
-v, --version show the version
|
|
59
|
+
|
|
60
|
+
A Jev key is only needed for browser_goal and browser_act's intent; the rest of the tools never
|
|
61
|
+
spend model quota. With --jev-decider chat the text model decides instead, so set the TEXT_MODEL_*
|
|
62
|
+
variables and no TypeSafe key is needed.
|
|
63
|
+
`
|
|
64
|
+
|
|
65
|
+
/** The option table: names are the long form, the values are `node:util` parseArgs descriptors. */
|
|
66
|
+
const OPTIONS = {
|
|
67
|
+
transport: { type: 'string' },
|
|
68
|
+
host: { type: 'string' },
|
|
69
|
+
port: { type: 'string' },
|
|
70
|
+
path: { type: 'string' },
|
|
71
|
+
mode: { type: 'string' },
|
|
72
|
+
'cdp-endpoint': { type: 'string' },
|
|
73
|
+
'attach-sharing': { type: 'string' },
|
|
74
|
+
executable: { type: 'string' },
|
|
75
|
+
profile: { type: 'string' },
|
|
76
|
+
'user-data-dir': { type: 'string' },
|
|
77
|
+
headless: { type: 'boolean' },
|
|
78
|
+
headed: { type: 'boolean' },
|
|
79
|
+
python: { type: 'string' },
|
|
80
|
+
home: { type: 'string' },
|
|
81
|
+
'no-eval': { type: 'boolean' },
|
|
82
|
+
'no-screenshots': { type: 'boolean' },
|
|
83
|
+
'no-jev': { type: 'boolean' },
|
|
84
|
+
'jev-decider': { type: 'string' },
|
|
85
|
+
'typesafe-base-url': { type: 'string' },
|
|
86
|
+
'typesafe-model': { type: 'string' },
|
|
87
|
+
'typesafe-api-key': { type: 'string' },
|
|
88
|
+
'text-model-base-url': { type: 'string' },
|
|
89
|
+
'text-model': { type: 'string' },
|
|
90
|
+
'text-model-api-key': { type: 'string' },
|
|
91
|
+
'text-model-reasoning': { type: 'string' },
|
|
92
|
+
'request-timeout-ms': { type: 'string' },
|
|
93
|
+
'log-level': { type: 'string' },
|
|
94
|
+
help: { type: 'boolean', short: 'h' },
|
|
95
|
+
version: { type: 'boolean', short: 'v' },
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse one command line.
|
|
100
|
+
*
|
|
101
|
+
* @param {string[]} argv - arguments after the node executable and script.
|
|
102
|
+
* @returns {{command: string, flags: object, help: boolean, version: boolean}}
|
|
103
|
+
* @throws {Error} when the arguments are not a command line this server understands.
|
|
104
|
+
*/
|
|
105
|
+
export function parseCommandLine(argv = []) {
|
|
106
|
+
let parsed
|
|
107
|
+
try {
|
|
108
|
+
parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true })
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`${error.message}\n\n${USAGE}`)
|
|
111
|
+
}
|
|
112
|
+
const { values, positionals } = parsed
|
|
113
|
+
if (positionals.length > 1) throw new Error(`expected one command, got ${positionals.length}\n\n${USAGE}`)
|
|
114
|
+
const command = positionals[0] ?? 'serve'
|
|
115
|
+
if (!COMMANDS.includes(command)) throw new Error(`unknown command ${JSON.stringify(command)}\n\n${USAGE}`)
|
|
116
|
+
const string = name => (values[name] === undefined ? undefined : String(values[name]))
|
|
117
|
+
const flags = {
|
|
118
|
+
transport: string('transport'),
|
|
119
|
+
host: string('host'),
|
|
120
|
+
port: string('port'),
|
|
121
|
+
path: string('path'),
|
|
122
|
+
mode: string('mode'),
|
|
123
|
+
cdpEndpoint: string('cdp-endpoint'),
|
|
124
|
+
attachSharing: string('attach-sharing'),
|
|
125
|
+
executablePath: string('executable'),
|
|
126
|
+
profile: string('profile'),
|
|
127
|
+
userDataDir: string('user-data-dir'),
|
|
128
|
+
headless: values.headless === true ? true : values.headed === true ? false : undefined,
|
|
129
|
+
pythonPath: string('python'),
|
|
130
|
+
browserUseMcpHome: string('home'),
|
|
131
|
+
noEval: values['no-eval'] === true,
|
|
132
|
+
noScreenshots: values['no-screenshots'] === true,
|
|
133
|
+
noJev: values['no-jev'] === true,
|
|
134
|
+
jevDecider: string('jev-decider'),
|
|
135
|
+
typesafeBaseURL: string('typesafe-base-url'),
|
|
136
|
+
typesafeModel: string('typesafe-model'),
|
|
137
|
+
typesafeApiKey: string('typesafe-api-key'),
|
|
138
|
+
textModelBaseURL: string('text-model-base-url'),
|
|
139
|
+
textModel: string('text-model'),
|
|
140
|
+
textModelApiKey: string('text-model-api-key'),
|
|
141
|
+
textModelReasoning: string('text-model-reasoning'),
|
|
142
|
+
requestTimeoutMs: string('request-timeout-ms'),
|
|
143
|
+
logLevel: string('log-level'),
|
|
144
|
+
}
|
|
145
|
+
return { command, flags, help: values.help === true, version: values.version === true }
|
|
146
|
+
}
|