@raultov/browser-control-mcp 0.6.0-beta.9 → 0.7.0-beta.11
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 +115 -0
- package/README.md +16 -13
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,121 @@ from a copy of its code: entries describe the differences from that release.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.7.0-beta.11] - 2026-10-31
|
|
14
|
+
|
|
15
|
+
Public **BETA**. Closes gaps with Playwright MCP and chrome-devtools-mcp and
|
|
16
|
+
fixes editors found in real pages: a new `drag` tool (sortable lists,
|
|
17
|
+
boards, sliders, HTML5 drag and drop), `navigate` back and forward in the
|
|
18
|
+
tab's history, `fill_input` with `append`, and `capture_screenshot` of one
|
|
19
|
+
element; `fill_input` now types into Monaco, CodeMirror 6 on Firefox and
|
|
20
|
+
TinyMCE on Firefox, and `snapshot` shows the value of ARIA comboboxes,
|
|
21
|
+
spinbuttons and sliders. Behaviour changes: the default tool list has one
|
|
22
|
+
more tool (about 390 more tokens), `navigate`'s `url` is optional (pass it
|
|
23
|
+
or `history`), and `fill_input` on an editor whose content the page does
|
|
24
|
+
not expose answers that the text was not checked.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `drag` tool: drags an element onto another with the mouse. It waits for
|
|
29
|
+
running animations to end (a sortable list animating its last
|
|
30
|
+
reordering starts no drag), presses on `from` (checked like
|
|
31
|
+
`click_element`), moves in 10 steps 40 ms apart to the centre of `to` and
|
|
32
|
+
releases, so pointer-driven libraries (sortable lists, boards, sliders)
|
|
33
|
+
and HTML5 drag and drop both work. Chrome does both natively; Firefox's
|
|
34
|
+
WebDriver input starts a native HTML5 drag but never drops it, leaving the
|
|
35
|
+
page stuck mid-drag, so there the native `dragstart` is cancelled and the
|
|
36
|
+
drag replayed by script along the path (the reply says so). Checked on
|
|
37
|
+
SortableJS and the-internet's drag and drop on both browsers. (#30)
|
|
38
|
+
- `fill_input` takes `append: true`: the text is typed at the end of what
|
|
39
|
+
the field already holds instead of replacing it, in text fields,
|
|
40
|
+
textareas and contenteditable elements, rich text editors included (the
|
|
41
|
+
caret goes to the end of their last text, so the editor records the
|
|
42
|
+
edit). A number, email or date field, which has no caret to move, is
|
|
43
|
+
emptied and retyped whole. The reply says `appended`; a field that did
|
|
44
|
+
not change is an error. Adding to a draft took one `press_key` per
|
|
45
|
+
character before. (#32)
|
|
46
|
+
- `navigate` takes `history: "back"` or `"forward"` instead of `url`: it
|
|
47
|
+
moves one entry in the tab's history and waits for that page to load,
|
|
48
|
+
answering like a URL navigation (`Went back to <url>.`). A page restored
|
|
49
|
+
from the back-forward cache, or an entry of the same document
|
|
50
|
+
(`pushState`), answers at once; at either end of the history it fails
|
|
51
|
+
without moving. Before, going back took `evaluate_js` with
|
|
52
|
+
`history.back()`, which answered before the page loaded. (#31)
|
|
53
|
+
- `capture_screenshot` takes a `selector` and captures only that element
|
|
54
|
+
(CSS, `host >>> inner` into shadow roots and iframes, or a snapshot ref):
|
|
55
|
+
scrolled into view, clipped to its box, whole even when taller than the
|
|
56
|
+
viewport (at most 16 384 px per side, with a note). Checking one component
|
|
57
|
+
no longer costs a whole viewport image. (#33)
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- On Firefox, `fill_input`, `fill_form` and `press_key` type into an
|
|
62
|
+
element inside an iframe (TinyMCE): its script focus left the top
|
|
63
|
+
document's focus on its `<body>`, where Firefox sent the keys, so nothing
|
|
64
|
+
reached the editor and `fill_input` reported that the field held
|
|
65
|
+
"something other than the text". Each iframe the element is in is now
|
|
66
|
+
focused first. (#35)
|
|
67
|
+
- `fill_input` replaces the content of CodeMirror 6 on Firefox: the text
|
|
68
|
+
was typed before the old content, because CodeMirror puts its own caret
|
|
69
|
+
back once focused and drops the selection #23 made with a DOM range. A
|
|
70
|
+
contenteditable element now also gets the keys a user presses after the
|
|
71
|
+
range: Control+A (Command+A on macOS) to replace, Control+End with
|
|
72
|
+
`append`. Checked with CodeMirror 6, ProseMirror, Lexical and the test
|
|
73
|
+
page's editor on both browsers. (#34)
|
|
74
|
+
- `fill_input` types into editors of their own, such as Monaco (VS Code
|
|
75
|
+
for the web): the snapshot named Monaco's input `textbox "JavaScript
|
|
76
|
+
Input"`, but `fill_input` refused it (not an input, textarea or
|
|
77
|
+
contenteditable element) on Chrome, where it is an `EditContext`
|
|
78
|
+
element, and on Firefox, where it is a hidden textarea, reported success
|
|
79
|
+
while the editor never got the text. Such an editor (an `EditContext`,
|
|
80
|
+
a focusable `textbox` role, or a text field without a visible box) now
|
|
81
|
+
gets a click on its visible container when its input has none, Control+A
|
|
82
|
+
(Command+A on macOS) or, with `append`, Control+End, then the text; its
|
|
83
|
+
content cannot be read back, so the reply says it was not checked. (#28)
|
|
84
|
+
- `snapshot` and `find_in_snapshot` show the value of ARIA widgets: an
|
|
85
|
+
`<input role="combobox">` its text (MUI Autocomplete after a pick), a
|
|
86
|
+
spinbutton or slider its `aria-valuetext` or `aria-valuenow` (MUI X
|
|
87
|
+
DatePicker sections), and a button-like combobox the option it shows
|
|
88
|
+
(Radix Select). Before, only text fields, sliders and `<select>` had one,
|
|
89
|
+
so the agent could not see that a pick took. (#29)
|
|
90
|
+
|
|
91
|
+
## [0.6.0-beta.10] - 2026-10-31
|
|
92
|
+
|
|
93
|
+
Public **BETA**. Fixes four input and snapshot bugs found while driving a
|
|
94
|
+
real web app (Discord): `fill_input` now types into rich text editors
|
|
95
|
+
(Slate, Lexical, ProseMirror) so the editor records the edit, styled
|
|
96
|
+
checkboxes covered by their own label are clicked, controls shown only on
|
|
97
|
+
hover are revealed and clicked, and `snapshot` lists the links and buttons
|
|
98
|
+
inside a clickable container. Behaviour change: a covered element now gets
|
|
99
|
+
a mouse move before the click is refused.
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- `click_element`, `hover` and `fill_form` reach controls shown only while
|
|
104
|
+
the mouse is over something (a message's actions, a row's toolbar): a
|
|
105
|
+
covered element now gets the mouse first and is checked again 100 ms
|
|
106
|
+
later. Discord's message "More" button failed with "covered by
|
|
107
|
+
'div.message…'". An element that stays covered still fails, naming what
|
|
108
|
+
covers it, without a click. (#26)
|
|
109
|
+
- `snapshot` and `find_in_snapshot` list what a clickable container holds:
|
|
110
|
+
an element without a role that has a click handler or a pointer cursor
|
|
111
|
+
was a leaf named from its whole text, so a container that sets
|
|
112
|
+
`cursor: pointer` hid every link and button inside it (Discord's whole
|
|
113
|
+
channel list was one line). A clickable that holds interactive elements
|
|
114
|
+
now lists them; one with text only is still one named line. (#25)
|
|
115
|
+
- `click_element`, `hover` and `fill_form` click a control covered by its
|
|
116
|
+
own label (or the element its `aria-labelledby` names), as styled
|
|
117
|
+
checkboxes and switches are: they failed with "covered by 'span…'". An
|
|
118
|
+
element under anything else still fails, naming it. (#24)
|
|
119
|
+
- `fill_input` and `fill_form` work in rich text editors (Slate, Lexical,
|
|
120
|
+
ProseMirror, Draft.js): a contenteditable element was emptied with
|
|
121
|
+
`textContent = ''`, which removed the nodes the editor renders from its
|
|
122
|
+
own model, so the typed text showed in the page but the editor did not
|
|
123
|
+
record it (Discord's channel topic was never saved), and the tool
|
|
124
|
+
reported success. The content is now selected and the first typed key
|
|
125
|
+
replaces it, as when a user selects all and types; an empty text deletes
|
|
126
|
+
it through the editing commands. (#23)
|
|
127
|
+
|
|
13
128
|
## [0.6.0-beta.9] - 2026-10-31
|
|
14
129
|
|
|
15
130
|
Public **BETA**. Spends fewer tokens and answers what the agent needs, after
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# browser-control-mcp
|
|
2
2
|
|
|
3
|
+
[](https://discord.gg/JDfcmD73A)
|
|
4
|
+
|
|
3
5
|
**Browser Control MCP** lets an AI agent control **and debug** real browsers
|
|
4
6
|
at runtime: breakpoints, pause on load, call frames, evaluation in a paused
|
|
5
7
|
frame, stepping and script search on **Chrome (CDP)** and **Firefox
|
|
@@ -11,13 +13,13 @@ It is the successor of
|
|
|
11
13
|
from a copy of its code) and complements Playwright MCP or
|
|
12
14
|
chrome-devtools-mcp with runtime JavaScript debugging on both browsers.
|
|
13
15
|
|
|
14
|
-
> **Status: BETA (0.
|
|
16
|
+
> **Status: BETA (0.7.0-beta.11).** This version contains known bugs and differences
|
|
15
17
|
> between Chrome and Firefox (see [Limitations and TODO](#limitations-and-todo))
|
|
16
18
|
> and parts that have not been verified (see [Untested](#untested)). Tool
|
|
17
19
|
> replies may change in incompatible ways before 1.0. Please report problems
|
|
18
20
|
> in the [issues](https://github.com/raultov/browser-control-mcp/issues).
|
|
19
21
|
|
|
20
|
-
💬 Questions, ideas or feedback: join us on [Discord](https://discord.gg/
|
|
22
|
+
💬 Questions, ideas or feedback: join us on [Discord](https://discord.gg/JDfcmD73A), where every release is announced in #announcements.
|
|
21
23
|
|
|
22
24
|
## Comparison with Playwright MCP and chrome-devtools-mcp
|
|
23
25
|
|
|
@@ -52,8 +54,8 @@ Tokens are estimated as characters / 4 for every server.
|
|
|
52
54
|
|
|
53
55
|
| Task | browser-control-mcp | Playwright MCP | chrome-devtools-mcp |
|
|
54
56
|
| --- | --- | --- | --- |
|
|
55
|
-
| Tool list in context, default tools | 6
|
|
56
|
-
| Tool list in context, every opt-in tool on | 9
|
|
57
|
+
| Tool list in context, default tools | 6 430 tokens | 5 330 tokens | 7 770 tokens |
|
|
58
|
+
| Tool list in context, every opt-in tool on | 9 880 tokens | 12 040 tokens | not measured (optional categories) |
|
|
57
59
|
| Snapshot of Hacker News | **4 588 tokens** | 12 356 tokens | 6 869 tokens |
|
|
58
60
|
| Snapshot of Wikipedia "Web browser" | **12 643 tokens** (cut, searchable) | 34 349 tokens | 27 656 tokens |
|
|
59
61
|
| Snapshot of MDN `<input>` | **12 625 tokens** (cut, searchable) | 158 077 tokens | 76 654 tokens |
|
|
@@ -94,7 +96,7 @@ tools are off by default and listed only with `--tool-groups`:
|
|
|
94
96
|
| `network` | `mock_route`, `list_routes`, `remove_route`, `set_offline` | changes what the page receives |
|
|
95
97
|
|
|
96
98
|
`--tool-groups all` lists every group. The page, input, observation and
|
|
97
|
-
debugger tools are always listed (
|
|
99
|
+
debugger tools are always listed (34 tools), plus `enable_proxy_auth` with
|
|
98
100
|
`--proxy-server` and the WebMCP tools with `--enable-webmcp`. A tool of a
|
|
99
101
|
group that is off fails naming the flag. The list is fixed for the server's
|
|
100
102
|
life: it never changes during a session, so clients can cache it.
|
|
@@ -116,20 +118,21 @@ see the note.
|
|
|
116
118
|
| `close_tab` | ✓ | ✓ | |
|
|
117
119
|
| `list_tabs` | ✓ | ✓ | |
|
|
118
120
|
| `switch_tab` | ✓ | ✓ | |
|
|
119
|
-
| `navigate` | ✓ | ✓ | |
|
|
121
|
+
| `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 |
|
|
120
122
|
| `reload` | ✓ | ✓ | |
|
|
121
123
|
| `scroll` | ✓ | ✓ | reports the window positions before and after |
|
|
122
124
|
| `emulate` | ✓ | partial | `emulation` group; `width`/`height`, `device_scale_factor`, `mobile` (touch), `color_scheme`, `reduced_motion`, `forced_colors`, `media` (`print`), `reset`; answers what the page now sees; Firefox: viewport and device pixel ratio only (BiDi has no media or touch emulation yet; those options fail naming them) |
|
|
123
125
|
| `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 |
|
|
124
126
|
| `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) |
|
|
125
|
-
| `snapshot` | ✓ | ✓ | outline of the page: role, name, states, value and a ref (`e12`) per element, through open shadow roots and same-origin iframes; 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; at most 50 000 characters (`selector` and `depth` narrow it); the same JavaScript on both browsers |
|
|
127
|
+
| `snapshot` | ✓ | ✓ | outline of the page: role, name, states, value and a ref (`e12`) per element, through open shadow roots and same-origin iframes; 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); an element that is only clickable (a handler or a pointer cursor) is one named line, 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 |
|
|
126
128
|
| `find_in_snapshot` | ✓ | ✓ | `text` (case-insensitive) or `regex` over the whole outline; matching lines under their ancestors, at most 20 |
|
|
127
129
|
| `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` |
|
|
128
|
-
| `capture_screenshot` | ✓ | ✓ | `format` `png`, `jpeg` (or `jpg`) or `webp`; `full_page` at most 16 384 px per side (a text note says when cut) |
|
|
129
|
-
| `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 or covered; `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}`) |
|
|
130
|
-
| `fill_input` | ✓ | ✓ | picks `<select>` options; fails without typing on elements that cannot take the text; 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 |
|
|
130
|
+
| `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 |
|
|
131
|
+
| `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 or 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); `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}`) |
|
|
132
|
+
| `fill_input` | ✓ | ✓ | picks `<select>` options; fails without typing on elements that cannot take the text; `append: true` types after the content instead of replacing it (a number, email or date 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 |
|
|
131
133
|
| `press_key` | ✓ | ✓ | one key (a character, Enter, Tab, Escape, arrows, F1–F12...) with Control / Shift / Alt / Meta held, in `selector` (focused first) or the focused element; a shortcut types nothing |
|
|
132
134
|
| `hover` | ✓ | ✓ | moves the mouse over the element (scrolled into view; fails when hidden or covered) |
|
|
135
|
+
| `drag` | ✓ | ✓ | presses on `from` (checked like `click_element`), moves in steps to the centre of `to` (which must then be in the viewport) and releases: sortable lists, boards, sliders; HTML5 drag and drop is real on Chrome and simulated with script events on Firefox (its WebDriver input starts a native drag but never drops it), and the reply says so |
|
|
133
136
|
| `fill_form` | ✓ | ✓ | several fields in one call: text and `<select>` as `fill_input`, a checkbox or radio takes `true`/`false` and is clicked only when its state differs; stops at the first failing field naming what was done; never echoes values |
|
|
134
137
|
| `upload_file` | ✓ | ✓ | `files` group; sets the files of an `<input type=file>` (Chrome `DOM.setFileInputFiles`, Firefox `input.setFiles`); files must be under the upload roots (`--upload-root`, default the working and temp directories) |
|
|
135
138
|
| `handle_dialog` | ✓ | ✓ | accepts or dismisses the open `alert` / `confirm` / `prompt` / `beforeunload` dialog |
|
|
@@ -161,8 +164,8 @@ see the note.
|
|
|
161
164
|
| `webmcp_get_invocation` | ✓ | ✗ | needs `--enable-webmcp` |
|
|
162
165
|
| `webmcp_list_invocations` | ✓ | ✗ | needs `--enable-webmcp` |
|
|
163
166
|
|
|
164
|
-
After `click_element`, `hover`, `fill_input`, `fill_form` and
|
|
165
|
-
the server waits for the page to settle before answering: until its DOM
|
|
167
|
+
After `click_element`, `hover`, `drag`, `fill_input`, `fill_form` and
|
|
168
|
+
`press_key` the server waits for the page to settle before answering: until its DOM
|
|
166
169
|
stays unchanged for 50 ms (style changes, often animations, aside) and the
|
|
167
170
|
requests the action started finished, at most 1 s, on both browsers. An
|
|
168
171
|
action nothing reacts to costs those 50 ms and keeps its reply; otherwise
|
|
@@ -509,7 +512,7 @@ Chrome and Firefox take the same calls:
|
|
|
509
512
|
with that location.
|
|
510
513
|
5. `evaluate_on_call_frame` reads locals of the paused frame; `step_over`
|
|
511
514
|
moves one statement; `resume` continues. While paused, the input tools
|
|
512
|
-
(`click_element`, `hover`, `fill_input`, `fill_form`, `press_key`) fail
|
|
515
|
+
(`click_element`, `hover`, `drag`, `fill_input`, `fill_form`, `press_key`) fail
|
|
513
516
|
at once naming the pause and do nothing: the page would handle their
|
|
514
517
|
events only after `resume`. `evaluate_js` still answers
|
|
515
518
|
while paused, in the page's global scope. A promise it returns cannot
|
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.7.0-beta.11"
|
|
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.7.0-beta.11"
|
|
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.7.0-beta.11"
|
|
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.7.0-beta.11",
|
|
96
96
|
"volta": {
|
|
97
97
|
"node": "18.14.1",
|
|
98
98
|
"npm": "9.5.0"
|