#!/usr/bin/env node /** * Fake `codegraph serve --mcp`: newline-delimited JSON-RPC 2.0 on stdio, for * tests of extensions/codegraph-client.ts. * * Env: * FAKE_CG_MODE "ok" (default) | "no-tool" | "crash-first" | "crash" | "hang" * no-tool: tools/list returns no codegraph_explore * crash-first: on the first tools/call, write FAKE_CG_MARKER * and exit(1) — a second spawned process sees * the marker and serves normally * crash: exit(1) on every tools/call * hang: never reply to tools/call * FAKE_CG_SCHEMA JSON array of inputSchema property names * (default ["query","maxFiles","projectPath"]) * FAKE_CG_MARKER marker file path for crash-first * FAKE_CG_LOG file to append every inbound message as a JSON line */ import fs from "node:fs"; import readline from "node:readline"; const mode = process.env.FAKE_CG_MODE || "ok"; const marker = process.env.FAKE_CG_MARKER; const logFile = process.env.FAKE_CG_LOG; const schemaProps = (() => { try { return JSON.parse(process.env.FAKE_CG_SCHEMA || ""); } catch { return ["query", "maxFiles", "projectPath"]; } })(); function log(entry) { if (logFile) fs.appendFileSync(logFile, JSON.stringify(entry) + "\n"); } function send(msg) { process.stdout.write(JSON.stringify(msg) + "\n"); } const rl = readline.createInterface({ input: process.stdin }); rl.on("line", (line) => { const text = line.trim(); if (!text) return; let msg; try { msg = JSON.parse(text); } catch { return; } if (!msg || typeof msg !== "object") return; log(msg); if (typeof msg.method !== "string") return; switch (msg.method) { case "initialize": send({ jsonrpc: "2.0", id: msg.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "fake-codegraph", version: "0.0.0" }, }, }); return; case "tools/list": { const tools = mode === "no-tool" ? [] : [ { name: "codegraph_explore", description: "fake explore", inputSchema: { type: "object", properties: Object.fromEntries( schemaProps.map((name) => [name, { type: "string" }]), ), required: ["query"], }, }, ]; send({ jsonrpc: "2.0", id: msg.id, result: { tools } }); return; } case "tools/call": { const shouldCrash = mode === "crash" || (mode === "crash-first" && marker && !fs.existsSync(marker)); if (shouldCrash) { if (mode === "crash-first") fs.writeFileSync(marker, "crashed"); // Synchronous write — process.exit() can truncate pending pipe output. fs.writeSync(2, "fatal: codegraph index corrupted\n"); process.exit(1); } if (mode === "hang") return; // Never reply — timeout/abort test. const args = (msg.params && msg.params.arguments) || {}; send({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: `explore:${JSON.stringify(args)}` }], }, }); return; } default: // Notifications (initialized, cancelled) need no reply; unknown // requests get -32601. if (msg.id !== undefined) { send({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "Method not found" }, }); } } });