chrome-bridge-mcp 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/extension/manifest.json +2 -2
- package/extension/service-worker.js +71 -3
- package/package.json +2 -2
- package/server/protocol.js +3 -1
- package/server/tools.js +417 -194
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Chrome Bridge
|
|
2
2
|
|
|
3
|
-
       [](https://chromewebstore.google.com/detail/chrome-bridge-for-claude/bioknpaeahidbelaljjohjofiloeodmb)
|
|
4
4
|
|
|
5
5
|
**Chrome Bridge is an MCP server that connects Claude Code to your real, logged-in Chrome browser — measured 2.75× fewer turns and 2.28× lower cost than the official "Claude in Chrome" extension on a form-filling task, with ~3× the toolset and no paid plan.**
|
|
6
6
|
|
|
7
|
-
By using a local WebSocket bridge and a specialized Chrome extension, Chrome Bridge provides
|
|
7
|
+
By using a local WebSocket bridge and a specialized Chrome extension, Chrome Bridge provides 60 web-development tools (navigation, DOM inspection, visual regression, audits, network mocking) and a dedicated headless instance for CI. It is self-hosted, local-only, and requires no paid plan.
|
|
8
8
|
|
|
9
9
|
## Why Chrome Bridge?
|
|
10
10
|
|
|
@@ -31,12 +31,12 @@ unfavourable ones) and the harness limits are in
|
|
|
31
31
|
- **Honest caveat:** per *turn*, Chrome Bridge costs slightly more than Claude in Chrome (41,985 vs 36,613 cache-read tokens; $0.0373 vs $0.0337). The win is in the number of turns.
|
|
32
32
|
|
|
33
33
|
### 2. Feature Comparison
|
|
34
|
-
Chrome Bridge ships
|
|
34
|
+
Chrome Bridge ships 60 specialized tools (31 core by default) compared to ~20 in Claude in Chrome. It is also the only automation that drives the real host Chrome on ChromeOS/Crostini.
|
|
35
35
|
|
|
36
36
|
| | Chrome Bridge | Claude in Chrome | Chrome DevTools MCP | Playwright MCP |
|
|
37
37
|
|---|---|---|---|---|
|
|
38
38
|
| **ChromeOS / Crostini** | **Yes** (real host) | No | Container only | Container only |
|
|
39
|
-
| **Tools** | **
|
|
39
|
+
| **Tools** | **60** (31 core) | ~20 | ~50 | 23 core (71 total) |
|
|
40
40
|
| **Requires Paid Plan** | **No** | Yes (Pro+) | No | No |
|
|
41
41
|
| **Network Mocking** | **Yes** (stub/headers) | No | No | Yes |
|
|
42
42
|
| **Visual Regression** | **Yes** (`screenshot_diff`) | No | No | No |
|
|
@@ -63,7 +63,7 @@ Claude Code calls `navigate` (returning clickable element refs), `accessibility_
|
|
|
63
63
|
|
|
64
64
|
## Token-Efficient Design
|
|
65
65
|
|
|
66
|
-
- **
|
|
66
|
+
- **Opt-in Surface**: 31 core tools cost ≈7.4k tokens of `tools/list`, all 60 cost ≈14.1k; specialized groups (audits, visual, network, storage, dom, files) are opt-in via `--caps`. That core figure was ≈3.8k in 1.8.0 and roughly doubled in 1.10.0: MCP annotations on every tool, rewritten descriptions, and a documented `.describe()` on all 254 parameters (coverage went from 35% to 100%). The trade is deliberate and it is not free — on the form benchmark it adds ≈8% to the cache-read tokens per turn — but the measured advantage was never prefix size, it was round trips: 2.75× fewer turns. Playwright MCP's 23 core tools measured ≈4.6k in July 2026, so on prefix size alone it is the leaner one. Measure ours with `npm run measure`.
|
|
67
67
|
- **Act-from-Result**: Tools attach a capped preview of interactive elements with short refs. Actions only report a `page_changed` delta when the URL or title actually changes.
|
|
68
68
|
- **Optimized Media**: Screenshots are downscaled to ≤1568px. Full-page captures are sliced into readable segments.
|
|
69
69
|
- **Zero-Token Escape Hatches**: The [CLI](#cli) skips MCP schemas entirely, and recorded flows [replay](#launch-mode-headless--ci) without any model in the loop.
|
|
@@ -84,7 +84,7 @@ Claude Code <--stdio--> MCP Server <--WebSocket :8765--> Chrome Extension
|
|
|
84
84
|
|
|
85
85
|
The **MCP Server** (Node.js) handles the protocol and tool logic. The **Chrome Extension** (MV3) executes commands via Chrome APIs. User scripts (`execute_js`) run via `chrome.userScripts.execute()`, requiring the "Allow user scripts" toggle in extension settings.
|
|
86
86
|
|
|
87
|
-
## Tools (
|
|
87
|
+
## Tools (60 total)
|
|
88
88
|
|
|
89
89
|
### Core & Navigation (7)
|
|
90
90
|
`get_status`, `get_tabs`, `create_tab`, `navigate`, `tab_action`, `get_frames`, `screenshot`.
|
|
@@ -95,8 +95,8 @@ The **MCP Server** (Node.js) handles the protocol and tool logic. The **Chrome E
|
|
|
95
95
|
### DOM & Inspection (11)
|
|
96
96
|
`read_page`, `extract`, `get_page_info`, `query_dom`, `modify_dom`, `find_text`, `get_interactives`, `inject_css`, `highlight_elements`, `watch_dom`, `measure_spacing`.
|
|
97
97
|
|
|
98
|
-
### Debugging & Network (
|
|
99
|
-
`execute_js`, `read_console`, `monitor_network`, `monitor_websocket`, `network_rules` (block/redirect/stub/headers), `get_performance`, `web_vitals`, `list_event_listeners`.
|
|
98
|
+
### Debugging & Network (9)
|
|
99
|
+
`execute_js`, `read_console`, `monitor_network`, `monitor_websocket`, `network_rules` (block/redirect/stub/headers), `http_request` (sent with the user's session cookies), `get_performance`, `web_vitals`, `list_event_listeners`.
|
|
100
100
|
|
|
101
101
|
### Visual & Responsive (7)
|
|
102
102
|
`element_screenshot`, `full_page_screenshot`, `screenshot_diff`, `viewport_resize`, `set_zoom`, `emulate_media`, `set_geolocation`.
|
|
@@ -132,7 +132,7 @@ Configure the server via environment variables (or the matching CLI flags):
|
|
|
132
132
|
- `CHROME_BRIDGE_PORT`: Default `8765`.
|
|
133
133
|
- `CHROME_BRIDGE_HOST` / `--host`: Bind address, default `127.0.0.1`. Set to `0.0.0.0` **only** where the browser lives outside the container (ChromeOS/Crostini port-forward) — and pair it with a token.
|
|
134
134
|
- `CHROME_BRIDGE_TOKEN`: Shared secret required on both `ext_init` and `relay_init`. Strongly recommended whenever the bind is not loopback.
|
|
135
|
-
- `CHROME_BRIDGE_CAPS` / `--caps`: Tool groups to load (`core`, `audits`, `visual`, `network`, `storage`, `dom`, `files`, or `all`). `install.sh` registers with `all`; the bare server defaults to `core` (
|
|
135
|
+
- `CHROME_BRIDGE_CAPS` / `--caps`: Tool groups to load (`core`, `audits`, `visual`, `network`, `storage`, `dom`, `files`, or `all`). `install.sh` registers with `all`; the bare server defaults to `core` (31 tools). `get_status` reports `caps_active` and `caps_available`.
|
|
136
136
|
|
|
137
137
|
Run `node tools/measure-schema.mjs` to see the exact schema cost of the active set.
|
|
138
138
|
|
package/extension/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Chrome Bridge for Claude Code",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "Bridge your browser to Claude Code:
|
|
4
|
+
"version": "1.10.0",
|
|
5
|
+
"description": "Bridge your browser to Claude Code: 60 token-efficient web-dev automation tools over a local WebSocket. ChromeOS included.",
|
|
6
6
|
"homepage_url": "https://github.com/frsorrentino/chrome-bridge",
|
|
7
7
|
"minimum_chrome_version": "135",
|
|
8
8
|
"permissions": [
|
|
@@ -423,6 +423,8 @@ async function executeCommand(msg) {
|
|
|
423
423
|
return await cmdManageDownloads(params);
|
|
424
424
|
case 'save_page':
|
|
425
425
|
return await cmdSavePage(params);
|
|
426
|
+
case 'http_request':
|
|
427
|
+
return await cmdHttpRequest(params);
|
|
426
428
|
case 'set_zoom':
|
|
427
429
|
return await cmdSetZoom(params);
|
|
428
430
|
case 'http_auth':
|
|
@@ -2177,12 +2179,12 @@ async function cmdWatchDom({ selector = 'body', attributes = true, childList = t
|
|
|
2177
2179
|
|
|
2178
2180
|
// --- emulate_media ---
|
|
2179
2181
|
|
|
2180
|
-
async function cmdEmulateMedia({ colorScheme, reducedMotion, printMode = false, reset = false, tab_id }) {
|
|
2182
|
+
async function cmdEmulateMedia({ colorScheme, reducedMotion, printMode = false, user_agent = null, reset = false, tab_id }) {
|
|
2181
2183
|
const tabId = await resolveTabId(tab_id);
|
|
2182
2184
|
|
|
2183
2185
|
const results = await chrome.scripting.executeScript({
|
|
2184
2186
|
target: { tabId },
|
|
2185
|
-
func: (cs, rm, print, doReset) => {
|
|
2187
|
+
func: (cs, rm, print, ua, doReset) => {
|
|
2186
2188
|
const STYLE_ID = '__chromeBridge_media';
|
|
2187
2189
|
const META_ID = '__chromeBridge_colorScheme';
|
|
2188
2190
|
|
|
@@ -2196,6 +2198,15 @@ async function cmdEmulateMedia({ colorScheme, reducedMotion, printMode = false,
|
|
|
2196
2198
|
window.matchMedia = window.__chromeBridge_origMatchMedia;
|
|
2197
2199
|
delete window.__chromeBridge_origMatchMedia;
|
|
2198
2200
|
}
|
|
2201
|
+
// navigator.userAgent è stato ridefinito con un getter: per tornare
|
|
2202
|
+
// all'originale va rimossa la proprietà own, così riemerge quella del
|
|
2203
|
+
// prototipo.
|
|
2204
|
+
if (window.__chromeBridge_uaPatched) {
|
|
2205
|
+
for (const prop of ['userAgent', 'appVersion', 'platform']) {
|
|
2206
|
+
try { delete navigator[prop]; } catch { /* proprietà non configurabile: si prosegue */ }
|
|
2207
|
+
}
|
|
2208
|
+
delete window.__chromeBridge_uaPatched;
|
|
2209
|
+
}
|
|
2199
2210
|
return { reset: true };
|
|
2200
2211
|
}
|
|
2201
2212
|
|
|
@@ -2261,9 +2272,26 @@ async function cmdEmulateMedia({ colorScheme, reducedMotion, printMode = false,
|
|
|
2261
2272
|
meta.remove();
|
|
2262
2273
|
}
|
|
2263
2274
|
|
|
2275
|
+
// navigator.userAgent non è scrivibile: va ridefinita come getter sull'
|
|
2276
|
+
// istanza, che precede quella del prototipo. Vale per il JS della pagina,
|
|
2277
|
+
// non per l'header della richiesta (quello è network_rules).
|
|
2278
|
+
if (ua) {
|
|
2279
|
+
for (const [prop, value] of [
|
|
2280
|
+
['userAgent', ua],
|
|
2281
|
+
['appVersion', ua.replace(/^Mozilla\//, '')],
|
|
2282
|
+
['platform', /iPhone|iPad|Android/i.test(ua) ? 'iPhone' : navigator.platform],
|
|
2283
|
+
]) {
|
|
2284
|
+
try {
|
|
2285
|
+
Object.defineProperty(navigator, prop, { get: () => value, configurable: true });
|
|
2286
|
+
} catch { /* alcune pagine congelano navigator: si prosegue */ }
|
|
2287
|
+
}
|
|
2288
|
+
window.__chromeBridge_uaPatched = true;
|
|
2289
|
+
overrides.userAgent = ua;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2264
2292
|
return { emulated: overrides };
|
|
2265
2293
|
},
|
|
2266
|
-
args: [colorScheme || null, reducedMotion || null, printMode, reset],
|
|
2294
|
+
args: [colorScheme || null, reducedMotion || null, printMode, user_agent, reset],
|
|
2267
2295
|
world: 'MAIN',
|
|
2268
2296
|
});
|
|
2269
2297
|
return results?.[0]?.result ?? {};
|
|
@@ -3440,6 +3468,46 @@ async function cmdManageDownloads({ action, timeout = 30000, limit = 10 }) {
|
|
|
3440
3468
|
|
|
3441
3469
|
// --- save_page (MHTML via pageCapture) ---
|
|
3442
3470
|
|
|
3471
|
+
// --- http_request ---
|
|
3472
|
+
|
|
3473
|
+
/**
|
|
3474
|
+
* La fetch parte dal service worker, non dal server Node: con host permission
|
|
3475
|
+
* `<all_urls>` e `credentials: 'include'` Chrome allega i cookie di sessione
|
|
3476
|
+
* dell'utente. È l'unica differenza che conta — la stessa URL richiesta da Node
|
|
3477
|
+
* restituisce la pagina di login.
|
|
3478
|
+
*/
|
|
3479
|
+
async function cmdHttpRequest({ url, method = 'GET', headers, body, binary = false }) {
|
|
3480
|
+
if (!/^https?:\/\//i.test(String(url || ''))) {
|
|
3481
|
+
throw new Error(`http_request needs an absolute http(s) URL, got "${url}"`);
|
|
3482
|
+
}
|
|
3483
|
+
const res = await fetch(url, {
|
|
3484
|
+
method,
|
|
3485
|
+
headers: headers && Object.keys(headers).length ? headers : undefined,
|
|
3486
|
+
body: body ?? undefined,
|
|
3487
|
+
credentials: 'include',
|
|
3488
|
+
redirect: 'follow',
|
|
3489
|
+
});
|
|
3490
|
+
|
|
3491
|
+
const outHeaders = {};
|
|
3492
|
+
for (const [k, v] of res.headers.entries()) outHeaders[k] = v;
|
|
3493
|
+
const common = {
|
|
3494
|
+
status: res.status,
|
|
3495
|
+
ok: res.ok,
|
|
3496
|
+
// res.url riflette i redirect seguiti: senza, un 302 verso il login
|
|
3497
|
+
// sembrerebbe una risposta della URL chiesta.
|
|
3498
|
+
url: res.url,
|
|
3499
|
+
content_type: res.headers.get('content-type') || '',
|
|
3500
|
+
headers: outHeaders,
|
|
3501
|
+
};
|
|
3502
|
+
|
|
3503
|
+
if (binary) {
|
|
3504
|
+
const blob = await res.blob();
|
|
3505
|
+
return { ...common, body_b64: await blobToBase64(blob), size: blob.size };
|
|
3506
|
+
}
|
|
3507
|
+
const text = await res.text();
|
|
3508
|
+
return { ...common, body: text, size: text.length };
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3443
3511
|
async function cmdSavePage({ tab_id }) {
|
|
3444
3512
|
const tabId = await resolveTabId(tab_id);
|
|
3445
3513
|
const blob = await new Promise((resolve, reject) => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
3
|
"mcpName": "io.github.frsorrentino/chrome-bridge",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.10.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "MCP server with
|
|
6
|
+
"description": "MCP server with 60 tools for browser automation via Chrome extension. Works on ChromeOS/Crostini.",
|
|
7
7
|
"main": "server/index.js",
|
|
8
8
|
"bin": {
|
|
9
9
|
"chrome-bridge-mcp": "server/index.js",
|
package/server/protocol.js
CHANGED
|
@@ -58,6 +58,7 @@ export const MessageType = Object.freeze({
|
|
|
58
58
|
SET_GEOLOCATION: 'set_geolocation',
|
|
59
59
|
MANAGE_DOWNLOADS: 'manage_downloads',
|
|
60
60
|
SAVE_PAGE: 'save_page',
|
|
61
|
+
HTTP_REQUEST: 'http_request',
|
|
61
62
|
SET_ZOOM: 'set_zoom',
|
|
62
63
|
HTTP_AUTH: 'http_auth',
|
|
63
64
|
GET_RESPONSE_HEADERS: 'get_response_headers',
|
|
@@ -80,7 +81,7 @@ export const MessageType = Object.freeze({
|
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
// Versione
|
|
83
|
-
export const VERSION = '1.
|
|
84
|
+
export const VERSION = '1.10.0';
|
|
84
85
|
|
|
85
86
|
// Configurazione
|
|
86
87
|
export const DEFAULT_PORT = 8765;
|
|
@@ -129,6 +130,7 @@ export function getTimeout(type) {
|
|
|
129
130
|
|| type === MessageType.UPLOAD_FILE
|
|
130
131
|
|| type === MessageType.MANAGE_DOWNLOADS
|
|
131
132
|
|| type === MessageType.SAVE_PAGE
|
|
133
|
+
|| type === MessageType.HTTP_REQUEST
|
|
132
134
|
|| type === MessageType.WAIT_FOR_FUNCTION
|
|
133
135
|
|| type === MessageType.SCROLL_UNTIL) return 60000;
|
|
134
136
|
return COMMAND_TIMEOUT_MS;
|