@agent360/browser-mcp 1.15.0 → 1.16.1
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 +14 -4
- package/extension/background.js +64 -4
- package/extension/manifest.json +1 -1
- package/index.js +2 -1
- package/package.json +3 -2
- package/tools.js +13 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
-
The only browser MCP with **multi-session support** (10 concurrent AI sessions), **human-in-the-loop** (2FA, CAPTCHA, credentials), and **built-in provider integrations** (Stripe, HubSpot, Slack, and 6 more).
|
|
13
|
+
The only browser MCP with **multi-session support** (10 concurrent AI sessions), **human-in-the-loop** (2FA, CAPTCHA, credentials), and **built-in provider integrations** (Stripe, HubSpot, Slack, and 6 more). 29 tools total.
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ Then load the extension in Chrome:
|
|
|
26
26
|
3. Click **Load unpacked** → select `~/.browser-mcp/extension/`
|
|
27
27
|
4. Restart Claude Code
|
|
28
28
|
|
|
29
|
-
That's it.
|
|
29
|
+
That's it. 29 browser tools are now available in Claude Code.
|
|
30
30
|
|
|
31
31
|
## Why This Over Playwright MCP / BrowserMCP?
|
|
32
32
|
|
|
@@ -43,7 +43,7 @@ That's it. 25 browser tools are now available in Claude Code.
|
|
|
43
43
|
| **Custom dropdowns** | Angular Material, React Select support | Works (headless) | Limited |
|
|
44
44
|
| **Install** | `npx @agent360/browser-mcp install` | `npx @anthropic-ai/mcp-playwright` | Manual clone |
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## 29 Tools
|
|
47
47
|
|
|
48
48
|
### Navigation & Content
|
|
49
49
|
| Tool | Description |
|
|
@@ -94,6 +94,16 @@ That's it. 25 browser tools are now available in Claude Code.
|
|
|
94
94
|
|------|-------------|
|
|
95
95
|
| `browser_ask_user` | Show overlay dialog for 2FA, CAPTCHA, credentials, or any user input |
|
|
96
96
|
|
|
97
|
+
### Data
|
|
98
|
+
| Tool | Description |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| `browser_get_cookies` | Get cookies for a domain |
|
|
101
|
+
| `browser_set_cookies` | Set cookies for a domain |
|
|
102
|
+
| `browser_get_local_storage` | Read localStorage from page |
|
|
103
|
+
| `browser_set_local_storage` | Write localStorage values |
|
|
104
|
+
| `browser_console_logs` | Capture console.log/warn/error messages from page |
|
|
105
|
+
| `browser_upload_file` | Upload files to `<input type="file">` via Chrome Debugger API (no dialog) |
|
|
106
|
+
|
|
97
107
|
## Multi-Session Support
|
|
98
108
|
|
|
99
109
|
Each Claude Code conversation gets its own MCP server on a unique port (9876-9885). The Chrome extension connects to all active servers simultaneously.
|
|
@@ -135,7 +145,7 @@ extension/
|
|
|
135
145
|
|
|
136
146
|
mcp-server/
|
|
137
147
|
index.js # MCP server (stdio) + WebSocket client
|
|
138
|
-
tools.js #
|
|
148
|
+
tools.js # 29 tool definitions
|
|
139
149
|
bin/cli.js # Install CLI
|
|
140
150
|
```
|
|
141
151
|
|
package/extension/background.js
CHANGED
|
@@ -255,12 +255,13 @@ async function debuggerClick(tabId, x, y) {
|
|
|
255
255
|
type: 'mouseReleased', x, y, button: 'left', clickCount: 1,
|
|
256
256
|
});
|
|
257
257
|
// 3. CDP doesn't synthesize 'click' event from mousePressed/mouseReleased.
|
|
258
|
-
// Fire JS click + React
|
|
258
|
+
// Fire JS click + React/Angular framework fallbacks.
|
|
259
259
|
await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
|
|
260
260
|
expression: `(() => {
|
|
261
261
|
const el = document.elementFromPoint(${x}, ${y});
|
|
262
262
|
if (!el) return;
|
|
263
263
|
el.click();
|
|
264
|
+
|
|
264
265
|
// React fiber fallback — find and call onClick handler directly
|
|
265
266
|
const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber') || k.startsWith('__reactInternalInstance'));
|
|
266
267
|
if (fiberKey) {
|
|
@@ -270,6 +271,18 @@ async function debuggerClick(tabId, x, y) {
|
|
|
270
271
|
fiber = fiber.return;
|
|
271
272
|
}
|
|
272
273
|
}
|
|
274
|
+
|
|
275
|
+
// Angular fallback — trigger via Zone.js patched events + ngClick
|
|
276
|
+
const ngKey = Object.keys(el).find(k => k.startsWith('__ng'));
|
|
277
|
+
if (ngKey || el.getAttribute('ng-click') || el.getAttribute('(click)')) {
|
|
278
|
+
// Angular uses Zone.js which patches addEventListener — dispatch real events through it
|
|
279
|
+
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true}));
|
|
280
|
+
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true}));
|
|
281
|
+
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window}));
|
|
282
|
+
// Angular Material components (mat-button, mat-checkbox, etc.) use ripple + internal handlers
|
|
283
|
+
const matRipple = el.closest('[mat-button], [mat-raised-button], [mat-icon-button], [mat-fab], mat-checkbox, mat-slide-toggle, mat-radio-button');
|
|
284
|
+
if (matRipple) matRipple.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window}));
|
|
285
|
+
}
|
|
273
286
|
})()`,
|
|
274
287
|
});
|
|
275
288
|
} finally {
|
|
@@ -671,9 +684,9 @@ async function dispatch(port, method, params) {
|
|
|
671
684
|
// Check for CAPTCHA after navigation
|
|
672
685
|
const captcha = await detectCaptcha(tab.id);
|
|
673
686
|
const result = { title: updated.title, url: updated.url, tab_id: tab.id, session: session.label };
|
|
674
|
-
if (captcha) {
|
|
675
|
-
result.captcha_detected = captcha;
|
|
676
|
-
result.hint = `CAPTCHA
|
|
687
|
+
if (captcha && captcha.found) {
|
|
688
|
+
result.captcha_detected = captcha.types.join(', ');
|
|
689
|
+
result.hint = `CAPTCHA detected: ${captcha.types.join(', ')}. Use browser_solve_captcha to handle it.`;
|
|
677
690
|
}
|
|
678
691
|
return result;
|
|
679
692
|
}
|
|
@@ -1406,6 +1419,53 @@ async function dispatch(port, method, params) {
|
|
|
1406
1419
|
return { error: 'Unknown action: ' + action };
|
|
1407
1420
|
}
|
|
1408
1421
|
|
|
1422
|
+
case 'upload_file': {
|
|
1423
|
+
const tab = await getSessionTab(port);
|
|
1424
|
+
const selector = params.selector || 'input[type="file"]';
|
|
1425
|
+
try {
|
|
1426
|
+
await debuggerAttach(tab.id);
|
|
1427
|
+
// Find the file input element
|
|
1428
|
+
const { result: nodeResult } = await chrome.debugger.sendCommand({ tabId: tab.id }, 'Runtime.evaluate', {
|
|
1429
|
+
expression: `(() => {
|
|
1430
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
1431
|
+
if (!el) return JSON.stringify({ found: false, error: 'File input not found: ${selector}' });
|
|
1432
|
+
return JSON.stringify({ found: true, tag: el.tagName, type: el.type, accept: el.accept, multiple: el.multiple });
|
|
1433
|
+
})()`,
|
|
1434
|
+
returnByValue: true,
|
|
1435
|
+
});
|
|
1436
|
+
const info = JSON.parse(nodeResult.value);
|
|
1437
|
+
if (!info.found) {
|
|
1438
|
+
await debuggerDetach(tab.id);
|
|
1439
|
+
return info;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Get the DOM node ID for the file input
|
|
1443
|
+
const { result: docResult } = await chrome.debugger.sendCommand({ tabId: tab.id }, 'DOM.getDocument', {});
|
|
1444
|
+
const { nodeId } = await chrome.debugger.sendCommand({ tabId: tab.id }, 'DOM.querySelector', {
|
|
1445
|
+
nodeId: docResult.root.nodeId,
|
|
1446
|
+
selector: selector,
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
if (!nodeId) {
|
|
1450
|
+
await debuggerDetach(tab.id);
|
|
1451
|
+
return { found: false, error: 'Could not get DOM node for file input' };
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// Set files on the input using CDP
|
|
1455
|
+
const files = Array.isArray(params.files) ? params.files : [params.files || params.file];
|
|
1456
|
+
await chrome.debugger.sendCommand({ tabId: tab.id }, 'DOM.setFileInputFiles', {
|
|
1457
|
+
nodeId: nodeId,
|
|
1458
|
+
files: files,
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
await debuggerDetach(tab.id);
|
|
1462
|
+
return { ok: true, files: files, input: info };
|
|
1463
|
+
} catch (e) {
|
|
1464
|
+
try { await debuggerDetach(tab.id); } catch {}
|
|
1465
|
+
return { ok: false, error: e.message };
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1409
1469
|
case 'reload_extension': {
|
|
1410
1470
|
// MCP server signals that extension files were updated via npx
|
|
1411
1471
|
// Reload after a short delay to allow response to be sent
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Agent360 Browser MCP",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.16.0",
|
|
5
5
|
"description": "Control your real Chrome from Claude Code — navigate, click, fill, screenshot, solve CAPTCHAs. 24 tools, multi-session, human-in-the-loop.",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"tabs",
|
package/index.js
CHANGED
|
@@ -219,7 +219,7 @@ If the extension files were updated, ask the user to reload it:
|
|
|
219
219
|
You cannot navigate to chrome:// pages — the user must do this manually.`;
|
|
220
220
|
|
|
221
221
|
const mcpServer = new Server(
|
|
222
|
-
{ name: 'agent360-browser', version: '1.
|
|
222
|
+
{ name: 'agent360-browser', version: '1.16.0' },
|
|
223
223
|
{ capabilities: { tools: {} } },
|
|
224
224
|
{ instructions: INSTRUCTIONS },
|
|
225
225
|
);
|
|
@@ -257,6 +257,7 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
257
257
|
browser_get_new_tab: 'get_new_tab',
|
|
258
258
|
browser_switch_tab: 'switch_tab',
|
|
259
259
|
browser_close_tab: 'close_tab',
|
|
260
|
+
browser_upload_file: 'upload_file',
|
|
260
261
|
browser_set_cookies: 'set_cookies',
|
|
261
262
|
browser_set_local_storage: 'set_local_storage',
|
|
262
263
|
browser_console_logs: 'console_logs',
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent360/browser-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Browser MCP — control your real Chrome from Claude Code.
|
|
3
|
+
"version": "1.16.1",
|
|
4
|
+
"description": "Browser MCP — control your real Chrome from Claude Code. 29 tools, CAPTCHA solving, file upload, multi-session, human-in-the-loop.",
|
|
5
|
+
"mcpName": "io.github.Agent360dk/browser-mcp",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"bin": {
|
package/tools.js
CHANGED
|
@@ -305,6 +305,19 @@ export const TOOLS = [
|
|
|
305
305
|
required: ['tab_id'],
|
|
306
306
|
},
|
|
307
307
|
},
|
|
308
|
+
{
|
|
309
|
+
name: 'browser_upload_file',
|
|
310
|
+
description: 'Upload a file to a <input type="file"> element on the page. Uses Chrome Debugger API to set files programmatically — no dialog needed.',
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
selector: { type: 'string', description: 'CSS selector for the file input (default: input[type="file"])' },
|
|
315
|
+
files: { type: 'array', items: { type: 'string' }, description: 'Array of absolute file paths to upload. E.g. ["/Users/me/photo.jpg"]' },
|
|
316
|
+
file: { type: 'string', description: 'Single file path (alternative to files array)' },
|
|
317
|
+
},
|
|
318
|
+
required: ['files'],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
308
321
|
{
|
|
309
322
|
name: 'browser_extract_token',
|
|
310
323
|
description: 'Navigate to a provider\'s API settings page and extract the API token. Optionally store it in Agent360 vault.',
|