@agent360/browser-mcp 1.16.0 → 1.19.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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Agent360 Browser MCP",
4
- "version": "1.16.0",
5
- "description": "Control your real Chrome from Claude Code — navigate, click, fill, screenshot, solve CAPTCHAs. 24 tools, multi-session, human-in-the-loop.",
4
+ "version": "1.19.0",
5
+ "description": "Control your real Chrome from Claude Code — navigate, click, fill, set dates, dismiss overlays, autocomplete, upload, screenshot, solve CAPTCHAs. 33 tools, multi-session, human-in-the-loop.",
6
6
  "permissions": [
7
7
  "tabs",
8
8
  "tabGroups",
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  const BASE_PORT = 9876;
12
- const MAX_PORT = 9885;
12
+ const MAX_PORT = 9895;
13
13
  const connections = new Map(); // port → WebSocket
14
14
 
15
15
  function scanPorts() {
@@ -92,6 +92,20 @@ function updateStatus() {
92
92
  }).catch(() => {});
93
93
  }
94
94
 
95
+ // Listen for terminate signals from background.js (sent when last tab in a session closes)
96
+ chrome.runtime.onMessage.addListener((msg) => {
97
+ if (msg.type !== 'terminate_mcp_session' || typeof msg.port !== 'number') return;
98
+ const ws = connections.get(msg.port);
99
+ if (!ws) return;
100
+ try {
101
+ if (ws.readyState === WebSocket.OPEN) {
102
+ ws.send(JSON.stringify({ type: 'terminate' }));
103
+ }
104
+ } catch {}
105
+ try { ws.close(); } catch {}
106
+ // ws.onclose handler removes from connections + notifies background
107
+ });
108
+
95
109
  // Initial scan + frequent rescan for new servers
96
110
  scanPorts();
97
111
  setInterval(scanPorts, 2000);
package/index.js CHANGED
@@ -14,10 +14,16 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
14
14
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
15
15
  import { WebSocketServer } from 'ws';
16
16
  import { execSync } from 'child_process';
17
- import { dirname } from 'path';
17
+ import { dirname, join } from 'path';
18
18
  import { fileURLToPath } from 'url';
19
+ import { readFileSync } from 'fs';
19
20
  import { TOOLS, PROVIDER_PAGES } from './tools.js';
20
21
 
22
+ // Read version from package.json — single source of truth, never drifts
23
+ const PKG_VERSION = JSON.parse(
24
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'package.json'), 'utf8')
25
+ ).version;
26
+
21
27
  // ── Auto-update on startup ─────────────────────────────────────────────────
22
28
 
23
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -47,7 +53,7 @@ try {
47
53
  }
48
54
 
49
55
  const BASE_PORT = 9876;
50
- const MAX_PORT = 9885;
56
+ const MAX_PORT = 9895; // 20 ports instead of 10 — zombies die within 5s via parent check
51
57
  let extensionSocket = null;
52
58
  let activePort = null;
53
59
  let wss = null; // Track WSS for graceful shutdown
@@ -90,6 +96,12 @@ function createWSS(port = BASE_PORT) {
90
96
  ws.on('message', (data) => {
91
97
  let msg;
92
98
  try { msg = JSON.parse(data.toString()); } catch { return; }
99
+
100
+ if (msg.type === 'terminate') {
101
+ process.stderr.write('[MCP] Terminate signal received from extension (last tab closed) — exiting\n');
102
+ process.exit(0);
103
+ }
104
+
93
105
  const { id, result, error } = msg;
94
106
  const p = pending.get(id);
95
107
  if (!p) return;
@@ -128,12 +140,16 @@ createWSS();
128
140
 
129
141
  // ── Send command to extension ───────────────────────────────────────────────
130
142
 
131
- function sendToExtension(method, params = {}, timeoutMs = 30000) {
132
- return new Promise((resolve, reject) => {
133
- if (!extensionSocket || extensionSocket.readyState !== 1) {
134
- reject(new Error('Chrome extension not connected. Open Chrome and ensure Agent360 Browser MCP extension is installed.'));
135
- return;
143
+ async function sendToExtension(method, params = {}, timeoutMs = 30000, _retries = 5) {
144
+ // Retry if extension is temporarily disconnected (reconnects every 2s)
145
+ if (!extensionSocket || extensionSocket.readyState !== 1) {
146
+ if (_retries > 0) {
147
+ await new Promise(r => setTimeout(r, 1500));
148
+ return sendToExtension(method, params, timeoutMs, _retries - 1);
136
149
  }
150
+ throw new Error('Chrome extension not connected after 5 retries. Open Chrome and ensure Agent360 Browser MCP extension is installed.');
151
+ }
152
+ return new Promise((resolve, reject) => {
137
153
  const id = ++cmdId;
138
154
  const timer = setTimeout(() => {
139
155
  pending.delete(id);
@@ -206,11 +222,18 @@ For image grid challenges: cells are 0-indexed, left-to-right, top-to-bottom. A
206
222
  - If a standard selector fails, the extension recursively searches shadow roots
207
223
  - Text-based selectors ("text=Submit") also traverse shadow DOM
208
224
 
225
+ ## Hard inputs — use the specialised tools first
226
+ - **Date inputs** → use browser_set_date (NOT browser_fill). Handles native date inputs, masked text inputs (MM/DD/YYYY etc.), AND calendar pickers (MUI, react-datepicker, AntD, Lexical/Meta). 3-path fallback with read-back verification.
227
+ - **Autocomplete / combobox** (Languages on Meta Ads, country selects, async dropdowns) → use browser_set_combobox (NOT browser_select_option). Types partial query, waits for filtered listbox, clicks option. Supports multi-value chips.
228
+ - **Drag-drop file zones without visible file input** → use browser_drop_file (NOT browser_upload_file). Finds hidden input in subtree/parent.
229
+ - **Annoying popups blocking the flow** (cookie banners, "Don't show again", Advantage+ tooltips, draft-confirm prompts) → call browser_dismiss_overlays before each major step. It only clicks safe close affordances by default; preserves forms with editable text fields.
230
+
209
231
  ## When things fail
210
232
  - Element not found → try text-based selector instead of CSS
211
233
  - Screenshot fails → debugger fallback is automatic
212
234
  - Click doesn't work on SPA → debugger mouse events are used automatically
213
235
  - CAPTCHA blocks page → use browser_ask_user, let human solve it
236
+ - browser_fill seemingly succeeds but value reverts → switch to browser_set_date or browser_set_combobox (most reverts are React-controlled validators)
214
237
 
215
238
  ## Extension updates
216
239
  The MCP server auto-pulls the latest code from git on every new session startup.
@@ -219,7 +242,7 @@ If the extension files were updated, ask the user to reload it:
219
242
  You cannot navigate to chrome:// pages — the user must do this manually.`;
220
243
 
221
244
  const mcpServer = new Server(
222
- { name: 'agent360-browser', version: '1.16.0' },
245
+ { name: 'agent360-browser', version: PKG_VERSION },
223
246
  { capabilities: { tools: {} } },
224
247
  { instructions: INSTRUCTIONS },
225
248
  );
@@ -262,6 +285,10 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
262
285
  browser_set_local_storage: 'set_local_storage',
263
286
  browser_console_logs: 'console_logs',
264
287
  browser_solve_captcha: 'solve_captcha',
288
+ browser_set_date: 'set_date',
289
+ browser_dismiss_overlays: 'dismiss_overlays',
290
+ browser_set_combobox: 'set_combobox',
291
+ browser_drop_file: 'drop_file',
265
292
  };
266
293
 
267
294
  if (name === 'browser_extract_token') {
@@ -345,7 +372,20 @@ process.on('exit', () => {
345
372
  if (extensionSocket) try { extensionSocket.close(); } catch {}
346
373
  });
347
374
 
348
- // Detect Claude Code exit (stdin closes when conversation ends)
375
+ // Detect Claude Code exit check if parent process is still alive
376
+ // stdin.on('end') doesn't work because MCP SDK's StdioServerTransport owns stdin
377
+ const parentPid = process.ppid;
378
+ const parentCheck = setInterval(() => {
379
+ try {
380
+ process.kill(parentPid, 0); // signal 0 = check if process exists
381
+ } catch {
382
+ process.stderr.write(`[MCP] Parent process ${parentPid} died — shutting down\n`);
383
+ clearInterval(parentCheck);
384
+ process.exit(0);
385
+ }
386
+ }, 5000); // check every 5 seconds
387
+
388
+ // Also listen for stdin close as backup
349
389
  process.stdin.on('end', () => {
350
390
  process.stderr.write('[MCP] stdin closed — shutting down\n');
351
391
  process.exit(0);
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@agent360/browser-mcp",
3
- "version": "1.16.0",
3
+ "version": "1.19.0",
4
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
@@ -56,7 +56,7 @@ export const TOOLS = [
56
56
  },
57
57
  {
58
58
  name: 'browser_fill',
59
- description: 'Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API.',
59
+ description: 'Fill a form input field with a value. Supports CSS selectors AND text-based selectors. Auto-scrolls and focuses the element. Works on CSP-strict sites via Chrome Debugger API. For date inputs use browser_set_date, for autocomplete/combobox use browser_set_combobox.',
60
60
  inputSchema: {
61
61
  type: 'object',
62
62
  properties: {
@@ -120,7 +120,7 @@ export const TOOLS = [
120
120
  },
121
121
  {
122
122
  name: 'browser_select_option',
123
- description: 'Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text.',
123
+ description: 'Select an option from a dropdown menu. Works with native <select> elements AND custom dropdowns (Angular Material, React Select, etc.). For custom dropdowns: clicks the trigger, waits for options, then clicks the matching option by text. For autocomplete (typing filters options) use browser_set_combobox instead.',
124
124
  inputSchema: {
125
125
  type: 'object',
126
126
  properties: {
@@ -131,6 +131,59 @@ export const TOOLS = [
131
131
  required: ['selector', 'option'],
132
132
  },
133
133
  },
134
+ {
135
+ name: 'browser_dismiss_overlays',
136
+ description: 'Dismiss visible popups, modals, tooltips, banners, and "Are you sure?"-style overlays in one call. Heuristic-based: finds close affordance via aria-label, text content (Skip/Cancel/Ikke nu/Don\'t show/Got it/Close), or × character button. Use when a flow is interrupted by unexpected dialogs (cookie banners, onboarding tooltips, draft-confirm prompts on Meta Ads, etc.). Returns list of what was dismissed.',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ scope: { type: 'string', enum: ['non_critical', 'aggressive'], description: 'non_critical (default): skip dialogs containing editable form inputs (preserves user data). aggressive: dismiss everything.' },
141
+ max_passes: { type: 'number', description: 'Number of dismissal passes (some overlays reveal others when closed). Default: 3' },
142
+ },
143
+ },
144
+ },
145
+ {
146
+ name: 'browser_set_combobox',
147
+ description: 'Set value(s) on an autocomplete/combobox input. Handles the click → type query → wait for filtered listbox → click option flow as one MCP call. Supports multi-select (e.g., Languages on Meta Ads). Use when browser_select_option fails because options render lazily after typing.',
148
+ inputSchema: {
149
+ type: 'object',
150
+ properties: {
151
+ selector: { type: 'string', description: 'CSS selector for the combobox/autocomplete input' },
152
+ value: { type: 'string', description: 'Single value to select (use this OR values)' },
153
+ values: { type: 'array', items: { type: 'string' }, description: 'Array of values for multi-select. E.g. ["Danish", "English", "Swedish"]' },
154
+ multi: { type: 'boolean', description: 'True if combobox accepts multiple values (chips). Default: auto-detected from presence of values array' },
155
+ query_chars: { type: 'number', description: 'How many characters to type as filter query (default: 4 or full value length, whichever is smaller)' },
156
+ wait_ms: { type: 'number', description: 'Max ms to wait for options listbox to appear after typing (default: 3000)' },
157
+ },
158
+ required: ['selector'],
159
+ },
160
+ },
161
+ {
162
+ name: 'browser_drop_file',
163
+ description: 'Upload a file by finding a hidden <input type="file"> within a drag-drop zone\'s subtree (or parent up to 2 levels). Use when browser_upload_file fails because the dropzone has no visible file input. Returns clear error if no input is found anywhere — pure drop-zones without backing inputs require manual handling.',
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ selector: { type: 'string', description: 'CSS selector for the drop-zone target element (e.g. ".upload-area")' },
168
+ file: { type: 'string', description: 'Single absolute file path' },
169
+ files: { type: 'array', items: { type: 'string' }, description: 'Array of absolute file paths' },
170
+ },
171
+ required: ['selector'],
172
+ },
173
+ },
174
+ {
175
+ name: 'browser_set_date',
176
+ description: 'Robustly set a date input — handles native <input type="date">, masked text inputs (e.g. MM/DD/YYYY), and calendar pickers (MUI, react-datepicker, AntD, Lexical/Meta). Tries native value-set, format-aware typing via Input.insertText, and ARIA-based picker navigation in sequence with read-back verification. Use instead of browser_fill when fill fails or for any input that opens a calendar widget.',
177
+ inputSchema: {
178
+ type: 'object',
179
+ properties: {
180
+ selector: { type: 'string', description: 'CSS selector for the date input element' },
181
+ date: { type: 'string', description: 'ISO date string (YYYY-MM-DD), e.g. "2026-05-15"' },
182
+ skip_picker: { type: 'boolean', description: 'If true, only try native + masked paths and skip calendar-picker navigation (default: false)' },
183
+ },
184
+ required: ['selector', 'date'],
185
+ },
186
+ },
134
187
  {
135
188
  name: 'browser_handle_dialog',
136
189
  description: 'Handle JavaScript alert(), confirm(), or prompt() dialogs. Call this BEFORE triggering the action that causes the dialog. Waits for the dialog to appear, then accepts or dismisses it.',
@@ -307,7 +360,7 @@ export const TOOLS = [
307
360
  },
308
361
  {
309
362
  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.',
363
+ description: 'Upload a file to a <input type="file"> element on the page. Uses Chrome Debugger API to set files programmatically — no dialog needed. For drag-drop zones without visible file input use browser_drop_file.',
311
364
  inputSchema: {
312
365
  type: 'object',
313
366
  properties: {