#!/usr/bin/env node
"use strict";
/**
 * MCP spawn-tee — parent-side stderr capture wrapper.
 *
 * Claude Code's `--mcp-config` accepts `{command, args, env}` descriptors and
 * spawns each MCP server itself; the platform never holds a ChildProcess
 * handle. The in-process `mcp-stderr-tee` patches `process.stderr.write` from
 * inside the MCP server, but its writes go through `createWriteStream` —
 * async, buffered. A synchronous module-load throw (e.g. schema-loader's
 * line-168 width check on memory) calls `process.exit(1)` before the buffer
 * flushes, so the per-server log file is empty and the platform's
 * `[mcp-init-error] tail="(no stderr file)"` probe is operationally useless.
 * That class shipped as the chronic memory-MCP silent-fail loop fixed by
 * (and hit graph — solved there with per-plugin
 * `appendFileSync` discipline that this wrapper now generalises).
 *
 * The wrapper sits between Claude Code and the real MCP server: it spawns
 * the real entry with `stdio: ['inherit', 'inherit', 'pipe']`, then
 * synchronously appends every child stderr chunk to
 * `${LOG_DIR}/mcp-${name}-stderr-<date>.log`. Synchronous writes survive
 * `process.exit` because the kernel queues the syscall before the call
 * returns. The chunk is also written to the wrapper's own stderr so
 * Claude Code's existing stderr consumer is unchanged — the mechanism is
 * additive, not interceptive.
 *
 *   Claude Code CLI
 *        │ spawns
 *        ▼
 *   wrapper (this file) — argv[2] = real entry, env.MCP_SPAWN_TEE_NAME = name
 *        │ spawns child with stdio:['inherit','inherit','pipe']
 *        ▼
 *   child = node <real-entry>
 *        │
 *        ├──▶ stdin/stdout: inherited from wrapper (Claude Code pipe)
 *        └──▶ stderr → wrapper.on('data', chunk =>)
 *               ├──▶ appendFileSync(${LOG_DIR}/mcp-${name}-stderr-<date>.log)
 *               └──▶ process.stderr.write(chunk)  → Claude Code consumer
 *
 * The wrapper catches:
 *   - MODULE_NOT_FOUND on the entry script itself (success criterion #3:
 *     `mv plugins/memory/mcp/dist plugins/memory/mcp/dist-broken && reboot`
 *     leaves the cause on disk).
 *   - Synchronous throws during module load before the in-process tee runs.
 *   - Any stderr a normally-running plugin emits (steady-state telemetry
 *     remains visible too — the in-process tee handles per-line stream-log
 *     prefixing; this wrapper handles the raw per-server file).
 *
 * SIGTERM/SIGINT propagation: forwarded to the child so a Claude Code
 * shutdown does not orphan the MCP server. Child exit code is propagated
 * verbatim so upstream (Claude Code) sees the real outcome.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const node_child_process_1 = require("node:child_process");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const SERVER_NAME = process.env.MCP_SPAWN_TEE_NAME ?? "unknown";
const LOG_DIR = process.env.LOG_DIR;
const ENTRY = process.argv[2];
// Sync-emit: appendFileSync to per-server log + stderr passthrough. Used for
// wrapper-internal diagnostics (attach line, errors) and for forwarding child
// stderr chunks. Each destination wrapped independently — an unwritable log
// must not mask the primary output.
function syncEmitLine(line) {
    if (LOG_DIR) {
        try {
            (0, node_fs_1.mkdirSync)(LOG_DIR, { recursive: true });
            const date = new Date().toISOString().slice(0, 10);
            (0, node_fs_1.appendFileSync)((0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${date}.log`), line);
        }
        catch {
            /* unwritable destination — preserve primary failure */
        }
    }
    try {
        process.stderr.write(line);
    }
    catch {
        /* stderr closed — nothing else to do */
    }
}
if (!ENTRY) {
    syncEmitLine(`[mcp-spawn-tee-error] server=${SERVER_NAME} reason="no entry given (argv[2] missing)"\n`);
    process.exit(2);
}
const child = (0, node_child_process_1.spawn)(process.execPath, [ENTRY], {
    stdio: ["inherit", "inherit", "pipe"],
    env: process.env,
});
syncEmitLine(`[mcp-spawn-tee-attached] server=${SERVER_NAME} pid=${child.pid ?? -1} entry=${ENTRY}\n`);
child.on("error", (err) => {
    const msg = err instanceof Error ? err.message : String(err);
    syncEmitLine(`[mcp-spawn-tee-error] server=${SERVER_NAME} reason=${JSON.stringify(`spawn error: ${msg}`)}\n`);
    process.exit(127);
});
if (child.stderr) {
    child.stderr.on("data", (chunk) => {
        if (LOG_DIR) {
            try {
                const date = new Date().toISOString().slice(0, 10);
                (0, node_fs_1.appendFileSync)((0, node_path_1.resolve)(LOG_DIR, `mcp-${SERVER_NAME}-stderr-${date}.log`), chunk);
            }
            catch {
                /* unwritable destination — preserve passthrough */
            }
        }
        try {
            process.stderr.write(chunk);
        }
        catch {
            /* stderr closed — nothing else to do */
        }
    });
}
// Forward SIGTERM/SIGINT so Claude Code shutdown doesn't orphan the child.
const forward = (signal) => {
    if (!child.killed)
        child.kill(signal);
};
process.on("SIGTERM", () => forward("SIGTERM"));
process.on("SIGINT", () => forward("SIGINT"));
child.on("exit", (code, signal) => {
    if (signal) {
        syncEmitLine(`[mcp-spawn-tee-exit] server=${SERVER_NAME} pid=${child.pid ?? -1} signal=${signal}\n`);
        process.exit(128 + (code ?? 0));
    }
    if (code !== 0) {
        syncEmitLine(`[mcp-spawn-tee-exit] server=${SERVER_NAME} pid=${child.pid ?? -1} code=${code}\n`);
    }
    process.exit(code ?? 0);
});
//# sourceMappingURL=index.js.map