fix: route Exa and CodeGraph through shared mcp adapter

This commit is contained in:
云服务部-叶林立
2026-08-19 12:10:24 +08:00
parent a0a4ae44d4
commit 6293ef1c63
9 changed files with 104 additions and 65 deletions
+1 -2
View File
@@ -15,10 +15,9 @@
- `extensions/fff-override.ts`:强制使用 FFF 官方 `override` 模式,统一接管 `find``grep``multi_grep` 和 FFF 的 `@` 补全;显式 CLI flag 仍遵循 FFF 官方优先级。 - `extensions/fff-override.ts`:强制使用 FFF 官方 `override` 模式,统一接管 `find``grep``multi_grep` 和 FFF 的 `@` 补全;显式 CLI flag 仍遵循 FFF 官方优先级。
- `extensions/tavily-override.ts`:加载 `@tavily/pi-extension`,把上游工具重命名为 `tavily_web_search` / `tavily_web_fetch`,并同步改写 label 与工具提示中的内部名称。 - `extensions/tavily-override.ts`:加载 `@tavily/pi-extension`,把上游工具重命名为 `tavily_web_search` / `tavily_web_fetch`,并同步改写 label 与工具提示中的内部名称。
- `@keenable/pi-search`:加载 `keenable_search` / `keenable_fetch` 及其 skill,默认 keyless`KEENABLE_API_KEY` 仅用于提高速率限制。 - `@keenable/pi-search`:加载 `keenable_search` / `keenable_fetch` 及其 skill,默认 keyless`KEENABLE_API_KEY` 仅用于提高速率限制。
- `extensions/exa.ts`:通过隔离的 `pi-mcp-adapter` 配置连接 Exa 托管 MCP,把原始工具映射为统一的来源前缀形式 `exa_web_search``exa_web_fetch``exa_web_search_advanced` - `extensions/mcp.ts`:通过单个共享 `pi-mcp-adapter` 实例同时连接 Exa 托管 MCP 与机器现有的 `codegraph serve --mcp`,避免重复注册 Pi 的全局 MCP flag 与命令;Exa 原始工具映射为统一的来源前缀形式 `exa_web_search``exa_web_fetch``exa_web_search_advanced`CodeGraph 只暴露 `codegraph_explore`
- `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。 - `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。
- `extensions/hashline.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`,以行哈希锚点覆盖内置 `read` / `edit`Hashline 自带 `grep` 强制关闭。 - `extensions/hashline.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`,以行哈希锚点覆盖内置 `read` / `edit`Hashline 自带 `grep` 强制关闭。
- `extensions/codegraph.ts`:通过隔离的 `pi-mcp-adapter` 配置连接机器现有的 `codegraph serve --mcp`,只暴露 `codegraph_explore`
- `extensions/permission-system.ts`:在权限扩展注册前,将 `config/pi-permission-system.json` 同步为全局权威配置。 - `extensions/permission-system.ts`:在权限扩展注册前,将 `config/pi-permission-system.json` 同步为全局权威配置。
- `extensions/lsp.ts`:把组合包内 TypeScript Language Server CLI 物化为绝对命令路径,部署 TypeScript/Kotlin/JDT LS 全局配置并加载 `pi-lsp` - `extensions/lsp.ts`:把组合包内 TypeScript Language Server CLI 物化为绝对命令路径,部署 TypeScript/Kotlin/JDT LS 全局配置并加载 `pi-lsp`
- `extensions/tool-routing.ts`:保留 Pi 默认系统提示词,在每轮开始前按激活工具追加简短路由规则,并提供 `/dump-system-prompt` 将扩展所见的有效提示词写入 `.pi-debug/effective-system-prompt.md` - `extensions/tool-routing.ts`:保留 Pi 默认系统提示词,在每轮开始前按激活工具追加简短路由规则,并提供 `/dump-system-prompt` 将扩展所见的有效提示词写入 `.pi-debug/effective-system-prompt.md`
+3 -3
View File
@@ -7,7 +7,7 @@
- `context-mode@1.0.169`:通过 Pi adapter 提供 `ctx_*` 工具、上下文隔离和会话连续性。 - `context-mode@1.0.169`:通过 Pi adapter 提供 `ctx_*` 工具、上下文隔离和会话连续性。
- `@tavily/pi-extension@0.1.2`:提供 Tavily 广泛搜索与页面提取;通过本地 override 暴露为 `tavily_web_search` / `tavily_web_fetch` - `@tavily/pi-extension@0.1.2`:提供 Tavily 广泛搜索与页面提取;通过本地 override 暴露为 `tavily_web_search` / `tavily_web_fetch`
- `@keenable/pi-search@0.1.2`:提供紧凑的 `keenable_search` / `keenable_fetch`,并加载其搜索 skillAPI key 可选。 - `@keenable/pi-search@0.1.2`:提供紧凑的 `keenable_search` / `keenable_fetch`,并加载其搜索 skillAPI key 可选。
- `pi-mcp-adapter@2.26.0`分别用隔离配置接入机器现有的 CodeGraph 和 Exa 托管 MCP。 - `pi-mcp-adapter@2.26.0`通过单个共享 adapter 实例接入机器现有的 CodeGraph 和 Exa 托管 MCP,避免重复注册 Pi 的全局 MCP flag 与命令
- `pi-context-view@0.4.2`:查看上下文占用。 - `pi-context-view@0.4.2`:查看上下文占用。
- `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/fast-mode` - `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/fast-mode`
- `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。 - `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。
@@ -159,11 +159,11 @@ Tavily 通过 `extensions/tavily-override.ts` 加载官方扩展。override 只
Keenable 直接加载 npm 包的扩展和 skill,默认无需 key;设置 `KEENABLE_API_KEY` 可提高速率限制。它适合中文内容、`site` 限定和发布日期筛选。搜索结果保持紧凑,只对选中的 URL 调用 `keenable_fetch` Keenable 直接加载 npm 包的扩展和 skill,默认无需 key;设置 `KEENABLE_API_KEY` 可提高速率限制。它适合中文内容、`site` 限定和发布日期筛选。搜索结果保持紧凑,只对选中的 URL 调用 `keenable_fetch`
`extensions/exa.ts` 通过已有的 `pi-mcp-adapter` 连接 `https://mcp.exa.ai/mcp`,把 MCP 原始工具映射为统一的来源前缀形式:`exa_web_search``exa_web_fetch``exa_web_search_advanced`。Exa 适合官方文档、技术资料、版本信息和论文;高级搜索必须显式限制结果数量与返回文本长度,避免响应过大。 `extensions/mcp.ts` 通过单个 `pi-mcp-adapter` 实例同时连接 Exa 与 CodeGraph,避免两个 adapter 重复注册 `--mcp-config``/mcp``/mcp-auth`。其中 Exa 连接 `https://mcp.exa.ai/mcp`把 MCP 原始工具映射为统一的来源前缀形式:`exa_web_search``exa_web_fetch``exa_web_search_advanced`。Exa 适合官方文档、技术资料、版本信息和论文;高级搜索必须显式限制结果数量与返回文本长度,避免响应过大。
### CodeGraph 只负责连接 ### CodeGraph 只负责连接
`extensions/codegraph.ts` 使用隔离的 `pi-mcp-adapter` 配置启动 同一个 `extensions/mcp.ts` adapter 配置启动
`codegraph serve --mcp`,不会读取或覆盖用户已有的 MCP 配置。连接采用 `codegraph serve --mcp`,不会读取或覆盖用户已有的 MCP 配置。连接采用
`keep-alive`,并只把官方当前主工具 `codegraph_explore` 作为同名 Pi 工具暴露; `keep-alive`,并只把官方当前主工具 `codegraph_explore` 作为同名 Pi 工具暴露;
CodeGraph 的文件 watcher 由该 MCP 进程自身维护。 CodeGraph 的文件 watcher 由该 MCP 进程自身维护。
-23
View File
@@ -1,23 +0,0 @@
import { createMcpAdapter } from "pi-mcp-adapter";
/** Connect Pi to the CodeGraph MCP server provided by the host machine. */
export default createMcpAdapter({
config: {
settings: {
disableProxyTool: true,
scriptMode: false,
},
mcpServers: {
codegraph: {
command: "codegraph",
args: ["serve", "--mcp"],
lifecycle: "keep-alive",
directTools: ["codegraph_explore"],
includeTools: ["codegraph_explore"],
toolPrefix: "none",
approveTools: false,
exposeResources: false,
},
},
},
});
-32
View File
@@ -1,32 +0,0 @@
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { createMcpAdapter } from "pi-mcp-adapter";
import { createExaToolApiOverride } from "./exa-tool-prefix.ts";
const EXA_TOOLS = ["web_search_exa", "web_fetch_exa", "web_search_advanced_exa"];
const EXA_MCP_URL = `https://mcp.exa.ai/mcp?tools=${EXA_TOOLS.join(",")}`;
const exaAdapter = createMcpAdapter({
config: {
settings: {
disableProxyTool: true,
scriptMode: false,
},
mcpServers: {
exa: {
url: EXA_MCP_URL,
protocolVersion: "auto",
lifecycle: "eager",
directTools: EXA_TOOLS,
includeTools: EXA_TOOLS,
toolPrefix: "none",
approveTools: false,
exposeResources: false,
},
},
},
});
/** Connect Pi to Exa while exposing every direct tool as `exa_<original-name>`. */
export default function exaExtension(pi: ExtensionAPI): void {
exaAdapter(createExaToolApiOverride(pi));
}
+33
View File
@@ -0,0 +1,33 @@
import type { McpConfig } from "pi-mcp-adapter";
export const EXA_TOOLS = ["web_search_exa", "web_fetch_exa", "web_search_advanced_exa"];
const EXA_MCP_URL = `https://mcp.exa.ai/mcp?tools=${EXA_TOOLS.join(",")}`;
export const MCP_CONFIG: McpConfig = {
settings: {
disableProxyTool: true,
scriptMode: false,
},
mcpServers: {
exa: {
url: EXA_MCP_URL,
protocolVersion: "auto",
lifecycle: "eager",
directTools: EXA_TOOLS,
includeTools: EXA_TOOLS,
toolPrefix: "none",
approveTools: false,
exposeResources: false,
},
codegraph: {
command: "codegraph",
args: ["serve", "--mcp"],
lifecycle: "keep-alive",
directTools: ["codegraph_explore"],
includeTools: ["codegraph_explore"],
toolPrefix: "none",
approveTools: false,
exposeResources: false,
},
},
};
+13
View File
@@ -0,0 +1,13 @@
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { createMcpAdapter } from "pi-mcp-adapter";
import { createExaToolApiOverride } from "./exa-tool-prefix.ts";
import { MCP_CONFIG } from "./mcp-config.ts";
const mcpAdapter = createMcpAdapter({
config: MCP_CONFIG,
});
/** Connect Pi to Exa and CodeGraph through one shared MCP adapter instance. */
export default function mcpExtension(pi: ExtensionAPI): void {
mcpAdapter(createExaToolApiOverride(pi));
}
+1 -2
View File
@@ -25,11 +25,10 @@
"./extensions/fff-override.ts", "./extensions/fff-override.ts",
"./extensions/tavily-override.ts", "./extensions/tavily-override.ts",
"./node_modules/@keenable/pi-search/src/index.ts", "./node_modules/@keenable/pi-search/src/index.ts",
"./extensions/exa.ts", "./extensions/mcp.ts",
"./pi-rtk-optimizer/index.ts", "./pi-rtk-optimizer/index.ts",
"./extensions/hashline.ts", "./extensions/hashline.ts",
"./node_modules/context-mode/build/adapters/pi/extension.js", "./node_modules/context-mode/build/adapters/pi/extension.js",
"./extensions/codegraph.ts",
"./extensions/lsp.ts", "./extensions/lsp.ts",
"./node_modules/pi-context-view/src/index.ts", "./node_modules/pi-context-view/src/index.ts",
"./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts", "./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts",
+6 -3
View File
@@ -32,11 +32,14 @@ test("Exa API override keeps registered and active tool names aligned", () => {
}); });
api.registerTool({ name: "web_fetch_exa", label: "MCP: web_fetch_exa" }); api.registerTool({ name: "web_fetch_exa", label: "MCP: web_fetch_exa" });
api.setActiveTools(["read", "web_fetch_exa", "web_fetch_exa"]); api.registerTool({ name: "codegraph_explore", label: "MCP: codegraph_explore" });
api.setActiveTools(["read", "web_fetch_exa", "web_fetch_exa", "codegraph_explore"]);
api.unregisterTool("web_fetch_exa"); api.unregisterTool("web_fetch_exa");
api.unregisterTool("codegraph_explore");
assert.equal(registered[0]?.name, "exa_web_fetch"); assert.equal(registered[0]?.name, "exa_web_fetch");
assert.equal(registered[0]?.label, "Exa Web Fetch"); assert.equal(registered[0]?.label, "Exa Web Fetch");
assert.deepEqual(active, ["read", "exa_web_fetch"]); assert.equal(registered[1]?.name, "codegraph_explore");
assert.deepEqual(unregistered, ["exa_web_fetch"]); assert.deepEqual(active, ["read", "exa_web_fetch", "codegraph_explore"]);
assert.deepEqual(unregistered, ["exa_web_fetch", "codegraph_explore"]);
}); });
+47
View File
@@ -0,0 +1,47 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { EXA_TOOLS, MCP_CONFIG } from "../extensions/mcp-config.ts";
test("one MCP adapter config contains both Exa and CodeGraph", () => {
assert.deepEqual(MCP_CONFIG, {
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,
},
codegraph: {
command: "codegraph",
args: ["serve", "--mcp"],
lifecycle: "keep-alive",
directTools: ["codegraph_explore"],
includeTools: ["codegraph_explore"],
toolPrefix: "none",
approveTools: false,
exposeResources: false,
},
},
});
});
test("the package loads only the shared MCP extension entry", async () => {
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as {
pi: { extensions: string[] };
};
const localMcpEntries = packageJson.pi.extensions.filter((entry) =>
entry === "./extensions/mcp.ts" || entry === "./extensions/exa.ts" || entry === "./extensions/codegraph.ts"
);
assert.deepEqual(localMcpEntries, ["./extensions/mcp.ts"]);
});