callLive

tcmts.cli.mcp.callLive · #w3bdgz5b · term · runs on the server

uses node:child_process

A tool call answered by the codebase's own tcm, as it is now (live): a server of its own (tcm run --bin tcm -- mcp, every command of it run the same way) is asked it, so the whole tool is the new one, what it says included, not only the commands it runs. bin runs this tcm; env adds to the server's environment (its session's branch); onLine hears the output of what it runs as it comes (its progress notifications).

import { liveArgv, plain } from "#codebase/tcmts/cli/mcp";
import type { Result } from "#codebase/tcmts/cli/mcp";
import { spawn } from "node:child_process";

export function callLive(
  bin: string[],
  root: string,
  name: string,
  args: Record<string, unknown>,
  env: Record<string, string> = {},
  onLine?: (line: string) => void,
): Promise<Result> {
  return new Promise((resolve) => {
    const child = spawn(bin[0]!, [...bin.slice(1), ...liveArgv(["mcp"])], {
      cwd: root,
      env: { ...process.env, ...env, TCM_MCP_SERVER: "1", TCM_MCP_LIVE: "1", NO_COLOR: "1" },
      stdio: ["pipe", "pipe", "pipe"],
    });
    let buf = "";
    let err = "";
    child.stderr.setEncoding("utf8").on("data", (d: string) => (err += d));
    child.stdout.setEncoding("utf8").on("data", (d: string) => {
      buf += d;
      for (let nl = buf.indexOf("\n"); nl >= 0; nl = buf.indexOf("\n")) {
        const line = buf.slice(0, nl);
        buf = buf.slice(nl + 1);
        let m: {
          id?: number;
          method?: string;
          params?: { message?: string };
          result?: { content?: { text?: string }[]; isError?: boolean };
          error?: { message?: string };
        };
        try {
          m = JSON.parse(line) as typeof m;
        } catch {
          continue;
        }
        if (m.method === "notifications/progress" && m.params?.message !== undefined)
          onLine?.(m.params.message);
        if (m.id !== 2) continue;
        child.stdin.end();
        resolve(
          m.result
            ? { text: m.result.content?.[0]?.text ?? "", isError: !!m.result.isError }
            : { text: m.error?.message ?? "the codebase's own tcm couldn't answer", isError: true },
        );
      }
    });
    child.on("close", (code) =>
      resolve({
        text: `the codebase's own tcm (\`tcm run --bin tcm -- mcp\`) ended (exit ${code}) without answering:\n${plain(err).trim()}`,
        isError: true,
      }),
    );
    const send = (msg: unknown) => child.stdin.write(`${JSON.stringify(msg)}\n`);
    send({
      jsonrpc: "2.0",
      id: 1,
      method: "initialize",
      params: {
        protocolVersion: "2025-06-18",
        capabilities: {},
        clientInfo: { name: "tcm live", version: "0" },
      },
    });
    send({
      jsonrpc: "2.0",
      id: 2,
      method: "tools/call",
      params: { name, arguments: args, ...(onLine ? { _meta: { progressToken: "live" } } : {}) },
    });
  });
}

uses

used by