# ai-mcp — 给大模型操作的 MCP 开关 server 插件 注册一个 `mcp_switch` 工具,让大模型自己切换 MCP 的开/关状态 (配置对调 + `location.reload` 热重载),配合 TUI 插件的 `/mcpon` `/mcpoff` 形成闭环。 分支名:`ai-mcp` | 文件:`ai-mcp.ts` --- ## 一、安装 > **加载机制(已确认)** > > - ✅ **已确认**:插件在 **session 初始化时**才加载,不是进程启动时。 > 只启动 `opencode serve` 而不建 session,**看不到任何插件日志** > (`opencode-btw` 的报错都带 `http.span=` 即为证据)。 > - ✅ **已确认**:OpenCode v2 对 default 导出有 schema 校验,缺则报 > `Plugin must export a default definition with an id and an effect or setup function.`, > 所以 default 导出**必须**给。 > - ✅ **已验证(2026-09-23 实测)**:全局 `~/.config/opencode/plugins/` 下的**一级 > `.ts` 文件会被自动扫描加载**。证据:本机 `plugins` 数组里没有注册 `ai-mcp.ts` > 时,`ai-mcp.log` 依然出现 `module imported` + `插件已加载`。 > 显式 `file:///` 注册仍然推荐(意图清晰、跨版本稳),两份配置都写。 > - ✅ **已确认**:`plugins` 数组里写**不存在的包名**(如已弃用的 `opencode-btw`) > 会让状态栏出现 `N plugins failed` 并在日志刷屏——数组里只留有效条目。 把插件复制到全局插件目录: ``` ~/.config/opencode/plugins/ai-mcp.ts # Linux / WSL C:\Users\<你>\.config\opencode\plugins\ai-mcp.ts # Windows ``` 然后在 `opencode.json` 里注册(**两份配置都要加**,否则 `mcpoff` 对调之后插件会消失): ```jsonc { "plugins": [ "file:///root/.config/opencode/plugins/ai-mcp.ts" // WSL/Linux // Windows 用: "file:///C:/Users/<你>/.config/opencode/plugins/ai-mcp.ts" ] } ``` > **两份配置(含 MCP 版 + 安静版)的 `plugins` 数组必须完全一致**—— > 历史教训:一份注册一份没注册,`mcpoff` 对调后插件就「消失」; > 已弃用的第三方条目(如 `opencode-btw`)会让状态栏报 `N plugins failed`,不要留。 > `plugins` 数组只放 **server 插件**;TUI-only 插件(`{ id, tui }`)放进去会被 > server loader 拒绝,它们的注册位置是 `tui.json` 的 `plugin` 数组。 依赖:插件目录下需有 `package.json` 声明 `@opencode-ai/plugin` 并装好 (OpenCode 启动时会跑 `bun install`;WSL 没有 bun 时手动 `npm install --omit=dev`)。 --- ## 二、工具说明 | 字段 | 值 | | --- | --- | | 工具名 | `mcp_switch` | | 参数 | `action`:`on` / `off` / `status` | | 效果 | 对调两份配置 → 校验活文件 → **先推送 mcp 配置进 OC** → `location.reload` 热重载 | `status` 只读,任何时候都允许;`on` / `off` 按 `mcpcfg.txt`「模式」键门控: | 模式 | 大模型调 `mcp_switch` | 用户 `/mcpon` `/mcpoff` | | --- | --- | --- | | 自动 | ✅ 允许 | ✅(视为手动切换一次) | | 手动 | ❌ 拒绝,提示用户用 `/mcpon` `/mcpoff` | ✅ 仅此可用 | | 关闭 | ❌ 拒绝 | ❌ 不注册 | **开启方向是「推送 + reload」双保险(v3 改为 GET+merge 补 shell)**:对调并写好 `mcp.on.flag` 之后、reload 之前,先把活配置里的 `mcp` 推进**运行中的 OC**: 1. `GET /api/config?directory=` 取运行中配置的 `info.shell`(GET → 活文件 → `null` 三级兜底;`PATCH` body **必含 `shell`**,缺则 400 `Missing key at ["shell"]`, 裸 `PATCH /config` 是 405,必须走 `/api/experimental/config`); 2. 口令链 `service.json` → 环境变量 `OPENCODE_PASSWORD`/`OPENCODE_SERVER_PASSWORD` → `opencode.json server.password`,鉴权 `Basic base64("opencode:<口令>")`; 3. SDK 两跳 `config.update({ shell, mcp })` / `({ body })` → 兜底原生 `fetch PATCH /api/experimental/config?directory=`。 推送成功再 reload,MCP **百分百加载**;推送失败只记日志,自动降级为原 reload 单通道, 不影响后续流程。TUI 插件 `/mcpon`(v10)与 CLI `mcpctl on` 是同一套语义 (GET+merge 补 shell → PATCH 真实路由),TUI 走进程内三跳、CLI 走端口发现+探活。 reload 走三连尝试:`client.location.reload({location})` → `({})` → 无参 → 兜底 `POST /api/location/reload`。全部失败时配置**已经对调完成**(若推送已成功则 OC 内已是新配置),重启 OpenCode 一样生效。 --- ## 三、格式说明(双导出) ```ts // 1) 命名导出 —— V1 legacy 形式(loader 直接调用拿 hooks) export const AiMcp: Plugin = async (ctx) => ({ tool: { mcp_switch: tool({...}) } }) // 2) default 导出 —— V2 校验形式(OpenCode 2.0.x 的硬性要求) export default { id: "ai-mcp", async setup(ctx) { ... } } ``` **为什么两个都要**:OpenCode v2 对 default 导出有 schema 校验,缺失会直接报 ``` Plugin must export a default definition with an id and an effect or setup function. (Missing key at ["default"]) ``` (`opencode-btw` 就因为缺 default 在日志里反复报这条)。命名导出则是 legacy loader 的入口。 两者返回同一套 hooks,无论加载器认哪个,`mcp_switch` 都能注册成功。 `tool` helper 通过**动态 import** 装载:某端没装依赖时只写错误日志, **不会让整个插件文件加载失败**。 --- ## 四、排查 日志:`~/.config/opencode/ai-mcp.log` | 日志内容 | 含义 | | --- | --- | | `module imported (pid=...)` | 文件**被 OpenCode import 了**(模块级留痕,最先出现) | | `ai-mcp v3 server 插件已加载(命名导出,推送走 GET+merge 补 shell)` | 命名导出**被调用了**(legacy loader 入口) | | `ai-mcp v3 server 插件已加载(default/setup,推送走 GET+merge 补 shell)` | default 导出**被调用了**(V2 校验入口) | | 什么都没有 | **没被加载** → 检查 `plugins` 数组是否注册、路径是否写对 | | `无法加载 @opencode-ai/plugin` | 依赖没装,去插件目录 `npm install` | > 注意:插件在 **session 初始化时**才加载,不是进程启动时。 > 只启动 `opencode serve` 不建 session,是看不到日志的。