// eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyHandler = (...args: any[]) => any; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyServer = { registerTool: (...args: any[]) => any }; let callCounter = 0; function nextToken(): string { return (++callCounter).toString(16).padStart(6, "0"); } /** * True when an MCP handler's return value signals failure via `isError: true`. * * An MCP handler reports failure two ways: it throws, or it returns a payload * with `isError: true`. Only the throw used to count, so a tool that caught an * error and returned it — the prevailing pattern — logged `outcome=ok`. An * operator grepping the log for failures saw a clean run over a hard error * (Task 1688: a Graph 404 logged `outcome=ok`). */ function isErrorPayload(result: unknown): boolean { return ( typeof result === "object" && result !== null && (result as { isError?: unknown }).isError === true ); } function censusHandles(): string { try { const resources = ( process as unknown as { getActiveResourcesInfo?: () => string[]; } ).getActiveResourcesInfo?.(); if (!resources) return "unavailable"; if (resources.length === 0) return "none"; const counts: Record = {}; for (const r of resources) { counts[r] = (counts[r] ?? 0) + 1; } return Object.entries(counts) .map(([k, v]) => `${k}:${v}`) .join(" "); } catch { return "unavailable"; } } export function wrapWithLifeline(name: string, handler: AnyHandler): AnyHandler { return async (...args: unknown[]) => { const plugin = process.env.MCP_SPAWN_TEE_NAME ?? "unknown"; const token = nextToken(); try { process.stderr.write(`[${plugin}] op=request tool=${name} token=${token}\n`); } catch { try { process.stderr.write(`[mcp-lifeline] op=self-error tool=${name}\n`); } catch { // stderr completely broken — still run handler } return handler(...args); } const start = process.hrtime.bigint(); let outcome: "ok" | "error" = "ok"; let thrown: unknown; // Whether the handler THREW is distinct from whether it FAILED: a returned // isError payload is a failure that must still be returned, not re-thrown. // The re-throw below keys off this flag, never off `outcome`. let didThrow = false; let result: unknown; try { result = await handler(...args); if (isErrorPayload(result)) outcome = "error"; } catch (err) { outcome = "error"; didThrow = true; thrown = err; } const ms = Number((process.hrtime.bigint() - start) / BigInt(1_000_000)); const handles = censusHandles(); try { process.stderr.write( `[${plugin}] op=result tool=${name} token=${token} outcome=${outcome} ms=${ms} handles=${handles}\n`, ); } catch { // broken pipe on result write — ignore, handler outcome takes priority } if (didThrow) throw thrown; return result; }; } export function lifelineTool( server: AnyServer, name: string, description: string, inputSchema: Record, handler: AnyHandler, ): void { server.registerTool(name, { description, inputSchema }, wrapWithLifeline(name, handler)); }