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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Chrome Bridge for Claude Code",
4
- "version": "1.29.0",
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
- const el = document.querySelector(sel);
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 = document.querySelector(sel);
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 = document.querySelector(selector);
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 = document.querySelector(submitSel);
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 ? document.querySelector(sel) : document;
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 = !!document.querySelector(selector);
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 = document.querySelector(valueOf); out.value = el ? (el.innerText || el.value || '').trim().slice(0, 500) : null; }
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
- const el = document.querySelector(sel);
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
- const el1 = document.querySelector(sel1);
3230
- const el2 = document.querySelector(sel2);
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
- if (!document.querySelector(sel)) return { missing: true };
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 = document.querySelector(sel);
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
- const el = document.querySelector(sel);
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
- const el = document.querySelector(sel);
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 = document.querySelector(containerSel);
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 = document.querySelector(sel);
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.2",
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",
@@ -99,7 +99,7 @@ export const MessageType = Object.freeze({
99
99
  });
100
100
 
101
101
  // Versione
102
- export const VERSION = '1.29.2';
102
+ export const VERSION = '1.29.3';
103
103
 
104
104
  // Configurazione
105
105
  export const DEFAULT_PORT = 8765;