@raultov/browser-control-mcp 0.1.1-beta.1 → 0.2.0-beta.2

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 CHANGED
@@ -10,6 +10,108 @@ from a copy of its code: entries describe the differences from that release.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.2.0-beta.2] - 2026-10-14
14
+
15
+ Public **BETA**. Firefox debugger internals only; tool replies are the same
16
+ as in 0.2.0-beta.1.
17
+
18
+ ### Changed
19
+
20
+ - `bidi-browser-lite` 0.1.5 (`bidi-lite` 0.1.3). Firefox tabs are attached to
21
+ their debugger with `Debugger::attach_to_context`: Firefox's Remote Agent
22
+ maps each BiDi context to its RDP tab, replacing the match by position in
23
+ the tab lists. Tabs on the same URL, several windows and error pages are
24
+ told apart, and a debugger tool never activates a tab any more (a tab
25
+ Firefox cannot map is an error, not a guess).
26
+ - Firefox `set_breakpoint` uses bidi-lite's `set_breakpoint_resolved`: the
27
+ bound column comes from the library (every inline script with the URL is
28
+ searched). A column-less breakpoint for a script not loaded yet fails with
29
+ an argument error asking for `column_number`, as before.
30
+
31
+ ## [0.2.0-beta.1] - 2026-10-13
32
+
33
+ Public **BETA**. Chrome and Firefox now answer the same replies; several are
34
+ incompatible with 0.1.x (marked **Breaking**; see the README's "Migration
35
+ from 0.1.x to 0.2.0").
36
+
37
+ ### Changed
38
+
39
+ - **Breaking:** `navigate` and `reload` answer the same shape on Chrome and
40
+ Firefox once the page fired `load` (at most 30 s): `{url, tab_id, browser,
41
+ notes?}`, `url` being the final URL after redirects and `browser` the
42
+ protocol's ids (Chrome used to answer the raw `Page.navigate` reply without
43
+ waiting, and `{}` on reload). `open_tab` reports the final URL too, and on
44
+ Firefox it now creates the tab blank and navigates once it is registered,
45
+ as on Chrome, so the page's first events are captured.
46
+ - **Breaking:** a network error fails `navigate` and `open_tab` on both
47
+ browsers with `Navigation failed: <browser's code> (<url>)` (Chrome used to
48
+ report success with `errorText` inside, or a tab on `chrome-error://`); from
49
+ `open_tab` the error names the tab, which stays open.
50
+ - **Breaking:** `evaluate_js` answers `{value, type, subtype?, description?}`
51
+ on both browsers (Chrome used to wrap the value in the CDP `RemoteObject`,
52
+ Firefox to return the bare value). Values without a JSON form (functions,
53
+ DOM nodes, symbols, `NaN`, bigints, maps...) have `value: null` and a
54
+ `description`; on Chrome they no longer fail with "Object couldn't be
55
+ returned by value" or answer `{}` (array-likes such as `NodeList` are
56
+ described, e.g. `NodeList(6)`, as on Firefox). A thrown exception fails
57
+ with the same wording on both: what was thrown plus its 1-based line and
58
+ column.
59
+ - **Breaking:** the debugger tools answer the same shapes on both browsers,
60
+ with 0-based lines everywhere: `set_breakpoint` returns an MCP id
61
+ (`bp-N`, also on Chrome instead of the CDP id) with the bound location and
62
+ `resolved`; `remove_breakpoint` takes that id and answers `{removed}`;
63
+ `step_over` and `resume` answer `{paused, location}` (Firefox's step
64
+ location was 1-based; Chrome reported none); `evaluate_on_call_frame`
65
+ answers the `evaluate_js` shape, and an exception in the frame is an error
66
+ on Chrome too (`Evaluation threw: ...`; it used to be a success carrying
67
+ `exceptionDetails`). `step_over` without a pause fails with "the debugger
68
+ is not paused" on both.
69
+ - **Breaking:** `get_console_logs` answers `{messages, total, truncated,
70
+ notes?}` instead of a bare array, returns at most `limit` messages (new
71
+ argument, default 200, the most recent ones), and `clear` removes only the
72
+ returned messages (it used to empty the whole buffer, even messages a
73
+ `level_filter` had hidden).
74
+
75
+ ### Added
76
+
77
+ - Partial `get_performance_metrics` and `profile_page_performance` on
78
+ Firefox, from the standard Web Performance APIs: `Nodes`, `Frames`,
79
+ `Resources` and `TransferSize`; FCP, LCP, DCL and Load (optionally after a
80
+ reload, cache bypassed with `disable_cache`). What Firefox cannot give (JS
81
+ heap, layout counts, long tasks, total blocking time) is `null` or listed
82
+ in a note, never a silent empty value.
83
+ - Firefox WebSocket frames in `get_network_logs`, and the console messages
84
+ WebDriver BiDi does not report (script errors with file and line, cookie
85
+ and security warnings, CSS warnings, platform messages) in
86
+ `get_console_logs`, from the RDP watcher of each tab's debugger, in the
87
+ same shapes as Chrome. Each tab's debugger is attached when the session
88
+ starts, when `open_tab` creates the tab, or at the first log call on a tab
89
+ the page opened, matched by its position in BiDi's and RDP's tab lists
90
+ (checked against their URLs), without bringing it to the front. Needs the
91
+ Firefox debugger port (default for a launched Firefox). Messages both feeds report are kept once; console
92
+ `source` labels say what a Firefox message is (`javascript`, `css`,
93
+ `security`, `network`, `other`).
94
+ - `--window-size <W>x<H>` (default `1920x1080`): launched Chrome and Firefox
95
+ get the same window size, headless too (they used to pick 921x648 and
96
+ 1280x887). Attached browsers keep their window.
97
+ - A tab's console buffer is cleared when the tab moves to another site
98
+ (registrable domain, via the `psl` crate's embedded Public Suffix List)
99
+ and kept across subdomains of one site, reloads and same-page
100
+ navigations; the next `get_console_logs` says what was cleared.
101
+
102
+ - Lifecycle crates bumped: `cdp-browser-lite` 0.4.2 and `bidi-browser-lite`
103
+ 0.1.4 (`bidi-lite` 0.1.2). Their mocks now record wire parameters, so the
104
+ Firefox page, input, network and debugger tools and the Chrome domain
105
+ setup are covered by hermetic tests instead of only by the E2E smoke.
106
+
107
+ ### Fixed
108
+
109
+ - Chrome `get_performance_metrics` returned `{}`: every tab now enables the
110
+ Performance domain when it is registered, the tool enables it on a session
111
+ that missed it, and an empty metric list is an error instead of `{}`.
112
+ - The "Chrome not found, use Firefox?" hint no longer ends the library's
113
+ sentence with two periods.
114
+
13
115
  ## [0.1.1-beta.1] - 2026-10-12
14
116
 
15
117
  Public **BETA**: known bugs and Chrome/Firefox differences are listed in the
package/README.md CHANGED
@@ -11,7 +11,7 @@ It is the successor of
11
11
  from a copy of its code) and complements Playwright MCP or
12
12
  chrome-devtools-mcp with runtime JavaScript debugging on both browsers.
13
13
 
14
- > **Status: BETA (0.1.1-beta.1).** This version contains known bugs and differences
14
+ > **Status: BETA (0.2.0-beta.2).** This version contains known bugs and differences
15
15
  > between Chrome and Firefox (see [Limitations and TODO](#limitations-and-todo))
16
16
  > and parts that have not been verified (see [Untested](#untested)). Tool
17
17
  > replies may change in incompatible ways before 1.0. Please report problems
@@ -54,11 +54,11 @@ see the note.
54
54
  | `click_element` | ✓ | ✓ | |
55
55
  | `fill_input` | ✓ | ✓ | |
56
56
  | `evaluate_js` | ✓ | ✓ | |
57
- | `get_console_logs` | ✓ | ✓ | |
58
- | `get_network_logs` | ✓ | partial | Firefox: HTTP requests and response bodies, no WebSocket frames |
57
+ | `get_console_logs` | ✓ | ✓ | Firefox: page errors, CSS warnings and platform messages need the debugger port |
58
+ | `get_network_logs` | ✓ | ✓ | Firefox: WebSocket frames need the debugger port |
59
59
  | `get_custom_events` | ✓ | partial | Firefox: events of the modules subscribed with `send_bidi_command` |
60
- | `get_performance_metrics` | ✓ | ✗ | Firefox has no equivalent of CDP's Performance domain |
61
- | `profile_page_performance` | ✓ | ✗ | Firefox has no equivalent of CDP's Tracing domain |
60
+ | `get_performance_metrics` | ✓ | partial | Firefox: `Nodes`, `Frames`, `Resources`, `TransferSize` from the Web Performance APIs (no Performance domain), plus a note |
61
+ | `profile_page_performance` | ✓ | partial | Firefox: FCP, LCP, DCL and Load from the Web Performance APIs; no trace, so `total_blocking_time_ms` and `top_long_tasks` are `null` |
62
62
  | `set_breakpoint` | ✓ | partial | Firefox: by URL only (no `script_id` / `script_hash`) |
63
63
  | `remove_breakpoint` | ✓ | ✓ | |
64
64
  | `resume` | ✓ | ✓ | |
@@ -76,7 +76,18 @@ see the note.
76
76
  | `webmcp_list_invocations` | ✓ | ✗ | needs `--enable-webmcp` |
77
77
 
78
78
  The JavaScript debugger tools on Firefox need its debugger (RDP) port: on by
79
- default for a launched Firefox, `--firefox-rdp-port` for an attached one.
79
+ default for a launched Firefox, `--firefox-rdp-port` for an attached one. The
80
+ same port gives Firefox's WebSocket frames and the console messages BiDi does
81
+ not report (script errors with their file and line, cookie and security
82
+ warnings, CSS warnings, platform messages), in the same shapes as Chrome.
83
+ Each tab's debugger watches them: the session's first tabs are attached when
84
+ the session starts and `open_tab` attaches its tab before loading the URL; a
85
+ tab the page opened is attached by the first `get_console_logs` or
86
+ `get_network_logs` on it. Firefox itself says which RDP tab shows a BiDi
87
+ tab (its Remote Agent maps the BiDi context to the tab's `browserId`), so
88
+ tabs on the same URL, several windows and error pages are told apart, and a
89
+ tab is never activated or brought to the front to attach it. Frames are
90
+ captured from the moment a tab is attached.
80
91
 
81
92
  ## 📦 Installation
82
93
 
@@ -262,8 +273,34 @@ claude mcp add browser-control-mcp -- npx -y @raultov/browser-control-mcp
262
273
  - **Tabs.** `open_tab`, `list_tabs`, `switch_tab`, `close_tab`. Every tool
263
274
  takes `instance_id` and `tab_id`; without them it targets the default
264
275
  instance and its active tab.
276
+ - **Same replies on both browsers.** `navigate` and `reload` answer once the
277
+ page fired `load` (at most 30 s, else with a note): a first line
278
+ (`Navigated to <final URL>.` / `Page reloaded.`), then
279
+ `{"url": "<final URL, after redirects>", "tab_id": "tab-N", "browser": {...}}`,
280
+ where `browser` holds the protocol's ids (CDP `frameId`/`loaderId`, BiDi
281
+ `navigation`). `open_tab` answers `{tab_id, target_id, url, label}` with the
282
+ final URL. A network error (unknown host, refused connection...) fails with
283
+ `Navigation failed: <browser's code> (<url>)`, e.g.
284
+ `net::ERR_NAME_NOT_RESOLVED` on Chrome and `NS_ERROR_UNKNOWN_HOST` on
285
+ Firefox; from `open_tab` the error names the tab, which stays open.
286
+ `evaluate_js` answers `{"value", "type", "subtype"?, "description"?}`:
287
+ `type` is JavaScript's `typeof`, `subtype` refines objects (`array`,
288
+ `null`, `node`, `date`, `map`...), `value` is the JSON form and is `null`
289
+ when there is none (functions, DOM nodes, symbols, `NaN`, bigints,
290
+ maps...), `description` then says what it was (Chrome describes functions
291
+ by their source, Firefox only as `function`). A thrown exception or a
292
+ rejected promise fails with what was thrown and its 1-based line and
293
+ column, e.g. `ReferenceError: x is not defined (line 1, column 1)`.
265
294
  - **Per-tab state.** Console logs, network traffic, breakpoints and custom
266
- events are kept per tab, in bounded buffers.
295
+ events are kept per tab, in bounded buffers. A tab's console buffer (last
296
+ 1000 messages) is cleared when the tab moves to another site (registrable
297
+ domain from the Public Suffix List: `www.knot.kz` and `docs.knot.kz` are
298
+ one site, `a.github.io` and `b.github.io` two) and kept across reloads and
299
+ same-page navigations. `get_console_logs` answers
300
+ `{"messages": [...], "total", "truncated", "notes"?}`: at most `limit`
301
+ (default 200) of the most recent matching messages, oldest first; `clear`
302
+ removes only the returned ones; a note says when a site change cleared
303
+ messages.
267
304
  - **Firefox allows a single automation session.** Each Firefox instance holds
268
305
  one WebDriver BiDi session shared by its tabs; `close_instance`,
269
306
  `stop_browser` and every server shutdown path end it, so the next run can
@@ -276,6 +313,7 @@ claude mcp add browser-control-mcp -- npx -y @raultov/browser-control-mcp
276
313
  | `--browser <chrome\|firefox>` | `chrome` | browser of the default instance | browser of the default instance |
277
314
  | `--local` | off | navigation limited to local addresses | same |
278
315
  | `--headless` | off | headless Chrome | headless Firefox |
316
+ | `--window-size <W>x<H>` | `1920x1080` | window of launched Chrome (`--window-size`) | window of launched Firefox (`--width`/`--height`) |
279
317
  | `--host <HOST>` | `127.0.0.1` | debugging host | debugging host |
280
318
  | `--port <PORT>` | `9222` | debugging port of the default instance | ignored (use `--firefox-port`) |
281
319
  | `--max-instances <N>` | `8` | instance cap | shared with Chrome |
@@ -334,9 +372,22 @@ Chrome and Firefox take the same calls:
334
372
  moves one statement; `resume` continues.
335
373
  6. `remove_breakpoint` with the id `set_breakpoint` returned.
336
374
 
375
+ Both browsers answer the same replies, with 0-based lines everywhere:
376
+
377
+ | Tool | Reply |
378
+ | --- | --- |
379
+ | `set_breakpoint` | `{"breakpoint_id": "bp-N", "url", "line_number", "column_number", "resolved"}`: where the browser bound it; `resolved` is false until a script with that URL loads |
380
+ | `remove_breakpoint` | `{"removed": "bp-N"}`; an unknown id fails listing the tab's ids |
381
+ | `step_over`, `resume` | `{"paused", "location": {"url", "line_number", "column_number"} \| null}`: where the thread stopped next (`step_over` waits up to 2 s; `resume` reports a pause only if one follows within 250 ms) |
382
+ | `evaluate_on_call_frame` | the `evaluate_js` shape; an exception fails with `Evaluation threw: <what was thrown>` |
383
+
384
+ Breakpoint ids are per tab and MCP-level (`bp-1`, `bp-2`...), like tab ids.
385
+
337
386
  Chrome also offers `pause_on_load` (pause on the first statement of the next
338
- load). Firefox notes: breakpoints by URL only; the column is snapped to the
339
- nearest breakable position automatically; the debugger needs the RDP port.
387
+ load). Firefox notes: breakpoints by URL only; without `column_number` the
388
+ breakpoint binds to the first breakable column of the line, in whichever of
389
+ the page's scripts with that URL has one (inline `<script>` blocks share the
390
+ page's URL), and needs the script loaded; the debugger needs the RDP port.
340
391
 
341
392
  ## Migration from chrome-debug-mcp
342
393
 
@@ -356,47 +407,39 @@ In client configurations replace the command `chrome-debug-mcp` with
356
407
  `browser-control-mcp` (or the npm package name). The old tool names answer an
357
408
  error that names the new ones.
358
409
 
410
+ ### Migration from 0.1.x to 0.2.0
411
+
412
+ 0.2.0 makes the replies the same on Chrome and Firefox; these changes are
413
+ incompatible:
414
+
415
+ | Tool | 0.1.x | 0.2.0 |
416
+ | --- | --- | --- |
417
+ | `navigate`, `reload` | Chrome: raw `Page.navigate` reply (`frameId`, `loaderId`, `errorText`) without waiting, `{}` on reload; Firefox: `{navigation, url}` | `{url, tab_id, browser, notes?}` after `load`; `url` is the final URL; protocol ids under `browser` |
418
+ | `navigate`, `open_tab` to an unreachable host | Chrome: success (`errorText` inside, or a `chrome-error://` tab) | error `Navigation failed: <code> (<url>)`; `open_tab` names the tab, which stays open |
419
+ | `evaluate_js` | Chrome: `{"result": {type, value, ...}}`; Firefox: the bare value | `{value, type, subtype?, description?}` |
420
+ | `set_breakpoint` | Chrome: CDP id (`1:3:0:http://…`) and `locations`; Firefox: `{breakpointId, url, lineNumber, columnNumber}` | `{breakpoint_id: "bp-N", url, line_number, column_number, resolved}` |
421
+ | `remove_breakpoint` | Chrome: CDP id, reply `{}` | `bp-N` id, reply `{removed}` |
422
+ | `step_over`, `resume` | Chrome: text only; Firefox `step_over`: `{paused, location: {line (1-based), column}}` | `{paused, location: {url, line_number, column_number} \| null}`, 0-based |
423
+ | `evaluate_on_call_frame` | Chrome: raw `RemoteObject` (exceptions as success); Firefox: bare value | the `evaluate_js` shape; exceptions are errors (`Evaluation threw: ...`) |
424
+ | `get_console_logs` | a JSON array; `clear` emptied the whole buffer | `{messages, total, truncated, notes?}`, at most `limit` (200) messages; `clear` removes the returned ones; the buffer is cleared on cross-site navigation |
425
+
359
426
  ## Limitations and TODO
360
427
 
361
- - **TODO (priority)** Unified `navigate` response: Chrome returns the CDP ids
362
- (`frameId`, `loaderId`) and Firefox `{navigation, url}`; both will return
363
- the final URL in the same shape.
364
- - **TODO (priority)** Unified `evaluate_js` result: Chrome wraps the value in
365
- the CDP `RemoteObject` (`{"result": {"type", "value"}}`) and Firefox
366
- returns the bare value; both will return the same shape.
367
- - **TODO (priority)** Navigation errors: on Chrome, `navigate` and `open_tab`
368
- to an unreachable host report success (the network error is only inside
369
- the reply, or absent); both browsers will fail with the network error.
370
- - **TODO (priority)** Console buffer per navigation: `get_console_logs` will
371
- clear a tab's buffer when it navigates to a different site (kept across
372
- subdomains of the same site) and gain a limit, so one call cannot flood
373
- the client.
374
- - **TODO (priority)** Unified debugger replies: `set_breakpoint`,
375
- `remove_breakpoint` and `step_over` answer differently on Chrome and
376
- Firefox (and Firefox's `step_over` location uses a 1-based line); both
377
- will return the same shape with 0-based lines.
378
- - **TODO (priority)** Known bug: on Chrome `get_performance_metrics` returns
379
- `{}` because the Performance domain is never enabled. Workaround:
380
- `send_cdp_command` with `Performance.enable` first.
381
- - **TODO (priority)** Partial performance on Firefox from the standard Web
382
- Performance APIs (navigation timings, FCP, LCP), stating what Firefox
383
- cannot give (long tasks, CLS, JS heap).
384
- - **TODO (priority)** Same default window size on both browsers (today
385
- Chrome 921x648, Firefox 1280x887): the screen size when known, else
386
- 1920x1080.
387
- - **TODO (priority)** Firefox WebSocket frames and the full console natively
388
- over the Firefox debugger (RDP watcher `websocket`, `error-message` and
389
- `css-message` resources), so both browsers return the same shape without
390
- any page injection.
428
+ - **TODO** Default window size from the screen size: launched browsers use
429
+ `--window-size` (default 1920x1080 on both); reading the screen size needs
430
+ native APIs per OS, left out of the server.
391
431
  - **TODO** `pause_on_load` on Firefox: neither a preload `debugger;`
392
432
  statement nor an RDP interrupt pauses a reloading page (spike verdict).
393
433
  - **TODO** `rust-mcp-sdk` 2.0: the server stays on 1.1.0 (MCP protocol
394
434
  2025-11-25). 2.0 moves to the stateless MCP protocol (2026-07-28), which
395
435
  today's MCP clients probably do not speak yet; upgrade once the clients do.
396
- - `get_performance_metrics` and `profile_page_performance` are Chrome only:
397
- Firefox has no Performance or Tracing domain.
398
- - WebSocket frames in `get_network_logs` are Chrome only: WebDriver BiDi has
399
- no WebSocket frame events.
436
+ - `get_performance_metrics` and `profile_page_performance` are partial on
437
+ Firefox: it has no Performance or Tracing domain, so they read what the
438
+ Web Performance APIs give and say what is missing (JS heap, layout and
439
+ style counts, long tasks, total blocking time).
440
+ - Firefox WebSocket frames and page errors need the Firefox debugger port
441
+ (WebDriver BiDi has no WebSocket frame events); binary frames are reported
442
+ as Firefox gives them, not base64 as on Chrome.
400
443
  - `webmcp_list_tools`, `webmcp_invoke_tool`, `webmcp_get_invocation` and
401
444
  `webmcp_list_invocations` are Chrome only (a Chrome testing feature).
402
445
  - `send_cdp_command` is Chrome only; `send_bidi_command` and
@@ -410,9 +453,9 @@ error that names the new ones.
410
453
  - Firefox: after a lost connection a launched Firefox gets a new session on
411
454
  the next tool call; an attached Firefox keeps its orphan session, so
412
455
  restart that Firefox.
413
- - Firefox debugger: it attaches to the tab Firefox has selected right after
414
- activating the requested one; switching tabs at that very moment can bind
415
- it to the wrong page.
456
+ - Firefox debugger: tabs are mapped to the debugger through Firefox's Remote
457
+ Agent (verified on Firefox 157 only); a Firefox without it answers the
458
+ debugger tools with an error instead of guessing a tab.
416
459
  - `get_network_logs` with response bodies has no total size limit (each
417
460
  Firefox body is capped at 2 MB): use `url_filter` on busy pages.
418
461
  - The Docker image is amd64 only.
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT",
21
21
  "name": "@raultov/browser-control-mcp",
22
- "version": "0.1.1-beta.1"
22
+ "version": "0.2.0-beta.2"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.1.1-beta.1"
51
+ "version": "0.2.0-beta.2"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/raultov/browser-control-mcp/releases/download/v0.1.1-beta.1"
3
+ "https://github.com/raultov/browser-control-mcp/releases/download/v0.2.0-beta.2"
4
4
  ],
5
5
  "author": "raultov <raultov@protonmail.com>",
6
6
  "bin": {
@@ -92,7 +92,7 @@
92
92
  "zipExt": ".tar.xz"
93
93
  }
94
94
  },
95
- "version": "0.1.1-beta.1",
95
+ "version": "0.2.0-beta.2",
96
96
  "volta": {
97
97
  "node": "18.14.1",
98
98
  "npm": "9.5.0"