mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
codegraph 有一等公民 CLI 且上游保证其输出与 codegraph_explore MCP 工具一致,走共享 MCP adapter 只是多余的传输层。改为原生 Pi 工具后 每次调用以短命 codegraph explore 进程完成,去掉常驻 server/daemon; 未初始化项目经 ctx.ui.confirm 确认后 init 并自动重试,拒绝或失败时 打回并引导模型回退 find/grep/read/LSP;结果按 codegraph.db mtime 做有界缓存,索引一变即失效。共享 MCP adapter 只保留 Exa;工具名 不变,权限策略、Tool Search 常驻表、路由规则与分组不受影响。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
|
|
import { createMcpConfig, EXA_TOOLS } from "../extensions/mcp-config.ts";
|
|
|
|
test("the shared MCP adapter config only connects Exa", () => {
|
|
assert.deepEqual(createMcpConfig({}), {
|
|
settings: {
|
|
disableProxyTool: true,
|
|
scriptMode: false,
|
|
},
|
|
mcpServers: {
|
|
exa: {
|
|
url: `https://mcp.exa.ai/mcp?tools=${EXA_TOOLS.join(",")}`,
|
|
protocolVersion: "auto",
|
|
lifecycle: "eager",
|
|
directTools: EXA_TOOLS,
|
|
includeTools: EXA_TOOLS,
|
|
toolPrefix: "none",
|
|
approveTools: false,
|
|
exposeResources: false,
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test("Exa API key is resolved from the environment into a header and never placed in the URL", () => {
|
|
const config = createMcpConfig({ EXA_API_KEY: "exa-test-key" });
|
|
assert.deepEqual(config.mcpServers.exa.headers, { "x-api-key": "$env:EXA_API_KEY" });
|
|
assert.equal(config.mcpServers.exa.url?.includes("exa-test-key"), false);
|
|
assert.equal(JSON.stringify(config).includes("exa-test-key"), false);
|
|
});
|
|
|
|
test("the package has exactly one local MCP adapter owner", async () => {
|
|
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as {
|
|
pi: { extensions: string[] };
|
|
};
|
|
const localEntries = packageJson.pi.extensions.filter((entry) => !entry.startsWith("./node_modules/"));
|
|
const adapterOwners: string[] = [];
|
|
for (const entry of localEntries) {
|
|
const source = await readFile(new URL(`../${entry.slice(2)}`, import.meta.url), "utf8");
|
|
if (source.includes("createMcpAdapter") || source.includes('from "pi-mcp-adapter"')) {
|
|
adapterOwners.push(entry);
|
|
}
|
|
}
|
|
assert.deepEqual(adapterOwners, ["./extensions/mcp.ts"]);
|
|
});
|
|
|
|
test("search credentials load before all search providers", async () => {
|
|
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as {
|
|
pi: { extensions: string[] };
|
|
};
|
|
const entries = packageJson.pi.extensions;
|
|
const loaderIndex = entries.indexOf("./extensions/search-config.ts");
|
|
for (const provider of [
|
|
"./extensions/tavily-override.ts",
|
|
"./node_modules/@keenable/pi-search/src/index.ts",
|
|
"./extensions/mcp.ts",
|
|
]) {
|
|
assert.ok(loaderIndex >= 0 && loaderIndex < entries.indexOf(provider), `${provider} must load after search config`);
|
|
}
|
|
});
|