@agent360/browser-mcp 1.20.0 → 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.
- package/extension/background.js +84 -22
- package/extension/manifest.json +2 -2
- package/package.json +4 -2
package/extension/background.js
CHANGED
|
@@ -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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
msg
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
//
|
|
1615
|
-
|
|
1616
|
-
|
|
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': {
|
package/extension/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Agent360 Browser MCP",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"description": "Control your real Chrome from Claude Code — navigate, click, fill,
|
|
4
|
+
"version": "1.21.0",
|
|
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",
|
|
8
8
|
"tabGroups",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent360/browser-mcp",
|
|
3
|
-
"version": "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",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"start": "node index.js"
|
|
19
|
+
"start": "node index.js",
|
|
20
|
+
"publish:cws": "../scripts/publish-cws.sh",
|
|
21
|
+
"publish:cws:draft": "../scripts/publish-cws.sh --draft"
|
|
20
22
|
},
|
|
21
23
|
"keywords": [
|
|
22
24
|
"mcp",
|