UNPKG

omnifocus-mcp

Version:

Model Context Protocol (MCP) server that integrates with OmniFocus for AI assistant interaction

52 lines (51 loc) 2.16 kB
/** * Idle-timeout backstop for the stdio server (issue #80). * * Signal/EOF propagation through the npx → cli.cjs → node wrapper chain is * unreliable: an orphaned server whose client was SIGKILL'd — and whose stdin an * intermediate wrapper is holding open — can survive for days, accumulating and * contending for AppleEvents on the single shared OmniFocus.app (the cause of the * -1712 timeout storms). The server's stdin-EOF and signal handlers catch the * clean-disconnect case; this is the last-resort catch for the unclean one. */ export const DEFAULT_IDLE_TIMEOUT_MINUTES = 30; /** * Resolve the idle timeout (in minutes) from an env value. Empty/undefined uses * the default; 0 disables; anything non-numeric or negative falls back to the * default with a warning. Pure and side-effect-free except the warning log. */ export function resolveIdleTimeoutMinutes(raw, defaultMinutes = DEFAULT_IDLE_TIMEOUT_MINUTES) { if (raw === undefined || raw.trim() === '') return defaultMinutes; const parsed = Number(raw); if (!Number.isFinite(parsed) || parsed < 0) { console.error(`[omnifocus-mcp] ignoring invalid OMNIFOCUS_MCP_IDLE_TIMEOUT_MINUTES="${raw}"; using default ${defaultMinutes}.`); return defaultMinutes; } return parsed; } /** * Arm an idle timer that fires `onIdle` after `minutes` with no data on `input`. * Every chunk resets the timer, so an actively-used server never times out * (each JSON-RPC request arrives as stdin data). A non-positive `minutes` * disables the backstop entirely. Returns a disposer that removes the listener * and clears the timer. */ export function installIdleTimeout(input, minutes, onIdle) { if (!(minutes > 0)) return () => { }; const idleMs = minutes * 60_000; let timer; const reset = () => { clearTimeout(timer); timer = setTimeout(onIdle, idleMs); // Don't let this timer alone keep the process alive — stdin already does. timer.unref?.(); }; input.on('data', reset); reset(); return () => { clearTimeout(timer); input.off('data', reset); }; }