@agent360/browser-mcp
Advanced tools
| { | ||
| "manifest_version": 3, | ||
| "name": "Agent360 Browser MCP", | ||
| "version": "1.19.0", | ||
| "version": "1.20.0", | ||
| "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,0 +6,0 @@ "permissions": [ |
+39
-18
@@ -64,2 +64,6 @@ #!/usr/bin/env node | ||
| // Timers hoisted to module scope so gracefulShutdown can clear them deterministically. | ||
| let heartbeat = null; | ||
| let parentCheck = null; | ||
| // ── WebSocket Server ─────────────────────────────────────────────────────── | ||
@@ -102,4 +106,4 @@ | ||
| if (msg.type === 'terminate') { | ||
| process.stderr.write('[MCP] Terminate signal received from extension (last tab closed) — exiting\n'); | ||
| process.exit(0); | ||
| gracefulShutdown('Terminate signal from extension (last tab closed)'); | ||
| return; | ||
| } | ||
@@ -129,4 +133,4 @@ | ||
| // Heartbeat + idle timeout (4 hours) | ||
| setInterval(() => { | ||
| // Heartbeat + idle timeout (4 hours) — hoisted to module scope so gracefulShutdown can clear it | ||
| heartbeat = setInterval(() => { | ||
| if (extensionSocket && extensionSocket.readyState === 1) { | ||
@@ -136,4 +140,3 @@ extensionSocket.ping(); | ||
| if (Date.now() - lastActivity > 4 * 60 * 60 * 1000) { | ||
| process.stderr.write('[MCP] Idle timeout (4h) — shutting down\n'); | ||
| process.exit(0); | ||
| gracefulShutdown('Idle timeout (4h)'); | ||
| } | ||
@@ -369,8 +372,31 @@ }, 20000); | ||
| // ── Start ─────────────────────────────────────────────────────────────────── | ||
| // ── Graceful shutdown ────────────────────────────────────────────────────── | ||
| // All shutdown paths funnel through gracefulShutdown so the cleanup chain runs | ||
| // deterministically — even on abrupt parent-exit. Without this, process.exit(0) | ||
| // was racing against WS close-handshake, leaving zombie tabs in Chrome. | ||
| // Clean shutdown — release port so next session can use it | ||
| process.on('SIGTERM', () => process.exit(0)); | ||
| process.on('SIGINT', () => process.exit(0)); | ||
| let shuttingDown = false; | ||
| function gracefulShutdown(reason, code = 0) { | ||
| if (shuttingDown) return; | ||
| shuttingDown = true; | ||
| process.stderr.write(`[MCP] ${reason} — shutting down\n`); | ||
| // Stop timers so they can't re-enter gracefulShutdown | ||
| if (parentCheck) clearInterval(parentCheck); | ||
| if (heartbeat) clearInterval(heartbeat); | ||
| // Close WS with explicit close-frame so extension's onclose handler fires | ||
| if (extensionSocket && extensionSocket.readyState === 1) { | ||
| try { extensionSocket.close(1000, 'mcp-shutdown'); } catch {} | ||
| } | ||
| if (wss) try { wss.close(); } catch {} | ||
| // 300ms grace for FIN-flush + extension session_disconnect cleanup | ||
| setTimeout(() => process.exit(code), 300); | ||
| } | ||
| process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); | ||
| process.on('SIGINT', () => gracefulShutdown('SIGINT')); | ||
| process.on('exit', () => { | ||
| // Safety net for direct process.exit calls that bypass gracefulShutdown | ||
| if (wss) try { wss.close(); } catch {} | ||
@@ -383,9 +409,7 @@ if (extensionSocket) try { extensionSocket.close(); } catch {} | ||
| const parentPid = process.ppid; | ||
| const parentCheck = setInterval(() => { | ||
| parentCheck = setInterval(() => { | ||
| try { | ||
| process.kill(parentPid, 0); // signal 0 = check if process exists | ||
| } catch { | ||
| process.stderr.write(`[MCP] Parent process ${parentPid} died — shutting down\n`); | ||
| clearInterval(parentCheck); | ||
| process.exit(0); | ||
| gracefulShutdown(`Parent process ${parentPid} died`); | ||
| } | ||
@@ -395,6 +419,3 @@ }, 5000); // check every 5 seconds | ||
| // Also listen for stdin close as backup | ||
| process.stdin.on('end', () => { | ||
| process.stderr.write('[MCP] stdin closed — shutting down\n'); | ||
| process.exit(0); | ||
| }); | ||
| process.stdin.on('end', () => gracefulShutdown('stdin closed')); | ||
@@ -401,0 +422,0 @@ const transport = new StdioServerTransport(); |
+2
-2
| { | ||
| "name": "@agent360/browser-mcp", | ||
| "version": "1.19.0", | ||
| "description": "Browser MCP — control your real Chrome from Claude Code. 29 tools, CAPTCHA solving, file upload, multi-session, human-in-the-loop.", | ||
| "version": "1.20.0", | ||
| "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.", | ||
| "mcpName": "io.github.Agent360dk/browser-mcp", | ||
@@ -6,0 +6,0 @@ "type": "module", |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
187200
0.52%3531
0.46%