@agent360/browser-mcp 1.19.0 → 1.20.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/manifest.json +1 -1
- package/index.js +39 -18
- package/package.json +2 -2
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.20.0",
|
|
5
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",
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
// ──
|
|
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
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
404
|
+
parentCheck = setInterval(() => {
|
|
379
405
|
try {
|
|
380
406
|
process.kill(parentPid, 0); // signal 0 = check if process exists
|
|
381
407
|
} catch {
|
|
382
|
-
|
|
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.
|
|
4
|
-
"description": "Browser MCP — control your real Chrome from Claude Code.
|
|
3
|
+
"version": "1.20.0",
|
|
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",
|