diff --git a/AGENTS.md b/AGENTS.md index 2479a3e..45eb03c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,10 +15,9 @@ - `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 与工具提示中的内部名称。 - `@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_*` 工具、隔离式大输出处理和会话连续性。 - `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/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`。 diff --git a/README.md b/README.md index 86026a7..55db40d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - `context-mode@1.0.169`:通过 Pi adapter 提供 `ctx_*` 工具、上下文隔离和会话连续性。 - `@tavily/pi-extension@0.1.2`:提供 Tavily 广泛搜索与页面提取;通过本地 override 暴露为 `tavily_web_search` / `tavily_web_fetch`。 - `@keenable/pi-search@0.1.2`:提供紧凑的 `keenable_search` / `keenable_fetch`,并加载其搜索 skill;API 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`:查看上下文占用。 - `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/fast-mode`。 - `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`。 -`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 只负责连接 -`extensions/codegraph.ts` 使用隔离的 `pi-mcp-adapter` 配置启动 +同一个 `extensions/mcp.ts` adapter 配置启动 `codegraph serve --mcp`,不会读取或覆盖用户已有的 MCP 配置。连接采用 `keep-alive`,并只把官方当前主工具 `codegraph_explore` 作为同名 Pi 工具暴露; CodeGraph 的文件 watcher 由该 MCP 进程自身维护。 diff --git a/extensions/codegraph.ts b/extensions/codegraph.ts deleted file mode 100644 index 42a9140..0000000 --- a/extensions/codegraph.ts +++ /dev/null @@ -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, - }, - }, - }, -}); diff --git a/extensions/exa.ts b/extensions/exa.ts deleted file mode 100644 index 1ed1e58..0000000 --- a/extensions/exa.ts +++ /dev/null @@ -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_`. */ -export default function exaExtension(pi: ExtensionAPI): void { - exaAdapter(createExaToolApiOverride(pi)); -} diff --git a/extensions/mcp-config.ts b/extensions/mcp-config.ts new file mode 100644 index 0000000..900f2bf --- /dev/null +++ b/extensions/mcp-config.ts @@ -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, + }, + }, +}; diff --git a/extensions/mcp.ts b/extensions/mcp.ts new file mode 100644 index 0000000..26dc43a --- /dev/null +++ b/extensions/mcp.ts @@ -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)); +} diff --git a/package.json b/package.json index 695e695..d16d26d 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,10 @@ "./extensions/fff-override.ts", "./extensions/tavily-override.ts", "./node_modules/@keenable/pi-search/src/index.ts", - "./extensions/exa.ts", + "./extensions/mcp.ts", "./pi-rtk-optimizer/index.ts", "./extensions/hashline.ts", "./node_modules/context-mode/build/adapters/pi/extension.js", - "./extensions/codegraph.ts", "./extensions/lsp.ts", "./node_modules/pi-context-view/src/index.ts", "./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts", diff --git a/tests/exa-override.test.ts b/tests/exa-override.test.ts index f656037..515efd0 100644 --- a/tests/exa-override.test.ts +++ b/tests/exa-override.test.ts @@ -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.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("codegraph_explore"); assert.equal(registered[0]?.name, "exa_web_fetch"); assert.equal(registered[0]?.label, "Exa Web Fetch"); - assert.deepEqual(active, ["read", "exa_web_fetch"]); - assert.deepEqual(unregistered, ["exa_web_fetch"]); + assert.equal(registered[1]?.name, "codegraph_explore"); + assert.deepEqual(active, ["read", "exa_web_fetch", "codegraph_explore"]); + assert.deepEqual(unregistered, ["exa_web_fetch", "codegraph_explore"]); }); diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts new file mode 100644 index 0000000..789bf27 --- /dev/null +++ b/tests/mcp.test.ts @@ -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"]); +});