133 lines
6.1 KiB
Markdown
133 lines
6.1 KiB
Markdown
# 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,是看不到日志的。
|