safari-mcp 2.15.6 → 2.15.7

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.
Files changed (2) hide show
  1. package/index.js +23 -13
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -210,19 +210,21 @@ function _startMemoryMonitor() {
210
210
 
211
211
  // Cleanup on exit
212
212
  let _cleaningUp = false;
213
+ async function _shutdown() {
214
+ if (_cleaningUp) return;
215
+ _cleaningUp = true;
216
+ // Restore the user's clipboard synchronously FIRST — if a native paste is mid-flight, its
217
+ // 2s restore timer would never fire once we exit, leaving the tool's text on the clipboard.
218
+ try { safari.flushClipboardRestore(); } catch {}
219
+ // Cap tab cleanup — if the daemon/Safari is wedged at shutdown (exactly when a SIGTERM
220
+ // tends to arrive), listTabs/closeTab can each block for their full timeout; never let
221
+ // that hang the exit past 3s.
222
+ await Promise.race([_cleanupTabs(), new Promise(r => setTimeout(r, 3000))]);
223
+ process.exit(0);
224
+ }
225
+
213
226
  for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
214
- process.on(sig, async () => {
215
- if (_cleaningUp) return; // Prevent double-exit on rapid signal repeat
216
- _cleaningUp = true;
217
- // Restore the user's clipboard synchronously FIRST — if a native paste is mid-flight, its
218
- // 2s restore timer would never fire once we exit, leaving the tool's text on the clipboard.
219
- try { safari.flushClipboardRestore(); } catch {}
220
- // Cap tab cleanup — if the daemon/Safari is wedged at shutdown (exactly when a SIGTERM
221
- // tends to arrive), listTabs/closeTab can each block for their full timeout; never let
222
- // that hang the exit past 3s.
223
- await Promise.race([_cleanupTabs(), new Promise(r => setTimeout(r, 3000))]);
224
- process.exit(0);
225
- });
227
+ process.on(sig, _shutdown);
226
228
  }
227
229
  process.on("exit", () => {
228
230
  if (_openedTabs.size > 0) {
@@ -2420,4 +2422,12 @@ _startMemoryMonitor();
2420
2422
 
2421
2423
  // Transport is chosen at runtime: default stdio (unchanged), or a shared HTTP instance when
2422
2424
  // SAFARI_MCP_HTTP=1 (many Claude sessions → one process). buildServer is the per-session factory.
2423
- await startTransport(buildServer, process.env);
2425
+ const transportHandle = await startTransport(buildServer, process.env);
2426
+
2427
+ if (transportHandle.kind === "stdio") {
2428
+ // A client can disappear without delivering a signal. Treat its pipe closing as the stdio
2429
+ // server's lifecycle boundary, while leaving the opt-in HTTP daemon independent of stdin.
2430
+ process.stdin.once("end", _shutdown);
2431
+ process.stdin.once("close", _shutdown);
2432
+ if (process.stdin.readableEnded || process.stdin.destroyed) void _shutdown();
2433
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-mcp",
3
- "version": "2.15.6",
3
+ "version": "2.15.7",
4
4
  "mcpName": "io.github.achiya-automation/safari-mcp",
5
5
  "description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 96 tools via AppleScript + JavaScript.",
6
6
  "type": "module",