chrome-bridge-mcp 1.29.2 → 1.29.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/extension/manifest.json +1 -1
- package/extension/service-worker.js +159 -16
- package/package.json +1 -1
- package/server/protocol.js +1 -1
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Chrome Bridge for Claude Code",
|
|
4
|
-
"version": "1.29.
|
|
4
|
+
"version": "1.29.3",
|
|
5
5
|
"description": "Bridge your browser to Claude Code: 66 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",
|
|
@@ -1845,7 +1845,20 @@ async function cmdModifyDom({ selector, action, name, value, className, tab_id,
|
|
|
1845
1845
|
const results = await chrome.scripting.executeScript({
|
|
1846
1846
|
target: scriptTarget(tabId, frame_id),
|
|
1847
1847
|
func: (sel, act, attrName, attrValue, cls) => {
|
|
1848
|
-
|
|
1848
|
+
function deepQuery(sel) {
|
|
1849
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
1850
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
1851
|
+
let ctx = document;
|
|
1852
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1853
|
+
const found = ctx.querySelector(parts[i]);
|
|
1854
|
+
if (!found) return null;
|
|
1855
|
+
if (i === parts.length - 1) return found;
|
|
1856
|
+
if (!found.shadowRoot) return null;
|
|
1857
|
+
ctx = found.shadowRoot;
|
|
1858
|
+
}
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
const el = deepQuery(sel);
|
|
1849
1862
|
if (!el) throw new Error(`Element not found: ${sel}`);
|
|
1850
1863
|
switch (act) {
|
|
1851
1864
|
case 'setAttribute':
|
|
@@ -2080,8 +2093,21 @@ async function cmdScrollTo({ selector, x, y, behavior = 'auto', offset_y = 0, ta
|
|
|
2080
2093
|
const results = await chrome.scripting.executeScript({
|
|
2081
2094
|
target: scriptTarget(tabId, frame_id),
|
|
2082
2095
|
func: (sel, sx, sy, beh, offY) => {
|
|
2096
|
+
function deepQuery(sel) {
|
|
2097
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
2098
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
2099
|
+
let ctx = document;
|
|
2100
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2101
|
+
const found = ctx.querySelector(parts[i]);
|
|
2102
|
+
if (!found) return null;
|
|
2103
|
+
if (i === parts.length - 1) return found;
|
|
2104
|
+
if (!found.shadowRoot) return null;
|
|
2105
|
+
ctx = found.shadowRoot;
|
|
2106
|
+
}
|
|
2107
|
+
return null;
|
|
2108
|
+
}
|
|
2083
2109
|
if (sel) {
|
|
2084
|
-
const el =
|
|
2110
|
+
const el = deepQuery(sel);
|
|
2085
2111
|
if (!el) throw new Error(`Element not found: ${sel}`);
|
|
2086
2112
|
const rect = el.getBoundingClientRect();
|
|
2087
2113
|
const targetY = rect.top + window.scrollY - offY;
|
|
@@ -2180,10 +2206,23 @@ async function cmdFillForm({ fields, submit_selector, tab_id, frame_id }) {
|
|
|
2180
2206
|
const results = await chrome.scripting.executeScript({
|
|
2181
2207
|
target: scriptTarget(tabId, frame_id),
|
|
2182
2208
|
func: (flds, submitSel) => {
|
|
2209
|
+
function deepQuery(sel) {
|
|
2210
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
2211
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
2212
|
+
let ctx = document;
|
|
2213
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2214
|
+
const found = ctx.querySelector(parts[i]);
|
|
2215
|
+
if (!found) return null;
|
|
2216
|
+
if (i === parts.length - 1) return found;
|
|
2217
|
+
if (!found.shadowRoot) return null;
|
|
2218
|
+
ctx = found.shadowRoot;
|
|
2219
|
+
}
|
|
2220
|
+
return null;
|
|
2221
|
+
}
|
|
2183
2222
|
const report = [];
|
|
2184
2223
|
for (const { selector, value } of flds) {
|
|
2185
2224
|
try {
|
|
2186
|
-
const el =
|
|
2225
|
+
const el = deepQuery(selector);
|
|
2187
2226
|
if (!el) { report.push({ selector, success: false, error: 'Element not found' }); continue; }
|
|
2188
2227
|
const tag = el.tagName.toLowerCase();
|
|
2189
2228
|
const type = (el.type || '').toLowerCase();
|
|
@@ -2251,7 +2290,7 @@ async function cmdFillForm({ fields, submit_selector, tab_id, frame_id }) {
|
|
|
2251
2290
|
let submit = null;
|
|
2252
2291
|
if (submitSel) {
|
|
2253
2292
|
const failed = report.filter((r) => !r.success).length;
|
|
2254
|
-
const btn =
|
|
2293
|
+
const btn = deepQuery(submitSel);
|
|
2255
2294
|
if (failed) submit = { submitted: false, reason: `${failed} field(s) failed: not submitted` };
|
|
2256
2295
|
else if (!btn) submit = { submitted: false, reason: `submit button not found: ${submitSel}` };
|
|
2257
2296
|
else { btn.click(); submit = { submitted: true }; }
|
|
@@ -2551,8 +2590,21 @@ async function cmdReadForm({ selector, tab_id }) {
|
|
|
2551
2590
|
const results = await chrome.scripting.executeScript({
|
|
2552
2591
|
target: { tabId },
|
|
2553
2592
|
func: (sel) => {
|
|
2593
|
+
function deepQuery(sel) {
|
|
2594
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
2595
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
2596
|
+
let ctx = document;
|
|
2597
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2598
|
+
const found = ctx.querySelector(parts[i]);
|
|
2599
|
+
if (!found) return null;
|
|
2600
|
+
if (i === parts.length - 1) return found;
|
|
2601
|
+
if (!found.shadowRoot) return null;
|
|
2602
|
+
ctx = found.shadowRoot;
|
|
2603
|
+
}
|
|
2604
|
+
return null;
|
|
2605
|
+
}
|
|
2554
2606
|
const SENSITIVE = /pass|pwd|card|cvv|cvc|secret|token|otp|pin\b/i;
|
|
2555
|
-
const root = sel ?
|
|
2607
|
+
const root = sel ? deepQuery(sel) : document;
|
|
2556
2608
|
if (!root) return { error: `No element matches ${sel}` };
|
|
2557
2609
|
const labelOf = (el) => {
|
|
2558
2610
|
const byFor = el.id && document.querySelector(`label[for="${CSS.escape(el.id)}"]`);
|
|
@@ -2616,10 +2668,23 @@ async function evalWatch(w) {
|
|
|
2616
2668
|
const results = await chrome.scripting.executeScript({
|
|
2617
2669
|
target: { tabId: w.tabId },
|
|
2618
2670
|
func: (selector, text, valueOf) => {
|
|
2671
|
+
function deepQuery(sel) {
|
|
2672
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
2673
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
2674
|
+
let ctx = document;
|
|
2675
|
+
for (let i = 0; i < parts.length; i++) {
|
|
2676
|
+
const found = ctx.querySelector(parts[i]);
|
|
2677
|
+
if (!found) return null;
|
|
2678
|
+
if (i === parts.length - 1) return found;
|
|
2679
|
+
if (!found.shadowRoot) return null;
|
|
2680
|
+
ctx = found.shadowRoot;
|
|
2681
|
+
}
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2619
2684
|
const out = { url: location.href };
|
|
2620
|
-
if (selector) out.present = !!
|
|
2685
|
+
if (selector) out.present = !!deepQuery(selector);
|
|
2621
2686
|
if (text) out.text_found = (document.body?.innerText || '').toLowerCase().includes(String(text).toLowerCase());
|
|
2622
|
-
if (valueOf) { const el =
|
|
2687
|
+
if (valueOf) { const el = deepQuery(valueOf); out.value = el ? (el.innerText || el.value || '').trim().slice(0, 500) : null; }
|
|
2623
2688
|
return out;
|
|
2624
2689
|
},
|
|
2625
2690
|
args: [w.selector ?? null, w.text ?? null, w.value_of ?? null],
|
|
@@ -2951,7 +3016,20 @@ async function cmdElementScreenshot({ selector, region, scale = 1, tab_id }) {
|
|
|
2951
3016
|
const res = await chrome.scripting.executeScript({
|
|
2952
3017
|
target: { tabId },
|
|
2953
3018
|
func: (sel) => {
|
|
2954
|
-
|
|
3019
|
+
function deepQuery(sel) {
|
|
3020
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
3021
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
3022
|
+
let ctx = document;
|
|
3023
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3024
|
+
const found = ctx.querySelector(parts[i]);
|
|
3025
|
+
if (!found) return null;
|
|
3026
|
+
if (i === parts.length - 1) return found;
|
|
3027
|
+
if (!found.shadowRoot) return null;
|
|
3028
|
+
ctx = found.shadowRoot;
|
|
3029
|
+
}
|
|
3030
|
+
return null;
|
|
3031
|
+
}
|
|
3032
|
+
const el = deepQuery(sel);
|
|
2955
3033
|
if (!el) throw new Error(`Element not found: ${sel}`);
|
|
2956
3034
|
el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'center' });
|
|
2957
3035
|
const r = el.getBoundingClientRect();
|
|
@@ -3226,8 +3304,21 @@ async function cmdMeasureSpacing({ selector1, selector2, tab_id }) {
|
|
|
3226
3304
|
const results = await chrome.scripting.executeScript({
|
|
3227
3305
|
target: { tabId },
|
|
3228
3306
|
func: (sel1, sel2) => {
|
|
3229
|
-
|
|
3230
|
-
|
|
3307
|
+
function deepQuery(sel) {
|
|
3308
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
3309
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
3310
|
+
let ctx = document;
|
|
3311
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3312
|
+
const found = ctx.querySelector(parts[i]);
|
|
3313
|
+
if (!found) return null;
|
|
3314
|
+
if (i === parts.length - 1) return found;
|
|
3315
|
+
if (!found.shadowRoot) return null;
|
|
3316
|
+
ctx = found.shadowRoot;
|
|
3317
|
+
}
|
|
3318
|
+
return null;
|
|
3319
|
+
}
|
|
3320
|
+
const el1 = deepQuery(sel1);
|
|
3321
|
+
const el2 = deepQuery(sel2);
|
|
3231
3322
|
if (!el1) throw new Error(`Element not found: ${sel1}`);
|
|
3232
3323
|
if (!el2) throw new Error(`Element not found: ${sel2}`);
|
|
3233
3324
|
|
|
@@ -3301,7 +3392,20 @@ async function cmdWatchDom({ selector = 'body', attributes = true, childList = t
|
|
|
3301
3392
|
const install = await chrome.scripting.executeScript({
|
|
3302
3393
|
target: { tabId },
|
|
3303
3394
|
func: (sel, opts) => {
|
|
3304
|
-
|
|
3395
|
+
function deepQuery(sel) {
|
|
3396
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
3397
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
3398
|
+
let ctx = document;
|
|
3399
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3400
|
+
const found = ctx.querySelector(parts[i]);
|
|
3401
|
+
if (!found) return null;
|
|
3402
|
+
if (i === parts.length - 1) return found;
|
|
3403
|
+
if (!found.shadowRoot) return null;
|
|
3404
|
+
ctx = found.shadowRoot;
|
|
3405
|
+
}
|
|
3406
|
+
return null;
|
|
3407
|
+
}
|
|
3408
|
+
if (!deepQuery(sel)) return { missing: true };
|
|
3305
3409
|
if (window.__chromeBridge_domWatcherHooked && window.__chromeBridge_domWatchSelector !== sel) {
|
|
3306
3410
|
if (window.__chromeBridge_domObserver) window.__chromeBridge_domObserver.disconnect();
|
|
3307
3411
|
window.__chromeBridge_domWatcherHooked = false;
|
|
@@ -3312,7 +3416,7 @@ async function cmdWatchDom({ selector = 'body', attributes = true, childList = t
|
|
|
3312
3416
|
window.__chromeBridge_domWatchSelector = sel;
|
|
3313
3417
|
window.__chromeBridge_domMutations = [];
|
|
3314
3418
|
const MAX = 1000;
|
|
3315
|
-
const target =
|
|
3419
|
+
const target = deepQuery(sel);
|
|
3316
3420
|
const observer = new MutationObserver((mutationList) => {
|
|
3317
3421
|
for (const m of mutationList) {
|
|
3318
3422
|
// Ring buffer: dopo un'azione interessano le mutation RECENTI, quindi
|
|
@@ -3684,7 +3788,20 @@ async function cmdUploadFile({ selector, name, mime_type, content_b64, upload_id
|
|
|
3684
3788
|
func: (sel, fname, mime, b64, id, expected) => {
|
|
3685
3789
|
const pieces = id ? window.__cbUploads?.[id] : null;
|
|
3686
3790
|
if (id) delete window.__cbUploads?.[id];
|
|
3687
|
-
|
|
3791
|
+
function deepQuery(sel) {
|
|
3792
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
3793
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
3794
|
+
let ctx = document;
|
|
3795
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3796
|
+
const found = ctx.querySelector(parts[i]);
|
|
3797
|
+
if (!found) return null;
|
|
3798
|
+
if (i === parts.length - 1) return found;
|
|
3799
|
+
if (!found.shadowRoot) return null;
|
|
3800
|
+
ctx = found.shadowRoot;
|
|
3801
|
+
}
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
const el = deepQuery(sel);
|
|
3688
3805
|
if (!el) throw new Error(`Element not found: ${sel}`);
|
|
3689
3806
|
// Un throw qui si perderebbe (elementOutcome lo leggerebbe come «not found»): esito esplicito.
|
|
3690
3807
|
if (!(el instanceof HTMLInputElement) || el.type !== 'file') return { uploaded: false, error: 'Element is not an input[type=file]' };
|
|
@@ -3995,7 +4112,20 @@ async function captureForDiff(tabId, selector) {
|
|
|
3995
4112
|
const res = await chrome.scripting.executeScript({
|
|
3996
4113
|
target: { tabId },
|
|
3997
4114
|
func: (sel) => {
|
|
3998
|
-
|
|
4115
|
+
function deepQuery(sel) {
|
|
4116
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
4117
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
4118
|
+
let ctx = document;
|
|
4119
|
+
for (let i = 0; i < parts.length; i++) {
|
|
4120
|
+
const found = ctx.querySelector(parts[i]);
|
|
4121
|
+
if (!found) return null;
|
|
4122
|
+
if (i === parts.length - 1) return found;
|
|
4123
|
+
if (!found.shadowRoot) return null;
|
|
4124
|
+
ctx = found.shadowRoot;
|
|
4125
|
+
}
|
|
4126
|
+
return null;
|
|
4127
|
+
}
|
|
4128
|
+
const el = deepQuery(sel);
|
|
3999
4129
|
if (!el) throw new Error(`Element not found: ${sel}`);
|
|
4000
4130
|
el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
|
|
4001
4131
|
const r = el.getBoundingClientRect();
|
|
@@ -5220,6 +5350,19 @@ async function cmdScrollUntil({ until = 'no_new_content', selector, container, m
|
|
|
5220
5350
|
const run = () => chrome.scripting.executeScript({
|
|
5221
5351
|
target: { tabId },
|
|
5222
5352
|
func: (mode, sel, containerSel, maxScrolls, stepPx, settleMs) => new Promise((resolve) => {
|
|
5353
|
+
function deepQuery(sel) {
|
|
5354
|
+
if (!sel.includes('>>>')) return document.querySelector(sel);
|
|
5355
|
+
const parts = sel.split('>>>').map((s) => s.trim());
|
|
5356
|
+
let ctx = document;
|
|
5357
|
+
for (let i = 0; i < parts.length; i++) {
|
|
5358
|
+
const found = ctx.querySelector(parts[i]);
|
|
5359
|
+
if (!found) return null;
|
|
5360
|
+
if (i === parts.length - 1) return found;
|
|
5361
|
+
if (!found.shadowRoot) return null;
|
|
5362
|
+
ctx = found.shadowRoot;
|
|
5363
|
+
}
|
|
5364
|
+
return null;
|
|
5365
|
+
}
|
|
5223
5366
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5224
5367
|
const docEl = document.scrollingElement || document.documentElement;
|
|
5225
5368
|
const scrollable = (el) => {
|
|
@@ -5238,7 +5381,7 @@ async function cmdScrollUntil({ until = 'no_new_content', selector, container, m
|
|
|
5238
5381
|
// scorrevole più grande, ma solo se il documento non scorre.
|
|
5239
5382
|
let box = null;
|
|
5240
5383
|
if (containerSel) {
|
|
5241
|
-
box =
|
|
5384
|
+
box = deepQuery(containerSel);
|
|
5242
5385
|
if (!box) { resolve({ stopped_reason: 'error', error: `Container not found: ${containerSel}`, scrolls: 0 }); return; }
|
|
5243
5386
|
} else if (docEl.scrollHeight <= innerHeight + 2
|
|
5244
5387
|
// overflow hidden su html o body: l'utente non può scorrere il
|
|
@@ -5264,7 +5407,7 @@ async function cmdScrollUntil({ until = 'no_new_content', selector, container, m
|
|
|
5264
5407
|
const step = stepPx || s.view();
|
|
5265
5408
|
for (scrolls = 0; scrolls < maxScrolls; scrolls++) {
|
|
5266
5409
|
if (mode === 'element' && sel) {
|
|
5267
|
-
const el =
|
|
5410
|
+
const el = deepQuery(sel);
|
|
5268
5411
|
if (el) {
|
|
5269
5412
|
const r = el.getBoundingClientRect();
|
|
5270
5413
|
const vis = r.width > 0 && r.height > 0 && getComputedStyle(el).visibility !== 'hidden';
|
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.29.
|
|
4
|
+
"version": "1.29.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). 66 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",
|