@agent360/browser-mcp 1.19.0 → 1.20.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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Agent360 Browser MCP",
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.",
4
+ "version": "1.20.1",
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/index.js CHANGED
@@ -61,6 +61,10 @@ let cmdId = 0;
61
61
  let lastActivity = Date.now();
62
62
  const pending = new Map();
63
63
 
64
+ // Timers hoisted to module scope so gracefulShutdown can clear them deterministically.
65
+ let heartbeat = null;
66
+ let parentCheck = null;
67
+
64
68
  // ── WebSocket Server ───────────────────────────────────────────────────────
65
69
 
66
70
  function createWSS(port = BASE_PORT) {
@@ -98,8 +102,8 @@ function createWSS(port = BASE_PORT) {
98
102
  try { msg = JSON.parse(data.toString()); } catch { return; }
99
103
 
100
104
  if (msg.type === 'terminate') {
101
- process.stderr.write('[MCP] Terminate signal received from extension (last tab closed) — exiting\n');
102
- process.exit(0);
105
+ gracefulShutdown('Terminate signal from extension (last tab closed)');
106
+ return;
103
107
  }
104
108
 
105
109
  const { id, result, error } = msg;
@@ -124,14 +128,13 @@ function createWSS(port = BASE_PORT) {
124
128
  process.stderr.write(`[MCP] WebSocket server listening on ws://127.0.0.1:${port}\n`);
125
129
  });
126
130
 
127
- // Heartbeat + idle timeout (4 hours)
128
- setInterval(() => {
131
+ // Heartbeat + idle timeout (4 hours) — hoisted to module scope so gracefulShutdown can clear it
132
+ heartbeat = setInterval(() => {
129
133
  if (extensionSocket && extensionSocket.readyState === 1) {
130
134
  extensionSocket.ping();
131
135
  }
132
136
  if (Date.now() - lastActivity > 4 * 60 * 60 * 1000) {
133
- process.stderr.write('[MCP] Idle timeout (4h) — shutting down\n');
134
- process.exit(0);
137
+ gracefulShutdown('Idle timeout (4h)');
135
138
  }
136
139
  }, 20000);
137
140
  }
@@ -362,12 +365,35 @@ async function handleExtractToken(args) {
362
365
  return { content };
363
366
  }
364
367
 
365
- // ── Start ───────────────────────────────────────────────────────────────────
368
+ // ── Graceful shutdown ──────────────────────────────────────────────────────
369
+ // All shutdown paths funnel through gracefulShutdown so the cleanup chain runs
370
+ // deterministically — even on abrupt parent-exit. Without this, process.exit(0)
371
+ // was racing against WS close-handshake, leaving zombie tabs in Chrome.
372
+
373
+ let shuttingDown = false;
374
+ function gracefulShutdown(reason, code = 0) {
375
+ if (shuttingDown) return;
376
+ shuttingDown = true;
377
+ process.stderr.write(`[MCP] ${reason} — shutting down\n`);
378
+
379
+ // Stop timers so they can't re-enter gracefulShutdown
380
+ if (parentCheck) clearInterval(parentCheck);
381
+ if (heartbeat) clearInterval(heartbeat);
366
382
 
367
- // Clean shutdown release port so next session can use it
368
- process.on('SIGTERM', () => process.exit(0));
369
- process.on('SIGINT', () => process.exit(0));
383
+ // Close WS with explicit close-frame so extension's onclose handler fires
384
+ if (extensionSocket && extensionSocket.readyState === 1) {
385
+ try { extensionSocket.close(1000, 'mcp-shutdown'); } catch {}
386
+ }
387
+ if (wss) try { wss.close(); } catch {}
388
+
389
+ // 300ms grace for FIN-flush + extension session_disconnect cleanup
390
+ setTimeout(() => process.exit(code), 300);
391
+ }
392
+
393
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
394
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
370
395
  process.on('exit', () => {
396
+ // Safety net for direct process.exit calls that bypass gracefulShutdown
371
397
  if (wss) try { wss.close(); } catch {}
372
398
  if (extensionSocket) try { extensionSocket.close(); } catch {}
373
399
  });
@@ -375,21 +401,16 @@ process.on('exit', () => {
375
401
  // Detect Claude Code exit — check if parent process is still alive
376
402
  // stdin.on('end') doesn't work because MCP SDK's StdioServerTransport owns stdin
377
403
  const parentPid = process.ppid;
378
- const parentCheck = setInterval(() => {
404
+ parentCheck = setInterval(() => {
379
405
  try {
380
406
  process.kill(parentPid, 0); // signal 0 = check if process exists
381
407
  } catch {
382
- process.stderr.write(`[MCP] Parent process ${parentPid} died — shutting down\n`);
383
- clearInterval(parentCheck);
384
- process.exit(0);
408
+ gracefulShutdown(`Parent process ${parentPid} died`);
385
409
  }
386
410
  }, 5000); // check every 5 seconds
387
411
 
388
412
  // Also listen for stdin close as backup
389
- process.stdin.on('end', () => {
390
- process.stderr.write('[MCP] stdin closed — shutting down\n');
391
- process.exit(0);
392
- });
413
+ process.stdin.on('end', () => gracefulShutdown('stdin closed'));
393
414
 
394
415
  const transport = new StdioServerTransport();
395
416
  await mcpServer.connect(transport);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agent360/browser-mcp",
3
- "version": "1.19.0",
4
- "description": "Browser MCP — control your real Chrome from Claude Code. 29 tools, CAPTCHA solving, file upload, multi-session, human-in-the-loop.",
3
+ "version": "1.20.1",
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",
7
7
  "main": "index.js",
@@ -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",