chrome-bridge-mcp 1.31.2 → 1.31.3
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/README.md +138 -89
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server/protocol.js +1 -1
- package/server/tools.js +47 -20
package/README.md
CHANGED
|
@@ -4,120 +4,158 @@
|
|
|
4
4
|
|
|
5
5
|
    [](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb) [](https://mcprush.com/to/chrome-bridge-mcp?ref=badge)
|
|
6
6
|
|
|
7
|
-
**Claude Code
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
**Claude Code drives the Chrome you are already using — signed in, with your
|
|
8
|
+
extensions and your cookies. 69 tools: navigation, forms, DOM inspection,
|
|
9
|
+
network mocking, visual regression, audits, performance traces and the
|
|
10
|
+
WebMCP tools a page declares. Self-hosted, local-only, no paid plan, works on
|
|
11
|
+
ChromeOS.**
|
|
12
|
+
|
|
13
|
+
Measured against the official Claude in Chrome extension on the same tasks
|
|
14
|
+
(paired runs, 17/10/2026, Claude Code 2.1.302, n=5 per task, medians):
|
|
15
|
+
|
|
16
|
+
| Task | Chrome Bridge | Claude in Chrome | Fewer turns | Lower cost |
|
|
17
|
+
|---|---|---|---:|---:|
|
|
18
|
+
| Fill a form | 3 turns · $0.043 | 15 turns · $0.236 | **5.0×** | **5.5×** |
|
|
19
|
+
| Find one row in a 1,500-row table | 3 turns · $0.039 | 6 turns · $0.154 | **2.0×** | **3.9×** |
|
|
20
|
+
| Debug a broken page | 6 turns · $0.091 | 31 turns · $0.559 | **5.2×** | **6.1×** |
|
|
21
|
+
|
|
22
|
+
15 correct answers out of 15, against 14. Method, every run and the caveats in
|
|
23
|
+
[docs/EFFICIENCY.md](docs/EFFICIENCY.md).
|
|
22
24
|
|
|
23
25
|
## Quickstart
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
1. In a Claude Code session:
|
|
28
|
+
```
|
|
29
|
+
/plugin install chrome-bridge --marketplace frsorrentino/chrome-bridge
|
|
30
|
+
```
|
|
31
|
+
From a shell, the same: `claude plugin install chrome-bridge --marketplace frsorrentino/chrome-bridge`.
|
|
32
|
+
2. Add the extension from the [Chrome Web
|
|
33
|
+
Store](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb).
|
|
34
|
+
3. Restart Claude Code.
|
|
35
|
+
|
|
36
|
+
Then ask for something like *"open localhost:3000, run an accessibility audit
|
|
37
|
+
and find the Sign Up button"*: Claude Code calls `navigate`, `audit` and
|
|
38
|
+
`find_text`. Because `navigate` already returns element refs,
|
|
39
|
+
`click(ref="n1")` follows with no discovery turn in between.
|
|
40
|
+
|
|
41
|
+
For `execute_js`, enable **Allow user scripts** in `chrome://extensions` →
|
|
42
|
+
Chrome Bridge → Details.
|
|
43
|
+
|
|
44
|
+
The plugin registers the MCP server from npm together with the recipes skill.
|
|
45
|
+
Requires Node.js 18+ and Chrome 135+.
|
|
46
|
+
|
|
47
|
+
<details>
|
|
48
|
+
<summary>Older Claude Code, from source, Windows, Chrome 135-137, one path only</summary>
|
|
49
|
+
|
|
50
|
+
**Older Claude Code.** The one-command install needs Claude Code 2.1.275+ in a
|
|
51
|
+
session and 2.1.292+ from a shell. Before that, add the marketplace first:
|
|
52
|
+
`/plugin marketplace add frsorrentino/chrome-bridge`, then
|
|
53
|
+
`/plugin install chrome-bridge@chrome-bridge` (from a shell:
|
|
54
|
+
`claude plugin marketplace add frsorrentino/chrome-bridge`, then
|
|
55
|
+
`claude plugin install chrome-bridge@chrome-bridge`).
|
|
56
|
+
|
|
57
|
+
**From source:**
|
|
26
58
|
|
|
27
59
|
```bash
|
|
28
60
|
git clone https://github.com/frsorrentino/chrome-bridge.git
|
|
29
61
|
cd chrome-bridge && ./install.sh
|
|
30
62
|
```
|
|
31
63
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Then ask for something like *"open localhost:3000, run an accessibility audit
|
|
37
|
-
and find the Sign Up button"*: Claude Code calls `navigate`,
|
|
38
|
-
`audit` and `find_text`. Because `navigate` already returns
|
|
39
|
-
element refs, `click(ref="n1")` follows with no discovery turn in between.
|
|
40
|
-
|
|
41
|
-
> **On ChromeOS/Crostini** install from the [Chrome Web
|
|
42
|
-
> Store](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb)
|
|
43
|
-
> instead: an unpacked extension is dropped on every reboot, because the
|
|
44
|
-
> container isn't mounted when Chrome starts.
|
|
45
|
-
|
|
46
|
-
> **On Windows** `install.sh` does not run from PowerShell or cmd. Use the
|
|
47
|
-
> plugin path below (`claude plugin marketplace add frsorrentino/chrome-bridge`,
|
|
48
|
-
> then `claude plugin install chrome-bridge@chrome-bridge`) with the extension
|
|
49
|
-
> from the Web Store, or run `install.sh` from Git Bash. The plugin's error
|
|
50
|
-
> log hooks run in Git Bash, as Claude Code runs every hook, and need Python
|
|
51
|
-
> 3.8+ as `python3`, `python` or `py` (the Microsoft Store `python3` alias is
|
|
52
|
-
> skipped); without Python they stay silent and the tools work the same. For
|
|
53
|
-
> launch mode see `CHROME_BRIDGE_BROWSER` below: Google Chrome cannot load the
|
|
54
|
-
> extension there, Microsoft Edge can.
|
|
55
|
-
|
|
56
|
-
`install.sh` registers the MCP server with `--scope user`. To do it by hand:
|
|
64
|
+
Then open `chrome://extensions`, enable **Developer mode**, click **Load
|
|
65
|
+
unpacked**, select the `extension/` folder, and restart Claude Code.
|
|
66
|
+
`install.sh` registers the MCP server with `--scope user`; by hand:
|
|
57
67
|
`claude mcp add --scope user chrome-bridge node /path/to/server/index.js`.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
claude.ai
|
|
76
|
-
|
|
68
|
+
On ChromeOS/Crostini use the Web Store extension instead: an unpacked one is
|
|
69
|
+
dropped on every reboot, because the container isn't mounted when Chrome
|
|
70
|
+
starts.
|
|
71
|
+
|
|
72
|
+
**Windows.** `install.sh` does not run from PowerShell or cmd: use the plugin
|
|
73
|
+
with the Web Store extension, or run `install.sh` from Git Bash. The plugin's
|
|
74
|
+
error log hooks run in Git Bash, as Claude Code runs every hook, and need
|
|
75
|
+
Python 3.8+ as `python3`, `python` or `py` (the Microsoft Store `python3` alias
|
|
76
|
+
is skipped); without Python they stay silent and the tools work the same. For
|
|
77
|
+
launch mode see `CHROME_BRIDGE_BROWSER` below: Google Chrome cannot load the
|
|
78
|
+
extension there, Microsoft Edge can.
|
|
79
|
+
|
|
80
|
+
**Chrome 135-137.** `execute_js` needs Developer Mode instead of the **Allow
|
|
81
|
+
user scripts** toggle.
|
|
82
|
+
|
|
83
|
+
**One path only.** The plugin and `install.sh` would register the same server
|
|
84
|
+
twice, and so would a plugin added from the [Anthropic
|
|
85
|
+
directory](https://claude.ai/directory) on claude.ai (it reaches Claude Code as
|
|
86
|
+
`chrome-bridge@synced`) next to a marketplace install. In claude.ai chat the
|
|
87
|
+
plugin loads only the recipes skill: the tools need Claude Code, or Cowork
|
|
88
|
+
running on your computer, where the local MCP server can start. Clients that
|
|
89
|
+
read [Agent Plugins 1.0](https://agent-plugins.org) get the same from
|
|
90
|
+
`plugin.json` + `mcp.json`.
|
|
91
|
+
|
|
92
|
+
</details>
|
|
93
|
+
|
|
94
|
+

|
|
95
|
+
|
|
96
|
+
## Why it takes fewer turns
|
|
97
|
+
|
|
98
|
+
- **Refs, not screenshots.** `navigate` returns the page's interactive
|
|
99
|
+
elements as short refs (`n1`, `n2`…), so the next call is the click, not a
|
|
100
|
+
screenshot and a coordinate guess.
|
|
101
|
+
- **One call for a whole form.** `fill_form` fills N fields and submits in the
|
|
102
|
+
same call.
|
|
103
|
+
- **Filtering on the server.** `extract_table` with `where` and `columns`
|
|
104
|
+
returns the matching rows of a 1,500-row table, not the table.
|
|
105
|
+
- **Debugging starts with the answer.** `navigate` reports the first console
|
|
106
|
+
error mapped to its source line and the failed requests with their status;
|
|
107
|
+
`get_css_styles` names the rule behind a style and flags undefined CSS
|
|
108
|
+
variables.
|
|
109
|
+
- **Deferred loading.** 22 stay in context; the rest of the 46 in the
|
|
110
|
+
default set wait behind Claude Code's tool search (2.1.285+), and loading
|
|
111
|
+
one mid-session does not invalidate the prompt cache. The other 23, in seven
|
|
112
|
+
optional groups, switch on mid-session with `get_status({enable: [...]})`,
|
|
113
|
+
no restart.
|
|
77
114
|
|
|
78
115
|
## Your browser, your login
|
|
79
116
|
|
|
80
117
|
No blank profile, no debugging port, no cloud browser. The extension runs in
|
|
81
118
|
your Chrome, so every site you are signed into is already open to the agent:
|
|
82
|
-
webmail, the hosting panel, a
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
webmail, the hosting panel, a back office, a staging site behind basic auth.
|
|
120
|
+
Tabs the agent opens are marked as its own and closed when the session ends;
|
|
121
|
+
your tabs stay yours. Several Claude Code sessions share the same browser: the
|
|
122
|
+
second one becomes a relay on the first one's bridge.
|
|
123
|
+
|
|
124
|
+
**WebMCP.** When a page declares its own tools through
|
|
125
|
+
[WebMCP](https://github.com/webmachinelearning/webmcp), `navigate` and
|
|
126
|
+
`get_interactives` say so in one line, `webmcp_tools` lists them and
|
|
127
|
+
`webmcp_call` runs one, with a timeout. Needs the WebMCP flag or origin trial
|
|
128
|
+
in Chrome; `get_status` reports whether it is on.
|
|
85
129
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
or let you pick one or more elements on the page.
|
|
130
|
+
**Limits you can set.** `upload_file` refuses keys and credentials
|
|
131
|
+
(`~/.ssh`, `.env*`, `*.pem`, …) and `--read-root` fences it to one directory;
|
|
132
|
+
`--write-root` fences every path the model writes; `--no-js` removes arbitrary
|
|
133
|
+
JavaScript. The agent never types credentials: for a login, a 2FA code or a
|
|
134
|
+
choice, `handoff` shows a banner in the page and waits for you.
|
|
92
135
|
|
|
93
|
-
##
|
|
136
|
+
## Compared
|
|
94
137
|
|
|
95
138
|
| | Chrome Bridge | Claude in Chrome | Chrome DevTools MCP | Playwright MCP |
|
|
96
139
|
|---|---|---|---|---|
|
|
97
140
|
| **ChromeOS / Crostini** | **Yes** (real host) | No | Container only | Container only |
|
|
98
|
-
| **Tools** | **
|
|
141
|
+
| **Tools** | **69** (46 default, 22 always loaded) | 22 | 29 default (56 with flags) | 24 core (71 total) |
|
|
99
142
|
| **Requires paid plan** | **No** | Yes (Pro+) | No | No |
|
|
143
|
+
| **WebMCP (tools the page declares)** | **Yes** | No | Yes | Yes |
|
|
100
144
|
| **Network mocking** | **Yes** (stub/headers) | No | No | Yes |
|
|
101
145
|
| **Visual regression** | **Yes** (`screenshot_diff`) | No | No | No |
|
|
102
146
|
| **Audits (a11y/SEO/sec)** | **Yes** (one call, report on disk) | No | Lighthouse | No |
|
|
147
|
+
| **Performance trace** | **Yes** (`perf_trace`, `lighthouse`) | No | Yes | Playwright trace only |
|
|
148
|
+
| **Screen recording** | **Yes** (`screencast`, mp4/webm) | Yes (GIF) | Partial | Yes (video) |
|
|
149
|
+
| **Heap snapshot** | **Yes** (launch mode) | No | Yes | No |
|
|
150
|
+
| **Breakpoints** | No | No | **Yes** | No |
|
|
103
151
|
| **Headless / CI** | **Yes** | No | Yes | Yes |
|
|
104
|
-
| **GIF / video** | No | **Yes** | Partial | No |
|
|
105
|
-
| **Breakpoints / heap** | No | No | **Yes** | No |
|
|
106
152
|
|
|
107
153
|
Codex for Chrome (OpenAI, May 2026) sits in the Claude in Chrome column: an
|
|
108
154
|
official extension with the `debugger` permission, macOS and Windows only,
|
|
109
155
|
the ChatGPT app required. Claude in Chrome documents Linux desktop since
|
|
110
|
-
September 2026; ChromeOS and WSL stay out. Competitor
|
|
111
|
-
2026-09-01 and 2026-09-11 (`docs/analisi-2026-09-11-concorrenti.md`)
|
|
112
|
-
|
|
113
|
-
It wins on **round trips** — short element refs instead of the
|
|
114
|
-
screenshot-and-click loop, `fill_form` filling N fields in one call — and, on
|
|
115
|
-
big pages, on **payload**: table filtering is done server-side. On the form, per single turn it actually costs
|
|
116
|
-
slightly *more*.
|
|
117
|
-
|
|
118
|
-
The full benchmark — method, every raw run including the unfavourable ones, and
|
|
119
|
-
what the harness can't measure — is in
|
|
120
|
-
[docs/EFFICIENCY.md](docs/EFFICIENCY.md).
|
|
156
|
+
September 2026; ChromeOS and WSL stay out. Competitor tool counts measured on
|
|
157
|
+
2026-09-01 and 2026-09-11 (`docs/analisi-2026-09-11-concorrenti.md`); WebMCP, recording and
|
|
158
|
+
tracing rows checked in their documentation on 2026-10-28.
|
|
121
159
|
|
|
122
160
|

|
|
123
161
|
|
|
@@ -165,11 +203,12 @@ user-script toggle isn't available.
|
|
|
165
203
|
## Tools
|
|
166
204
|
|
|
167
205
|
69 in total. `core` (46 tools: every tool used in 101 real sessions, plus the
|
|
168
|
-
two WebMCP tools) loads by default
|
|
169
|
-
|
|
170
|
-
(`--caps` still sets them
|
|
206
|
+
two WebMCP tools) loads by default, 22 of them always in context; the other 23
|
|
207
|
+
sit in seven optional caps that the agent switches on mid-session with
|
|
208
|
+
`get_status({enable: ["visual"]})`, with no restart (`--caps` still sets them
|
|
209
|
+
at startup).
|
|
171
210
|
|
|
172
|
-

|
|
173
212
|
|
|
174
213
|
| Group | N | What's in it |
|
|
175
214
|
|---|---|---|
|
|
@@ -177,6 +216,7 @@ mid-session with `get_status({enable: ["visual"]})`, with no restart
|
|
|
177
216
|
| Interaction | 11 | `click`, `fill_form`, `upload_file`, dialogs, clipboard |
|
|
178
217
|
| DOM & Inspection | 12 | `read_page`, `extract`, `extract_table`, `query_dom`, `get_css_styles`, `watch_dom` |
|
|
179
218
|
| Debugging & Network | 8 | `execute_js`, console, network log, mocking, `track_events` |
|
|
219
|
+
| WebMCP | 2 | `webmcp_tools`, `webmcp_call`: the tools a page declares |
|
|
180
220
|
| Visual & Responsive | 5 | `screenshot_diff`, viewport and zoom, media emulation |
|
|
181
221
|
| Audits | 2 | `audit` (a11y, keyboard, SEO, security, links, vitals, css, resources, cache in one call), `cookie_audit` |
|
|
182
222
|
| State, Storage & Files | 10 | storage, fixtures, MHTML, `capture_downloads` (exports saved with no "Save as" dialog), recording, `assert` |
|
|
@@ -199,7 +239,8 @@ executes commands through Chrome APIs. User scripts (`execute_js`) run via
|
|
|
199
239
|
|
|
200
240
|
## Configuration and security
|
|
201
241
|
|
|
202
|
-
|
|
242
|
+
<details>
|
|
243
|
+
<summary>Environment variables, each with a matching CLI flag</summary>
|
|
203
244
|
|
|
204
245
|
| Variable | Default | Notes |
|
|
205
246
|
|---|---|---|
|
|
@@ -214,6 +255,8 @@ Environment variables, each with a matching CLI flag:
|
|
|
214
255
|
| `CHROME_BRIDGE_OBSERVE` | unset | `off` stops the local error log below. The plugin sets `hook`: its hooks keep the log and the server stays out of it |
|
|
215
256
|
| `CHROME_BRIDGE_WRITE_ROOT` / `--write-root` | unset | Every path the model chooses (`save_to`, `output_path`, exports) must be under this directory, checked before the browser does any work; the server's own state under `~/.config/chrome-bridge` stays writable. The CLI is your shell and is not restricted |
|
|
216
257
|
|
|
258
|
+
</details>
|
|
259
|
+
|
|
217
260
|
The bridge binds loopback, accepts extension connections only from a
|
|
218
261
|
`chrome-extension://` origin, and — when a token is set — requires it on both
|
|
219
262
|
handshakes. Without one, any local process could act as a relay and reach
|
|
@@ -296,6 +339,9 @@ count.
|
|
|
296
339
|
|
|
297
340
|
## Troubleshooting
|
|
298
341
|
|
|
342
|
+
<details>
|
|
343
|
+
<summary>Symptoms and fixes</summary>
|
|
344
|
+
|
|
299
345
|
| Symptom | Cause / fix |
|
|
300
346
|
|---|---|
|
|
301
347
|
| `Chrome extension not connected` | Extension disabled, or its port differs from the server's. The error names the actual host/port; check them in the popup (⚙). |
|
|
@@ -314,11 +360,14 @@ count.
|
|
|
314
360
|
| `claude mcp get` shows no command, arguments or environment | Expected with the plugin: since Claude Code 2.1.285 it hides them for stdio servers that plugins provide (variable names stay). `get_status` reports the server and extension versions, mode, host, port and caps; `/mcp` shows the connection. |
|
|
315
361
|
| Popup says «In attesa: un altro browser è collegato», `get_status` lists `refused_browsers` | Two browsers with the extension reach the same port. The one already connected keeps the bridge; the other retries every 15 s and takes over when the first disconnects. `get_status` → `browser` names the connected one. Before this, the last one to connect took over silently. |
|
|
316
362
|
|
|
363
|
+
</details>
|
|
364
|
+
|
|
317
365
|
## Documentation
|
|
318
366
|
|
|
319
367
|
- [docs/TOOLS.md](docs/TOOLS.md) — all 69 tools, by group
|
|
320
368
|
- [docs/CAPABILITIES.md](docs/CAPABILITIES.md) — what the bridge gets past and what it does not, one dated state per wall
|
|
321
369
|
- [docs/EFFICIENCY.md](docs/EFFICIENCY.md) — the benchmark and the design behind it
|
|
370
|
+
- [docs/misure/2026-10-token-cache-vs-claude-in-chrome.md](docs/misure/2026-10-token-cache-vs-claude-in-chrome.md) — the 17/10 paired set and the prompt-cache breakdown (Italian)
|
|
322
371
|
- [docs/PERFORMANCE.md](docs/PERFORMANCE.md) — latency per tool on the real path, `npm run bench:latency`
|
|
323
372
|
- [bench/RESULTS.md](bench/RESULTS.md) — raw runs and inclusion rule
|
|
324
373
|
- [CHANGELOG.md](CHANGELOG.md)
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
|
-
"version": "1.31.
|
|
3
|
+
"version": "1.31.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "chrome-bridge-mcp",
|
|
9
|
-
"version": "1.31.
|
|
9
|
+
"version": "1.31.3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@jridgewell/trace-mapping": "^0.3.31",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
3
|
"mcpName": "io.github.frsorrentino/chrome-bridge",
|
|
4
|
-
"version": "1.31.
|
|
4
|
+
"version": "1.31.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Chrome Bridge by frsorrentino: MCP server that lets Claude Code borrow a tab from your own logged-in Chrome and hand it back — logins, 2FA and CAPTCHAs stay with you (handoff). 69 tools; 2.8-4.3× cheaper and up to 9.3× fewer turns than Claude in Chrome, measured; ChromeOS included.",
|
|
7
7
|
"main": "server/index.js",
|
package/server/protocol.js
CHANGED
package/server/tools.js
CHANGED
|
@@ -242,14 +242,20 @@ const MIME_BY_EXT = {
|
|
|
242
242
|
* @returns {Promise<object|null>} risultato dell'attesa, o null se none
|
|
243
243
|
*/
|
|
244
244
|
async function applyWaitAfter(send, wait_after, tab_id, since_url) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
245
|
+
try {
|
|
246
|
+
if (wait_after === 'navigation') {
|
|
247
|
+
// since_url: l'URL prima dell'azione. Una navigazione veloce può essere già
|
|
248
|
+
// finita quando l'attesa parte: senza, l'estensione aspettava 5 s un
|
|
249
|
+
// 'loading' che non arrivava più e rispondeva «No navigation started».
|
|
250
|
+
return await send(MessageType.WAIT_FOR_NAVIGATION, { timeout: 15000, tab_id, ...(since_url && { since_url }) });
|
|
251
|
+
}
|
|
252
|
+
if (wait_after === 'networkidle') {
|
|
253
|
+
return await send(MessageType.WAIT_FOR_NETWORK_IDLE, { idle_ms: 500, timeout: 15000, tab_id });
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
// L'azione è già avvenuta: un errore qui faceva leggere al modello un click
|
|
257
|
+
// fallito, e lo ripeteva (nota di claude-observe del 21/10).
|
|
258
|
+
return { done: false, error: err?.message ?? String(err), note: 'the action itself ran; only the wait failed: do not repeat it, check the page instead' };
|
|
253
259
|
}
|
|
254
260
|
return null;
|
|
255
261
|
}
|
|
@@ -302,9 +308,23 @@ const DEFER_META = { 'anthropic/alwaysLoad': false };
|
|
|
302
308
|
const saveToField = (what) => z.string().optional()
|
|
303
309
|
.describe(`Absolute path: write ${what} there and return the path instead of the content`);
|
|
304
310
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
// save_to scritto così com'è dava ENOENT con una cartella da creare e EISDIR
|
|
312
|
+
// con una cartella esistente, a lavoro già fatto (note di claude-observe,
|
|
313
|
+
// ottobre). Le cartelle mancanti si creano; una cartella riceve un file con un
|
|
314
|
+
// nome generato. Torna il percorso davvero scritto.
|
|
315
|
+
async function writeSaved(path, bytes, ext = '') {
|
|
316
|
+
let target = path;
|
|
317
|
+
if (/[\\/]$/.test(String(path)) || (await stat(path).catch(() => null))?.isDirectory()) {
|
|
318
|
+
target = join(path, `${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}${ext}`);
|
|
319
|
+
}
|
|
320
|
+
await mkdir(dirname(target), { recursive: true });
|
|
321
|
+
await writeFile(target, bytes);
|
|
322
|
+
return target;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function savedSummary(path, bytes, extra = {}, ext = '') {
|
|
326
|
+
const saved = await writeSaved(path, bytes, ext);
|
|
327
|
+
return { content: [{ type: 'text', text: jsonText({ saved, bytes: bytes.length, ...extra }) }] };
|
|
308
328
|
}
|
|
309
329
|
|
|
310
330
|
const selectorField = (extra = '') => z.string()
|
|
@@ -597,7 +617,15 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
597
617
|
// fallisce fino al prossimo navigate. Un solo ritentativo sulla tab
|
|
598
618
|
// attiva salva un turno intero al modello.
|
|
599
619
|
const gone = /No tab with id|No active tab|No tab found/i.test(err?.message ?? '');
|
|
600
|
-
if (!gone
|
|
620
|
+
if (!gone) throw err;
|
|
621
|
+
// tab_id esplicito di una scheda chiusa: il messaggio nudo faceva
|
|
622
|
+
// ritentare lo stesso id (note di claude-observe, ottobre).
|
|
623
|
+
if (!implicitTab) {
|
|
624
|
+
if (/No tab with id/i.test(err.message) && !/get_tabs/.test(err.message)) {
|
|
625
|
+
err.message = `${err.message.replace(/\.$/, '')} — the tab was closed or replaced: get_tabs lists the open ones and why this one ended; omit tab_id for the active tab`;
|
|
626
|
+
}
|
|
627
|
+
throw err;
|
|
628
|
+
}
|
|
601
629
|
sessionTabId = null;
|
|
602
630
|
const { tab_id: _drop, ...retry } = params;
|
|
603
631
|
const data = await wsManager.sendCommand(type, retry);
|
|
@@ -1061,7 +1089,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
1061
1089
|
}
|
|
1062
1090
|
const data = await send(MessageType.SCREENSHOT, { tab_id });
|
|
1063
1091
|
const b64 = data?.image ?? data?.data;
|
|
1064
|
-
if (save_to && b64) return savedSummary(save_to, Buffer.from(b64, 'base64'), { mimeType: 'image/png' });
|
|
1092
|
+
if (save_to && b64) return savedSummary(save_to, Buffer.from(b64, 'base64'), { mimeType: 'image/png' }, '.png');
|
|
1065
1093
|
// data.image è base64 PNG. data.viewport (CSS px) è il sistema di
|
|
1066
1094
|
// riferimento di element_screenshot.region: senza, il modello non sa
|
|
1067
1095
|
// mappare ciò che vede sull'immagine ridotta a ≤1568px.
|
|
@@ -1111,7 +1139,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
1111
1139
|
if (save_to) {
|
|
1112
1140
|
const value = data?.result;
|
|
1113
1141
|
const isString = typeof value === 'string';
|
|
1114
|
-
return savedSummary(save_to, Buffer.from(isString ? value : JSON.stringify(value ?? null), 'utf8'), { format: isString ? 'text' : 'json' });
|
|
1142
|
+
return savedSummary(save_to, Buffer.from(isString ? value : JSON.stringify(value ?? null), 'utf8'), { format: isString ? 'text' : 'json' }, isString ? '.txt' : '.json');
|
|
1115
1143
|
}
|
|
1116
1144
|
return { content: [{ type: 'text', text: jsResultText(data, max_length ?? DEFAULT_MAX_OUTPUT) }] };
|
|
1117
1145
|
}
|
|
@@ -1205,7 +1233,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
1205
1233
|
guardWrite(save_to);
|
|
1206
1234
|
const data = await send(MessageType.READ_PAGE, { mode, tab_id, frame_id });
|
|
1207
1235
|
const text = typeof data === 'string' ? data : JSON.stringify(data);
|
|
1208
|
-
if (save_to) return savedSummary(save_to, Buffer.from(text, 'utf8'), { mode: mode ?? 'text' });
|
|
1236
|
+
if (save_to) return savedSummary(save_to, Buffer.from(text, 'utf8'), { mode: mode ?? 'text' }, mode === 'html' ? '.html' : mode === 'markdown' ? '.md' : '.txt');
|
|
1209
1237
|
return {
|
|
1210
1238
|
content: [{
|
|
1211
1239
|
type: 'text',
|
|
@@ -2733,8 +2761,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
2733
2761
|
const summary = summarizeAudit(results);
|
|
2734
2762
|
let text = `audit ${info?.url ?? ''}\n${summary.lines.join('\n')}`;
|
|
2735
2763
|
if (save_to) {
|
|
2736
|
-
await
|
|
2737
|
-
text += `\nreport: ${save_to}`;
|
|
2764
|
+
text += `\nreport: ${await writeSaved(save_to, auditReport({ url: info?.url, title: info?.title, results, summary }), '.md')}`;
|
|
2738
2765
|
} else {
|
|
2739
2766
|
text += '\n(pass save_to for the full report with every finding)';
|
|
2740
2767
|
}
|
|
@@ -2993,12 +3020,12 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
2993
3020
|
if (save_to) {
|
|
2994
3021
|
// Il base64 non deve mai raggiungere il modello: un PDF di 300 kB sono
|
|
2995
3022
|
// ~100k token di rumore per un contenuto che va letto da file.
|
|
2996
|
-
await
|
|
3023
|
+
const saved = await writeSaved(save_to, Buffer.from(data.body_b64 ?? '', 'base64'), extname(new URL(data.url ?? url).pathname) || '.bin');
|
|
2997
3024
|
return {
|
|
2998
3025
|
content: [{
|
|
2999
3026
|
type: 'text',
|
|
3000
3027
|
text: jsonText({
|
|
3001
|
-
saved
|
|
3028
|
+
saved,
|
|
3002
3029
|
status: data.status,
|
|
3003
3030
|
ok: data.ok,
|
|
3004
3031
|
content_type: data.content_type,
|
|
@@ -3322,7 +3349,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
|
|
|
3322
3349
|
}
|
|
3323
3350
|
return row;
|
|
3324
3351
|
});
|
|
3325
|
-
if (save_to) return savedSummary(save_to, Buffer.from(JSON.stringify({ total: nodes.length, shown: items.length, items }), 'utf8'), { total: nodes.length, shown: items.length });
|
|
3352
|
+
if (save_to) return savedSummary(save_to, Buffer.from(JSON.stringify({ total: nodes.length, shown: items.length, items }), 'utf8'), { total: nodes.length, shown: items.length }, '.json');
|
|
3326
3353
|
if ((format ?? 'lines') === 'json') {
|
|
3327
3354
|
return { content: [{ type: 'text', text: truncateText(JSON.stringify({ total: nodes.length, shown: items.length, items }), max_length, 'max_items or max_length') }] };
|
|
3328
3355
|
}
|