Files
my-pi/tests/fixtures/fake-codegraph-mcp.mjs
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> dc4fe92e27 feat(codegraph): codegraph_explore 改为自有 stdio JSON-RPC 客户端直连 codegraph serve
一次性 `codegraph explore` CLI 不会同步索引,新增符号在手动 sync 前
无法被搜到。改为按索引根惰性启动常驻 `codegraph serve --mcp` 子进程,
由组合包自有的逐行 JSON-RPC 客户端(extensions/codegraph-client.ts,
不依赖 MCP adapter/SDK)收发,serve 内置文件监听自动同步索引,删除
mtime 结果缓存。子进程闲置 10 分钟或会话结束关闭,子进程死亡时透明
重试一次;用户拒绝初始化在会话内记住;用户中止调用不关闭子进程;
所有失败以文本打回引导改用 find/grep/read/LSP。新增模拟服务与真实
CLI 集成测试。

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-20 02:02:47 -07:00

121 lines
3.3 KiB
JavaScript

#!/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" },
});
}
}
});