@agent360/browser-mcp 1.20.1 → 1.21.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.
@@ -237,27 +237,39 @@ const RETRYABLE_CDP_METHODS = new Set([
237
237
  // For side-effectful methods, only re-attaches and throws — caller must decide.
238
238
  async function cdpSend(tabId, method, params = {}) {
239
239
  await debuggerAttach(tabId);
240
- try {
241
- return await chrome.debugger.sendCommand({ tabId }, method, params);
242
- } catch (e) {
243
- const msg = e?.message || String(e);
244
- const isDetachError =
245
- msg.includes('not attached') ||
246
- msg.includes('Detached') ||
247
- msg.includes('detached') ||
248
- msg.includes('Debugger is gone') ||
249
- msg.includes('No tab with given id');
250
- if (!isDetachError) throw e;
251
- debuggerAttached.delete(tabId);
252
- if (!RETRYABLE_CDP_METHODS.has(method)) {
253
- // Re-attach for next caller, but don't auto-retry side-effectful command
254
- try { await debuggerAttach(tabId); } catch {}
255
- throw new Error(`Debugger detached during ${method} — not auto-retried (side-effect risk). Original: ${msg}`);
240
+ let lastMsg = '';
241
+ // 4 total attempts (initial + 3 retries) for read-only methods; backoff 100/300/500ms.
242
+ // Handles aggressive auto-detach on anti-automation sites (Apple ASC, Salesforce, etc.)
243
+ // where Chrome re-detaches between attach and command execution.
244
+ for (let attempt = 0; attempt < 4; attempt++) {
245
+ try {
246
+ return await chrome.debugger.sendCommand({ tabId }, method, params);
247
+ } catch (e) {
248
+ const msg = e?.message || String(e);
249
+ const isDetachError =
250
+ msg.includes('not attached') ||
251
+ msg.includes('Detached') ||
252
+ msg.includes('detached') ||
253
+ msg.includes('Debugger is gone') ||
254
+ msg.includes('No tab with given id');
255
+ if (!isDetachError) throw e;
256
+ lastMsg = msg;
257
+ debuggerAttached.delete(tabId);
258
+ if (!RETRYABLE_CDP_METHODS.has(method)) {
259
+ // Side-effectful methods (Input.*) — re-attach for next caller but signal
260
+ // to handler so it can fall back to chrome.scripting (e.g., synthetic click).
261
+ try { await debuggerAttach(tabId); } catch {}
262
+ throw new Error(`Debugger detached during ${method} — not auto-retried (side-effect risk). Original: ${msg}`);
263
+ }
264
+ if (attempt < 3) {
265
+ await new Promise(r => setTimeout(r, 100 + attempt * 200));
266
+ try { await debuggerAttach(tabId); } catch (attachErr) {
267
+ throw new Error(`Re-attach failed during ${method}: ${attachErr.message}`);
268
+ }
269
+ }
256
270
  }
257
- await new Promise(r => setTimeout(r, 100));
258
- await debuggerAttach(tabId);
259
- return await chrome.debugger.sendCommand({ tabId }, method, params);
260
271
  }
272
+ throw new Error(`Debugger detached repeatedly during ${method} (4 attempts). Last: ${lastMsg}`);
261
273
  }
262
274
 
263
275
  // Clean up debugger + session refs when tabs close
@@ -437,6 +449,46 @@ async function debuggerEval(tabId, expression) {
437
449
  }
438
450
  }
439
451
 
452
+ // Synthetic click via chrome.scripting — fallback when debugger detaches on
453
+ // anti-automation sites (Apple ASC, etc.). Loses isTrusted=true but works for
454
+ // the ~95% of sites that don't check it. Handles text= and :text() selectors.
455
+ async function scriptingClick(tabId, selector) {
456
+ try {
457
+ const [result] = await chrome.scripting.executeScript({
458
+ target: { tabId },
459
+ world: 'MAIN',
460
+ func: (sel) => {
461
+ let el;
462
+ if (sel.startsWith('text=')) {
463
+ const text = sel.slice(5).trim();
464
+ el = Array.from(document.querySelectorAll('button, a, [role="button"], [role="menuitem"], [role="tab"], [role="option"], input, label, span, div, p, li, td'))
465
+ .find(e => (e.textContent || '').trim() === text);
466
+ } else {
467
+ const m = sel.match(/^([\w-]+):text\(([^)]+)\)$/);
468
+ if (m) {
469
+ const needle = m[2].trim();
470
+ el = Array.from(document.querySelectorAll(m[1]))
471
+ .find(e => (e.textContent || '').trim().includes(needle));
472
+ } else {
473
+ el = document.querySelector(sel);
474
+ }
475
+ }
476
+ if (!el) return { ok: false, reason: 'not_found' };
477
+ el.scrollIntoView({ block: 'center', behavior: 'instant' });
478
+ const opts = { bubbles: true, cancelable: true, view: window };
479
+ el.dispatchEvent(new MouseEvent('mousedown', opts));
480
+ el.dispatchEvent(new MouseEvent('mouseup', opts));
481
+ el.click();
482
+ return { ok: true, tag: el.tagName };
483
+ },
484
+ args: [selector],
485
+ });
486
+ return result?.result || { ok: false, reason: 'no_result' };
487
+ } catch (e) {
488
+ return { ok: false, reason: 'exception', error: e.message };
489
+ }
490
+ }
491
+
440
492
  // Try executeScript first, fall back to debugger on CSP error
441
493
  async function safeExecuteScript(tabId, func, args = [], world = 'MAIN') {
442
494
  try {
@@ -1611,9 +1663,19 @@ async function dispatch(port, method, params) {
1611
1663
  const el = await resolveElement(tab.id, params.selector);
1612
1664
  if (!el) return { ok: false, error: 'Element not found: ' + params.selector };
1613
1665
 
1614
- // Always use debugger mouse events works on all sites including SPAs
1615
- await debuggerClick(tab.id, el.x, el.y);
1616
- return { ok: true, method: el.method || 'debugger', tag: el.tag, text: el.text };
1666
+ // Primary path: debugger mouse events (isTrusted=true, works on React/Angular SPAs)
1667
+ try {
1668
+ await debuggerClick(tab.id, el.x, el.y);
1669
+ return { ok: true, method: el.method || 'debugger', tag: el.tag, text: el.text };
1670
+ } catch (e) {
1671
+ // Fallback: synthetic click via chrome.scripting for anti-automation sites
1672
+ // (Apple ASC etc.) where Chrome auto-detaches debugger on every interaction.
1673
+ if (/Debugger detached/.test(e?.message || '')) {
1674
+ const r = await scriptingClick(tab.id, params.selector);
1675
+ if (r.ok) return { ok: true, method: 'scripting-fallback', tag: r.tag, text: el.text };
1676
+ }
1677
+ throw e;
1678
+ }
1617
1679
  }
1618
1680
 
1619
1681
  case 'fill': {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Agent360 Browser MCP",
4
- "version": "1.20.1",
4
+ "version": "1.21.0",
5
5
  "description": "Control your real Chrome from Claude Code — navigate, click, fill, screenshot, solve CAPTCHAs. 33 tools, multi-session.",
6
6
  "permissions": [
7
7
  "tabs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent360/browser-mcp",
3
- "version": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "description": "Browser MCP — control your real Chrome from Claude Code. 33 tools, CAPTCHA solving, date pickers, autocomplete combobox, overlay dismissal, file upload, multi-session, human-in-the-loop.",
5
5
  "mcpName": "io.github.Agent360dk/browser-mcp",
6
6
  "type": "module",