@raultov/browser-control-mcp 0.10.0-beta.15 → 0.11.0-beta.17
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 +148 -0
- package/README.md +29 -18
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,154 @@ from a copy of its code: entries describe the differences from that release.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.11.0-beta.17] - 2026-11-02
|
|
14
|
+
|
|
15
|
+
Public **BETA**. Fixes found probing the debugger, tab and raw protocol tools
|
|
16
|
+
on both browsers. The browser's front tab now stays on the active tab (pages
|
|
17
|
+
in background tabs were hidden, with `requestAnimationFrame` stopped and
|
|
18
|
+
Chrome screenshots timing out); a click whose handler pauses or opens a
|
|
19
|
+
dialog after an `await` is noted at once; Chrome `snapshot`,
|
|
20
|
+
`find_in_snapshot` and `emulate` answer on a paused page; Firefox
|
|
21
|
+
`search_scripts` sees inline scripts and modules, `step_over` and `resume`
|
|
22
|
+
report a breakpoint hit again in a loop, and `get_custom_events` keeps every
|
|
23
|
+
subscribed event and RDP event packets; Chrome `navigate` to a hash route
|
|
24
|
+
reports the new URL; `webmcp_invoke_tool` passes a page tool's error and
|
|
25
|
+
blocks through. Behaviour changes: `open_tab` makes the new tab active.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `open_tab` makes the new tab the active one (the tools that omit `tab_id`
|
|
30
|
+
act on it), as the browser shows it, Playwright MCP's `browser_tabs` and
|
|
31
|
+
chrome-devtools-mcp's `new_page` do. Before, the previous tab stayed
|
|
32
|
+
active while the browser showed the new one. (#68)
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Firefox: `profile_page_performance` with `action: "reload"` and
|
|
37
|
+
`disable_cache: true` no longer fails with `Argument "ignoreCache" is
|
|
38
|
+
not supported yet`: it bypasses the cache with `network.setCacheBehavior`
|
|
39
|
+
(restored on every exit path). A reload Firefox aborts with
|
|
40
|
+
`NS_BINDING_ABORTED` right after navigating to a page that mounts frames
|
|
41
|
+
during hydration (as MDN does) is retried once after 50 ms instead of
|
|
42
|
+
failing. `forced_reflows.total_ms` no longer serializes `-0.0` when there
|
|
43
|
+
are none. (#58)
|
|
44
|
+
- Firefox: `search_scripts` finds every script of the page, not only the
|
|
45
|
+
first one Firefox announced: inline scripts, modules and scripts loaded
|
|
46
|
+
later were missing (`Total cached scripts: 1` on a page with five). Each
|
|
47
|
+
thread is now asked for its `sources`, merged with the announced ones;
|
|
48
|
+
pause locations and breakable columns use the same list. (#62)
|
|
49
|
+
- The browser's front tab is kept on the active tab. After a click that
|
|
50
|
+
opened a tab (`target=_blank`, a popup, `window.open`), after
|
|
51
|
+
`close_tab` and after a page or the user closed a tab, the active tab was
|
|
52
|
+
left in the background: its page was `hidden`, `requestAnimationFrame`
|
|
53
|
+
stopped (Chrome) or ran at about 1 s (Firefox), and on Chrome an element
|
|
54
|
+
screenshot hung 10 s before a raw CDP timeout. On Chrome,
|
|
55
|
+
`capture_screenshot` of a tab still in the background now fails at once,
|
|
56
|
+
naming `switch_tab`. On Chrome, a click whose reply came before the new
|
|
57
|
+
tab was registered no longer misses the "opened tab" note: it waits (at
|
|
58
|
+
most 2 s) for the pages Chrome announced. (#68)
|
|
59
|
+
- Chrome: `snapshot`, `find_in_snapshot` and `emulate` answer at once on a
|
|
60
|
+
page paused in the debugger, instead of failing after 10 s with
|
|
61
|
+
`CDP Error: Command Runtime.evaluate timed out after 10s`: their
|
|
62
|
+
synchronous snippets are no longer awaited (Chrome runs no microtask while
|
|
63
|
+
paused). `emulate` with `geolocation` on a paused page says the position
|
|
64
|
+
is read once the page resumes. (#64)
|
|
65
|
+
- An input action (`click_element`, `press_key`, `fill_form`, `drag`,
|
|
66
|
+
`hover`) whose handler pauses in the debugger or opens a dialog after an
|
|
67
|
+
async step (an `await`, a timer, a fetch) no longer waits 2 s and answers
|
|
68
|
+
a plain success: the watch of the page settling ends at once and the
|
|
69
|
+
reply notes `{paused, location}` or `{dialog}`, as for a handler that
|
|
70
|
+
blocks right away. Both browsers. (#65)
|
|
71
|
+
- Chrome: `navigate` to another fragment of the current page (a hash route
|
|
72
|
+
such as `#/contact`) answers the new URL; it often answered the previous
|
|
73
|
+
one, read from a history entry Chrome had not updated yet. The URL now
|
|
74
|
+
comes from the page's `Page.navigatedWithinDocument`. (#70)
|
|
75
|
+
- Firefox: `step_over` and `resume` that stop at the same place again (a
|
|
76
|
+
breakpoint in a loop, hit on the next iteration) report the pause with
|
|
77
|
+
its location, instead of waiting 2 s and answering `paused: false` while
|
|
78
|
+
the page was paused. (#63)
|
|
79
|
+
- `webmcp_invoke_tool` maps a page tool's MCP-shaped result
|
|
80
|
+
(`{content: [...], isError}`) to its own reply: the page's text and image
|
|
81
|
+
blocks as they are, and `isError: true` as a tool error. Before, a failed
|
|
82
|
+
page tool answered as a success with its error buried in the text, and
|
|
83
|
+
its output came as JSON escaped inside a string. (#72)
|
|
84
|
+
- Firefox: `get_custom_events` gets every event the agent subscribed to
|
|
85
|
+
with `send_bidi_command`, also those the server handles itself
|
|
86
|
+
(`browsingContext.load`, `contextCreated`, `log.entryAdded`,
|
|
87
|
+
`network.*`...), which were silently dropped. A tab opening or closing,
|
|
88
|
+
and an event without a context (`script.realmDestroyed`), go to the
|
|
89
|
+
active tab. The event packets an actor sends after a `send_rdp_request`
|
|
90
|
+
to it (`evaluationResult` after `evaluateJSAsync`, `pageError` after
|
|
91
|
+
`startListeners`) are kept too, as `rdp.<type>` (at most 16 actors
|
|
92
|
+
watched). Firefox itself sends `script.realmCreated` only for a new
|
|
93
|
+
tab's first document. (#71)
|
|
94
|
+
|
|
95
|
+
## [0.11.0-beta.16] - 2026-11-02
|
|
96
|
+
|
|
97
|
+
Public **BETA**. `audit_page` (`perf` group) audits the rendered page for
|
|
98
|
+
accessibility, SEO and best-practice problems on Chrome and Firefox, with the
|
|
99
|
+
same script and no Lighthouse bundle (each failing check lists its first 10
|
|
100
|
+
elements with their snapshot refs); `emulate` gains `cpu_throttling` and
|
|
101
|
+
`network_throttling` on Chrome; `profile_page_performance` adds INP, LCP image
|
|
102
|
+
discovery, DOM size, oversized/unsized images and third-party weight on both
|
|
103
|
+
browsers, and on Chrome extracts `forced_reflows` from the trace, measures
|
|
104
|
+
`unused_code` (JavaScript and CSS) on a reload and saves the raw trace with
|
|
105
|
+
`save_path`; `get_performance_metrics` takes `collect_garbage: true` and reads
|
|
106
|
+
`Memory.getDOMCounters` on Chrome. Behaviour changes: `get_performance_metrics`
|
|
107
|
+
sorts its metrics by name.
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- `emulate` gains `cpu_throttling` (`1` to `20`, e.g. `4` for a 4× slowdown)
|
|
112
|
+
and `network_throttling` (`slow-3g`, `fast-3g`, `4g` or `none`; Chrome
|
|
113
|
+
DevTools names such as `Slow 3G` and `Fast 3G` are accepted too) on Chrome,
|
|
114
|
+
kept in the tab's emulation state across calls, reported in the reply and
|
|
115
|
+
cleared by `reset: true`. On Firefox, where WebDriver BiDi has no CPU or
|
|
116
|
+
bandwidth/latency throttling, they fail naming them. (#52)
|
|
117
|
+
- `profile_page_performance` with `insights: true` adds Interaction to Next
|
|
118
|
+
Paint (`inp`: worst interaction, target and input/processing/presentation
|
|
119
|
+
breakdown), LCP image discovery (`lazy_loaded`, `fetch_priority`), DOM
|
|
120
|
+
size (`nodes`, `max_depth`, `widest_element`, `max_children`), oversized
|
|
121
|
+
or unsized images (`images`) and third-party origins (`third_parties`),
|
|
122
|
+
with findings when they cross the web.dev and Lighthouse thresholds, on
|
|
123
|
+
both Chrome and Firefox. (#53)
|
|
124
|
+
- `audit_page` (`perf` group) audits the rendered page for accessibility
|
|
125
|
+
(images without alt text; fields, buttons and links without a name;
|
|
126
|
+
`html lang`; heading order; duplicate ids; broken `aria-labelledby` and
|
|
127
|
+
`aria-describedby`; `tabindex` above 0; targets under 24×24 px too close
|
|
128
|
+
to another; text contrast under WCAG AA), SEO (title, meta description,
|
|
129
|
+
viewport, vague link text, uncrawlable links, canonical, `noindex`) and
|
|
130
|
+
best practices (doctype and quirks mode, HTTPS, mixed content, distorted
|
|
131
|
+
images, console errors), with the same script on Chrome and Firefox and
|
|
132
|
+
no Lighthouse bundle. Each failing check lists its first 10 elements with
|
|
133
|
+
a snapshot ref, a selector, its start tag and a detail; `categories`
|
|
134
|
+
narrows it. (#55)
|
|
135
|
+
- `get_performance_metrics` takes `collect_garbage: true`, which collects
|
|
136
|
+
garbage before reading on Chrome (`HeapProfiler.collectGarbage`), and adds
|
|
137
|
+
the live `Documents`, `Nodes` and `JSEventListeners` of
|
|
138
|
+
`Memory.getDOMCounters`, so reads after repeating an action show what it
|
|
139
|
+
leaked; its metrics are sorted by name. On Firefox, where WebDriver BiDi
|
|
140
|
+
cannot force a collection, a note says so. (#57)
|
|
141
|
+
- `profile_page_performance` with `insights: true` and `action: "reload"`
|
|
142
|
+
adds `unused_code` on Chrome: the bytes of JavaScript that never ran and
|
|
143
|
+
of CSS that matched nothing during the load (`Profiler` precise coverage
|
|
144
|
+
and `CSS` rule usage, as Lighthouse's "Reduce unused JavaScript / CSS"),
|
|
145
|
+
with the 5 files that waste most and a finding over 50 KB. Without a
|
|
146
|
+
reload, and on Firefox (no coverage over WebDriver BiDi), it is `null`
|
|
147
|
+
with a note saying why. (#54)
|
|
148
|
+
- `profile_page_performance` on Chrome extracts `forced_reflows` from the
|
|
149
|
+
trace when `insights: true` (`Layout` and `UpdateLayoutTree` events with a
|
|
150
|
+
JavaScript stack, grouped by call site, with a finding over 30 ms; `null`
|
|
151
|
+
with a note on Firefox) and takes `save_path` (`.json` or `.json.gz` under
|
|
152
|
+
the upload roots) to write the raw trace for Chrome DevTools or Perfetto
|
|
153
|
+
(refused on Firefox before running). (#56)
|
|
154
|
+
|
|
155
|
+
### Fixed
|
|
156
|
+
|
|
157
|
+
- `snapshot` names an element by the text slotted into its shadow tree
|
|
158
|
+
(a web component's button labelled by a `<span><slot></span>`):
|
|
159
|
+
`innerText` does not see slotted nodes, so it had no name. (#55)
|
|
160
|
+
|
|
13
161
|
## [0.10.0-beta.15] - 2026-11-02
|
|
14
162
|
|
|
15
163
|
Public **BETA**. Fixes from a comparison round against Playwright MCP,
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ It is the successor of
|
|
|
13
13
|
from a copy of its code) and complements Playwright MCP or
|
|
14
14
|
chrome-devtools-mcp with runtime JavaScript debugging on both browsers.
|
|
15
15
|
|
|
16
|
-
> **Status: BETA (0.
|
|
16
|
+
> **Status: BETA (0.11.0-beta.17).** This version contains known bugs and differences
|
|
17
17
|
> between Chrome and Firefox (see [Limitations and TODO](#limitations-and-todo))
|
|
18
18
|
> and parts that have not been verified (see [Untested](#untested)). Tool
|
|
19
19
|
> replies may change in incompatible ways before 1.0. Please report problems
|
|
@@ -29,7 +29,7 @@ the same stdio client through the same tasks on Chrome 156 (headless,
|
|
|
29
29
|
Linux). Every outcome was checked in the page, not taken from the reply.
|
|
30
30
|
Tokens are estimated as characters / 4 for every server. browser-control's
|
|
31
31
|
tool lists and the HTTP authentication row were measured again on
|
|
32
|
-
2026-11-02,
|
|
32
|
+
2026-11-02, with the changes of #46 to #55.
|
|
33
33
|
|
|
34
34
|
**What each one can do**
|
|
35
35
|
|
|
@@ -48,7 +48,8 @@ tool lists and the HTTP authentication row were measured again on
|
|
|
48
48
|
| PDF | ✓ file or inline | ✓ file | ✗ |
|
|
49
49
|
| Cookies and web storage | ✓ values hidden unless asked | ✓ values shown | ✗ |
|
|
50
50
|
| Secrets masked in output (`--secrets`) | ✓ | ✓ | ✗ |
|
|
51
|
-
| Performance | vitals + insights (LCP breakdown, layout shifts, render-blocking, long scripts) | trace file | trace + insights
|
|
51
|
+
| Performance | vitals + insights (INP, LCP breakdown and discovery, layout shifts, render-blocking, long scripts, DOM size, images, third parties; on Chrome: forced reflows, unused JS/CSS, trace export `.json`/`.gz`, DOM/listener counters after GC); CPU and network throttling on Chrome | trace file | trace + insights; CPU and network throttling |
|
|
52
|
+
| Page audit (accessibility, SEO, best practices) | ✓ `audit_page`, on Chrome and Firefox: failing elements with snapshot refs, no bundle | ✗ | Lighthouse, Chrome only: scores, failing elements with selectors, reports saved to disk |
|
|
52
53
|
| Heap snapshots, CSS styles, screencast | ✗ | video, tracing | ✓ |
|
|
53
54
|
| Generates Playwright test code | ✗ | ✓ | ✗ |
|
|
54
55
|
| Telemetry | **none** | none found | usage statistics on by default; traced URLs sent to CrUX |
|
|
@@ -59,7 +60,7 @@ tool lists and the HTTP authentication row were measured again on
|
|
|
59
60
|
| Task | browser-control-mcp | Playwright MCP | chrome-devtools-mcp |
|
|
60
61
|
| --- | --- | --- | --- |
|
|
61
62
|
| Tool list in context, default tools | 6 730 tokens | 5 330 tokens | 7 770 tokens |
|
|
62
|
-
| Tool list in context, every opt-in tool on | 10
|
|
63
|
+
| Tool list in context, every opt-in tool on | 10 880 tokens | 12 040 tokens | not measured (optional categories) |
|
|
63
64
|
| Snapshot of Hacker News | **4 588 tokens** | 12 356 tokens | 6 869 tokens |
|
|
64
65
|
| Snapshot of Wikipedia "Web browser" | **12 643 tokens** (cut, searchable) | 34 349 tokens | 27 656 tokens |
|
|
65
66
|
| Snapshot of MDN `<input>` | **12 625 tokens** (cut, searchable) | 158 077 tokens | 76 654 tokens |
|
|
@@ -95,7 +96,7 @@ tools are off by default and listed only with `--tool-groups`:
|
|
|
95
96
|
| `storage` | `cookies`, `web_storage` | reads and changes session tokens |
|
|
96
97
|
| `files` | `upload_file`, `save_pdf` | reads and writes files of the machine |
|
|
97
98
|
| `raw` | `send_cdp_command`, `send_bidi_command`, `send_rdp_request`, `get_custom_events` | anything the protocol allows |
|
|
98
|
-
| `perf` | `get_performance_metrics`, `profile_page_performance` | niche |
|
|
99
|
+
| `perf` | `get_performance_metrics`, `profile_page_performance`, `audit_page` | niche |
|
|
99
100
|
| `emulation` | `emulate` | niche |
|
|
100
101
|
| `network` | `mock_route`, `list_routes`, `remove_route`, `set_offline` | changes what the page receives |
|
|
101
102
|
|
|
@@ -118,21 +119,21 @@ see the note.
|
|
|
118
119
|
| `list_instances` | ✓ | ✓ | each instance reports its `browser` and `capabilities` |
|
|
119
120
|
| `restart_browser` | ✓ | ✓ | was `restart_chrome`; refused on a Chrome already running on the port (attached, not launched by the server) |
|
|
120
121
|
| `stop_browser` | ✓ | ✓ | was `stop_chrome`; on an attached Chrome or Firefox it only disconnects (Firefox: ends the session), the browser keeps running and the reply says so |
|
|
121
|
-
| `open_tab` | ✓ | ✓ | |
|
|
122
|
-
| `close_tab` | ✓ | ✓ | |
|
|
122
|
+
| `open_tab` | ✓ | ✓ | the new tab becomes the active one, in front |
|
|
123
|
+
| `close_tab` | ✓ | ✓ | the next active tab is brought to the front |
|
|
123
124
|
| `list_tabs` | ✓ | ✓ | |
|
|
124
125
|
| `switch_tab` | ✓ | ✓ | |
|
|
125
126
|
| `navigate` | ✓ | ✓ | `history: "back"` or `"forward"` instead of `url` moves one entry in the tab's history and waits for the page to load (also when it comes from the back-forward cache, which fires no `load`); at either end it fails without moving; a URL the browser saves instead of opening (a download: `Content-Disposition: attachment`, or a type such as CSV) fails saying so, and the tab stays where it was; a page behind HTTP authentication (401) fails at once naming its scheme and realm and how to log in (credentials in the URL, `http://user:password@host/path`, which the browser keeps for the session); a page that pauses at a breakpoint or opens a dialog while loading fails at once naming it (see `resume`, `handle_dialog`) |
|
|
126
127
|
| `reload` | ✓ | ✓ | a pause, a dialog or HTTP authentication while loading fails at once, as with `navigate` |
|
|
127
128
|
| `scroll` | ✓ | ✓ | reports the window positions before and after |
|
|
128
|
-
| `emulate` | ✓ | partial | `emulation` group; `width`/`height`, `device_scale_factor`, `mobile` (touch), `color_scheme`, `reduced_motion`, `forced_colors`, `media` (`print`), `user_agent`, `locale`, `timezone` (checked by the page's `Intl` first), `geolocation` (the page is granted the permission to read it), `reset`; each call adds to the earlier ones; answers what the page now sees (with the position it reads); Firefox: no `mobile
|
|
129
|
+
| `emulate` | ✓ | partial | `emulation` group; `width`/`height`, `device_scale_factor`, `mobile` (touch), `color_scheme`, `reduced_motion`, `forced_colors`, `media` (`print`), `user_agent`, `locale`, `timezone` (checked by the page's `Intl` first), `geolocation` (the page is granted the permission to read it), `cpu_throttling` (1–20×), `network_throttling` (`slow-3g`, `fast-3g`, `4g`, `none`), `reset`; each call adds to the earlier ones; answers what the page now sees (with the position it reads and the active throttling); Firefox: no `mobile`, media or throttling options (BiDi has no media, touch or throttling emulation yet; they fail naming them) |
|
|
129
130
|
| `wait_for` | ✓ | ✓ | waits (checking every 100 ms, at most 30 s) until a visible text appears or is gone, a selector is `visible` / `hidden` / `attached` / `detached`, or the URL contains a text; a dialog or debugger pause ends the wait at once |
|
|
130
131
|
| `inspect_dom` | ✓ | ✓ | includes open shadow roots and same-origin iframes (cross-origin ones are noted); at most 100 000 characters without `query` (cut in the page) |
|
|
131
132
|
| `snapshot` | ✓ | ✓ | outline of the page: role, name, states, value and a ref (`e12`) per element, through open shadow roots and same-origin iframes, and on Chrome closed shadow roots too (pages up to 20 000 elements; their elements are reached by ref, not by `>>>`); frames of another origin are read too (at most 20, 5 deep), their iframe marked `[frame=f2]` and their refs prefixed (`f2e5`), and `selector: "f2"` outlines one; tables without headers (`th`, `thead`, `caption`) are layout and leave only their content, and a cell is named by its text only when it holds nothing else; comboboxes, spinbuttons and sliders built with ARIA show their value (the field's text, `aria-valuetext` or `aria-valuenow`, the option a button-like combobox shows), progress bars and meters theirs (`40/100`), and a `<details>` summary `[expanded=true|false]`; a field the page rejects (`aria-invalid`) or the browser does (`:user-invalid`, once the user changed it or tried to submit) is `[invalid]`, with `[error="..."]` from its `aria-errormessage`, `aria-describedby` or the browser's validation message; a native `<select>` lists its options under its combobox (`[selected]`, `[disabled]`, optgroups as groups; no refs: `fill_input` picks one), the first 25 and how many more, all of them for `find_in_snapshot`; what the page made inert is left out: `inert` subtrees and, while a modal `<dialog>` is open (marked `[modal]`), everything outside it; an element that is only clickable (a handler or a pointer cursor), draggable (`draggable="true"`, marked `[draggable]`) or a drop zone (`ondrop`/`ondragover`) is one named line with a ref for `drag`, unless it holds links, buttons or fields, which are listed under it; at most 50 000 characters (`selector` and `depth` narrow it); the same JavaScript on both browsers |
|
|
132
133
|
| `find_in_snapshot` | ✓ | ✓ | `text` (case-insensitive) or `regex` over the whole outline; matching lines under their ancestors, at most 20 |
|
|
133
134
|
| `save_pdf` | ✓ | ✓ | `files` group; prints the tab as the browser's print does (`landscape`, `print_background`, `scale` 0.1–2, `page_ranges` like `1-3, 5`); with `path` (an absolute `.pdf` under the upload roots) writes the file and answers `{path, bytes, pages}`, else returns the PDF as an embedded `application/pdf` resource (at most 5 MB); Chrome `Page.printToPDF`, Firefox `browsingContext.print` |
|
|
134
|
-
| `capture_screenshot` | ✓ | ✓ | `format` `png`, `jpeg` (or `jpg`) or `webp`; `full_page` at most 16 384 px per side (a text note says when cut); `selector` captures one element (CSS, `host >>> inner` or a snapshot ref), scrolled into view, whole even when taller than the viewport |
|
|
135
|
-
| `click_element` | ✓ | ✓ | scrolls the element into view; `button` (left/right/middle), `click_count` (2 = double click) and `modifiers`; fails without clicking when it is hidden, covered (its own label on top does not count: styled checkboxes and switches are clicked through it; a covered element first gets the mouse, so controls shown only on hover are revealed and clicked) or a disabled control, whose handler would never run; an element a CSS selector does not find yet, or a disabled control, is checked again for up to 2 s, as pages add elements and enable buttons a moment after the previous action (a ref that names nothing, a hidden or a covered element fail at once); `host >>> inner` selectors reach open shadow roots and same-origin iframes (also in `fill_input`, `scroll`, `wait_for`); a dialog or debugger pause the click causes is noted in a successful reply (`{dialog}` or `{paused, location}`) |
|
|
135
|
+
| `capture_screenshot` | ✓ | ✓ | `format` `png`, `jpeg` (or `jpg`) or `webp`; `full_page` at most 16 384 px per side (a text note says when cut); `selector` captures one element (CSS, `host >>> inner` or a snapshot ref), scrolled into view, whole even when taller than the viewport; on Chrome a tab in the background (hidden) is refused at once, naming `switch_tab` |
|
|
136
|
+
| `click_element` | ✓ | ✓ | scrolls the element into view; `button` (left/right/middle), `click_count` (2 = double click) and `modifiers`; fails without clicking when it is hidden, covered (its own label on top does not count: styled checkboxes and switches are clicked through it; a covered element first gets the mouse, so controls shown only on hover are revealed and clicked) or a disabled control, whose handler would never run; an element a CSS selector does not find yet, or a disabled control, is checked again for up to 2 s, as pages add elements and enable buttons a moment after the previous action (a ref that names nothing, a hidden or a covered element fail at once); `host >>> inner` selectors reach open shadow roots and same-origin iframes (also in `fill_input`, `scroll`, `wait_for`); a dialog or debugger pause the click causes is noted in a successful reply (`{dialog}` or `{paused, location}`), also when its handler blocks the page after an `await` or a timer, while the page is watched to settle |
|
|
136
137
|
| `fill_input` | ✓ | ✓ | picks `<select>` options (on a `<select multiple>`, exactly the ones named one per line; an unknown one changes nothing); sets `date`, `time`, `datetime-local`, `month`, `week`, `range` and `color` inputs to the text in their own format (`2026-10-31`, `13:45`, `72`, `#ff0000`), firing `input` and `change`, noting a value the input adjusted (a range snaps to its step) and failing, without changing it, on a malformed one; fails without typing on elements that cannot take the text; `append: true` types after the content instead of replacing it (a number or email field, which has no caret, is retyped whole); an editor of its own (an element with an `EditContext` or a focusable `textbox` role, or the hidden input of a code editor such as Monaco or Ace) gets the keys a user presses: a click on the editor when its input has no visible box, Control+A (Command+A on macOS) or Control+End, then the text, and the reply says its content could not be checked; in a contenteditable element (including rich text editors such as Slate, Lexical, ProseMirror or CodeMirror 6) it selects the content (a range, then Control+A as a user would) and types over it, so the editor records the edit; types key by key on both (key handlers run; newlines and tabs inserted as text), about 1 ms per character; `change` on leaving the field, or at the end when the window has no focus |
|
|
137
138
|
| `press_key` | ✓ | ✓ | one key (a character, Enter, Tab, Escape, arrows, F1–F12...) with Control / Shift / Alt / Meta held (in `modifiers`, or written before the key as `Control+a`, `Shift+Tab` or `ControlOrMeta+c`: Meta on macOS, Control elsewhere), in `selector` (focused first) or the focused element; a shortcut types nothing |
|
|
138
139
|
| `hover` | ✓ | ✓ | moves the mouse over the element (scrolled into view; fails when hidden or covered) |
|
|
@@ -149,22 +150,23 @@ see the note.
|
|
|
149
150
|
| `list_routes` | ✓ | ✓ | `network` group; the tab's routes with the `hits` each answered |
|
|
150
151
|
| `remove_route` | ✓ | ✓ | `network` group; one route by id, or all |
|
|
151
152
|
| `set_offline` | ✓ | ✓ | `network` group; requests fail as without a network and `navigator.onLine` is false, until set back (Chrome `Network.emulateNetworkConditions`, Firefox `emulation.setNetworkConditions`); mocked routes still answer; Chrome still serves what its HTTP cache holds, as an offline browser does, Firefox fails every request |
|
|
152
|
-
| `get_custom_events` | ✓ |
|
|
153
|
-
| `get_performance_metrics` | ✓ | partial | `perf` group; Firefox: `Nodes`, `Frames`, `Resources`, `TransferSize` from the Web Performance APIs (no Performance domain), plus
|
|
154
|
-
| `profile_page_performance` | ✓ | partial | `perf` group; `insights: true` adds what explains the numbers, read with the Web Performance APIs (same JavaScript on both): the document's TTFB, server time and compression, the LCP element
|
|
153
|
+
| `get_custom_events` | ✓ | ✓ | `raw` group; `{events, total, truncated}`, the newest `limit` events; `clear` removes the returned ones; params over 64 000 characters of JSON are replaced by `{truncated, characters}`; Firefox: events of the modules subscribed with `send_bidi_command` (also those the server handles, such as `browsingContext.load`; a tab opening or closing, or an event without a context, goes to the active tab) and the event packets of actors addressed with `send_rdp_request` (`rdp.<type>`, e.g. `rdp.evaluationResult`) |
|
|
154
|
+
| `get_performance_metrics` | ✓ | partial | `perf` group; Chrome's metrics sorted by name, with the live `Documents`, `Nodes` and `JSEventListeners` of `Memory.getDOMCounters`; `collect_garbage: true` collects garbage first (`HeapProfiler.collectGarbage`), so reads after repeating an action show what it leaked; Firefox: `Nodes`, `Frames`, `Resources`, `TransferSize` from the Web Performance APIs (no Performance domain) and no garbage collection (WebDriver BiDi has none), plus notes |
|
|
155
|
+
| `profile_page_performance` | ✓ | partial | `perf` group; `save_path` (Chrome only) writes the raw trace (`.json` or `.json.gz`, under the upload roots) for Chrome DevTools or Perfetto; `insights: true` adds what explains the numbers, read with the Web Performance APIs and the DOM (same JavaScript on both): the document's TTFB, server time and compression, the LCP element, its breakdown (TTFB, load delay, load, render delay) and image discovery (`lazy_loaded`, `fetch_priority`), Interaction to Next Paint (`inp`: duration, event, target and input/processing/presentation breakdown), layout-shift culprits, render-blocking requests, the slowest requests, the scripts of long animation frames, DOM size (`nodes`, `max_depth`, `widest_element`, `max_children`), oversized or unsized images, and third-party origins by duration and bytes (at most 5 each), plus `findings` from the web.dev and Lighthouse thresholds; on Chrome, `forced_reflows` from the trace (`Layout`/`UpdateLayoutTree` forced by a script, grouped by call site, a finding over 30 ms) and, with `action: "reload"`, `unused_code` (`unused_js_bytes`, `unused_css_bytes` and the 5 files with most unused bytes, from the `Profiler` precise coverage and `CSS` rule usage of the reload; a finding over 50 KB; without a reload it is `null`: code run before the recording would count as unused); Firefox: FCP, LCP, DCL and Load; no trace (`save_path` fails), so `total_blocking_time_ms` and `top_long_tasks` are `null`, and its insights have no layout shifts, render-blocking requests, long frames, forced reflows or unused code (`null`, with notes) |
|
|
156
|
+
| `audit_page` | ✓ | ✓ | `perf` group; accessibility (images without alt, fields, buttons and links without a name, `html lang`, heading order, duplicate ids, broken `aria-labelledby`/`aria-describedby`, `tabindex` > 0, targets under 24×24 px too close to another, text contrast under WCAG AA), SEO (title, meta description, viewport, vague link text, uncrawlable links, canonical, `noindex`) and best practices (doctype and quirks mode, HTTPS, mixed content, distorted images, console errors); the same script on both browsers; each failing check lists its first 10 elements with a snapshot ref, a selector, its start tag and a detail (the contrast ratio, the size...), then the ids that passed or did not apply; `categories` narrows it; not checked: text over background images or gradients, iframes, closed shadow roots, response headers |
|
|
155
157
|
| `set_breakpoint` | ✓ | partial | Firefox: by URL only (no `script_id` / `script_hash`) |
|
|
156
158
|
| `remove_breakpoint` | ✓ | ✓ | |
|
|
157
159
|
| `resume` | ✓ | ✓ | |
|
|
158
160
|
| `step_over` | ✓ | ✓ | |
|
|
159
161
|
| `evaluate_on_call_frame` | ✓ | ✓ | |
|
|
160
|
-
| `search_scripts` | ✓ | ✓ | a long line's preview is 100 characters on each side of the match; frames of another origin too (Chrome ids such as `2.17`) |
|
|
162
|
+
| `search_scripts` | ✓ | ✓ | a long line's preview is 100 characters on each side of the match; inline scripts and modules included; frames of another origin too (Chrome ids such as `2.17`) |
|
|
161
163
|
| `pause_on_load` | ✓ | ✗ | TODO on Firefox, see [Limitations and TODO](#limitations-and-todo) |
|
|
162
164
|
| `enable_proxy_auth` | ✓ | partial | needs `--proxy-server`; a request the proxy rejects twice is cancelled; untested on Firefox against a real proxy |
|
|
163
165
|
| `send_cdp_command` | ✓ | ✗ | `raw` group; raw Chrome DevTools Protocol; refuses `Network`/`Page`/`Runtime`/`Log`/`Debugger.disable` and `Browser.close`; `Fetch.enable` comes with a note |
|
|
164
166
|
| `send_bidi_command` | ✗ | ✓ | `raw` group; raw WebDriver BiDi; refuses `session.new`, `session.end`, `browser.close` |
|
|
165
|
-
| `send_rdp_request` | ✗ | ✓ | `raw` group; raw Firefox RDP request to an actor (`thread`, `console`, `root` shortcuts) |
|
|
167
|
+
| `send_rdp_request` | ✗ | ✓ | `raw` group; raw Firefox RDP request to an actor (`thread`, `console`, `root` shortcuts); the event packets that actor sends afterwards go to `get_custom_events` (at most 16 actors watched) |
|
|
166
168
|
| `webmcp_list_tools` | ✓ | ✗ | needs `--enable-webmcp`; WebMCP is a Chrome testing feature; every annotation Chrome reports is listed (`readOnly`, `consequential`, `untrustedContent`, `debugging`, `autosubmit`, newer ones as they are) |
|
|
167
|
-
| `webmcp_invoke_tool` | ✓ | ✗ | needs `--enable-webmcp`; the output of an `untrustedContent` tool comes with a note to treat it as data |
|
|
169
|
+
| `webmcp_invoke_tool` | ✓ | ✗ | needs `--enable-webmcp`; a tool answering in MCP shape (`{content, isError}`) gives its own text and image blocks, and its `isError` makes the reply an error; other output comes as JSON; the output of an `untrustedContent` tool comes with a note to treat it as data |
|
|
168
170
|
| `webmcp_get_invocation` | ✓ | ✗ | needs `--enable-webmcp` |
|
|
169
171
|
| `webmcp_list_invocations` | ✓ | ✗ | needs `--enable-webmcp` |
|
|
170
172
|
|
|
@@ -377,7 +379,12 @@ claude mcp add browser-control-mcp -- npx -y @raultov/browser-control-mcp
|
|
|
377
379
|
lazily, on the first tool that needs them, with an ephemeral profile.
|
|
378
380
|
- **Tabs.** `open_tab`, `list_tabs`, `switch_tab`, `close_tab`. Every tool
|
|
379
381
|
takes `instance_id` and `tab_id`; without them it targets the default
|
|
380
|
-
instance and its active tab.
|
|
382
|
+
instance and its active tab. The active tab is the one the browser shows:
|
|
383
|
+
`open_tab` makes its new tab active, `close_tab` brings the next one to
|
|
384
|
+
the front, and after a page opens or closes a tab (a `target=_blank`
|
|
385
|
+
link, a popup, `window.close()`) the active tab is brought back to the
|
|
386
|
+
front. Background tabs are hidden to their pages (no
|
|
387
|
+
`requestAnimationFrame`, throttled timers). An argument a tool does not have fails the
|
|
381
388
|
call before anything runs (a `tabId` typo answers "did you mean
|
|
382
389
|
`tab_id`?"), so a typo never falls back to the active tab.
|
|
383
390
|
- **Same replies on both browsers.** `navigate` and `reload` answer once the
|
|
@@ -511,7 +518,7 @@ one subscription per session, routed by browsing context id.
|
|
|
511
518
|
|
|
512
519
|
Chrome and Firefox take the same calls:
|
|
513
520
|
|
|
514
|
-
1. `navigate` to the page (or `open_tab`
|
|
521
|
+
1. `navigate` to the page (or `open_tab`).
|
|
515
522
|
2. `search_scripts` with a needle to find the script URL and line.
|
|
516
523
|
3. `set_breakpoint` with `url` and `line_number` (0-based).
|
|
517
524
|
4. Trigger the code (`click_element`, `evaluate_js` with a `setTimeout`, a
|
|
@@ -599,6 +606,10 @@ incompatible:
|
|
|
599
606
|
- **TODO** `rust-mcp-sdk` 2.0: the server stays on 1.1.0 (MCP protocol
|
|
600
607
|
2025-11-25). 2.0 moves to the stateless MCP protocol (2026-07-28), which
|
|
601
608
|
today's MCP clients probably do not speak yet; upgrade once the clients do.
|
|
609
|
+
- On Firefox, a reload with `disable_cache: true` bypasses the cache with
|
|
610
|
+
`network.setCacheBehavior` (Firefox rejects `ignoreCache` on
|
|
611
|
+
`browsingContext.reload`); a reload Firefox aborts with
|
|
612
|
+
`NS_BINDING_ABORTED` is retried once after 50 ms.
|
|
602
613
|
- `get_performance_metrics` and `profile_page_performance` are partial on
|
|
603
614
|
Firefox: it has no Performance or Tracing domain, so they read what the
|
|
604
615
|
Web Performance APIs give and say what is missing (JS heap, layout and
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"hasInstallScript": true,
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"name": "@raultov/browser-control-mcp",
|
|
22
|
-
"version": "0.
|
|
22
|
+
"version": "0.11.0-beta.17"
|
|
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.
|
|
51
|
+
"version": "0.11.0-beta.17"
|
|
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.
|
|
3
|
+
"https://github.com/raultov/browser-control-mcp/releases/download/v0.11.0-beta.17"
|
|
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.
|
|
95
|
+
"version": "0.11.0-beta.17",
|
|
96
96
|
"volta": {
|
|
97
97
|
"node": "18.14.1",
|
|
98
98
|
"npm": "9.5.0"
|