@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 CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  ![Browser MCP Demo](assets/demo.gif)
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). 25 tools total.
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. 25 browser tools are now available in Claude Code.
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
- ## 25 Tools
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 # 21 tool definitions
148
+ tools.js # 29 tool definitions
139
149
  bin/cli.js # Install CLI
140
150
  ```
141
151
 
@@ -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 fiber lookup as backup for React SPAs.
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 (${captcha}) detected. Use browser_ask_user to ask the user to solve it, then retry.`;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Agent360 Browser MCP",
4
- "version": "1.15.0",
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.15.0' },
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.15.0",
4
- "description": "Browser MCP — control your real Chrome from Claude Code. 28 tools, CAPTCHA solving, multi-session, human-in-the-loop.",
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.',