@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/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
+ }