@harmonyos-arkts/opencode-acp 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +100 -42
- package/README.zh.md +36 -37
- package/dist/index.cjs +464 -187
- package/dist/index.cjs.map +4 -4
- package/docs/codebase-overview.md +49 -49
- package/docs/mcp-extmethod-design.md +366 -0
- package/docs/provider-config-flow.md +312 -0
- package/docs/question-asked.md +35 -18
- package/docs/session-stats-to-vscode-design.md +217 -0
- package/docs/subagent-visibility.md +26 -25
- package/docs/tui-vs-acp-analysis.md +36 -36
- package/package.json +4 -1
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
|
|
40
40
|
#### 内部辅助方法
|
|
41
41
|
|
|
42
|
-
| 方法
|
|
43
|
-
|
|
44
|
-
| `defaultModel()`
|
|
45
|
-
| `loadSessionMode()`
|
|
46
|
-
| `processMessage()`
|
|
47
|
-
| `replayToolPart()`
|
|
48
|
-
| `convertPromptParts()` | ACP prompt 格式 → OpenCode parts 格式
|
|
49
|
-
| `parseUri()`
|
|
50
|
-
| `logMessageContent()`
|
|
51
|
-
| `buildUsage()`
|
|
42
|
+
| 方法 | 职责 |
|
|
43
|
+
| ---------------------- | --------------------------------------------------------------------------------- |
|
|
44
|
+
| `defaultModel()` | 多级模型发现:配置 → opencode config → providers → fallback `opencode/big-pickle` |
|
|
45
|
+
| `loadSessionMode()` | 加载可用模型列表,注册 MCP 服务器,推送 `available_commands_update` |
|
|
46
|
+
| `processMessage()` | 消息回放(文本、工具调用、文件、推理等) |
|
|
47
|
+
| `replayToolPart()` | 工具调用回放(completed/error 状态) |
|
|
48
|
+
| `convertPromptParts()` | ACP prompt 格式 → OpenCode parts 格式 |
|
|
49
|
+
| `parseUri()` | 解析 `file://` 和 `zed://` URI |
|
|
50
|
+
| `logMessageContent()` | prompt 完成后抓取完整消息内容记录日志 |
|
|
51
|
+
| `buildUsage()` | 从 `AssistantMessage.tokens` 构建 ACP Usage 对象 |
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -63,16 +63,16 @@
|
|
|
63
63
|
|
|
64
64
|
#### 关键方法
|
|
65
65
|
|
|
66
|
-
| 方法
|
|
67
|
-
|
|
68
|
-
| `create()` / `load()`
|
|
69
|
-
| `registerDiscovered()`
|
|
70
|
-
| `findRootSession()`
|
|
71
|
-
| `getRelatedSessions()`
|
|
72
|
-
| `getChildren()`
|
|
73
|
-
| `getModel/setModel`
|
|
74
|
-
| `getVariant/setVariant` | 会话变体管理
|
|
75
|
-
| `getMode/setMode`
|
|
66
|
+
| 方法 | 职责 |
|
|
67
|
+
| ----------------------- | ---------------------------------------------------------------------------- |
|
|
68
|
+
| `create()` / `load()` | 注册顶层会话(由 agent 调用) |
|
|
69
|
+
| `registerDiscovered()` | **核心增强**:通过 SSE 事件自动注册子会话,继承父会话的 cwd/mcpServers/model |
|
|
70
|
+
| `findRootSession()` | 向上遍历 parentID 链找到根会话 |
|
|
71
|
+
| `getRelatedSessions()` | 递归获取子树所有会话 |
|
|
72
|
+
| `getChildren()` | 获取直接子会话 ID 列表 |
|
|
73
|
+
| `getModel/setModel` | 会话模型状态管理 |
|
|
74
|
+
| `getVariant/setVariant` | 会话变体管理 |
|
|
75
|
+
| `getMode/setMode` | 会话模式管理 |
|
|
76
76
|
|
|
77
77
|
---
|
|
78
78
|
|
|
@@ -82,23 +82,23 @@
|
|
|
82
82
|
|
|
83
83
|
#### 核心状态
|
|
84
84
|
|
|
85
|
-
| 字段
|
|
86
|
-
|
|
87
|
-
| `messageBuffers`
|
|
88
|
-
| `bashSnapshots`
|
|
89
|
-
| `toolStarts`
|
|
90
|
-
| `permissionQueues` | 按 sessionID 串行化权限请求
|
|
91
|
-
| `questionQueues`
|
|
85
|
+
| 字段 | 用途 |
|
|
86
|
+
| ------------------ | --------------------------------------------- |
|
|
87
|
+
| `messageBuffers` | 累积 text/thought chunk,边界时刷新为完整日志 |
|
|
88
|
+
| `bashSnapshots` | bash 输出去重(用简单哈希避免重复推送) |
|
|
89
|
+
| `toolStarts` | 跟踪已发送 `tool_call` 开始事件的 callID |
|
|
90
|
+
| `permissionQueues` | 按 sessionID 串行化权限请求 |
|
|
91
|
+
| `questionQueues` | 按 sessionID 串行化问题请求 |
|
|
92
92
|
|
|
93
93
|
#### 事件处理
|
|
94
94
|
|
|
95
|
-
| 事件类型
|
|
96
|
-
|
|
95
|
+
| 事件类型 | 处理逻辑 |
|
|
96
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
97
97
|
| `session.created` / `session.updated` | 自动发现子会话,通过 `announceChildSession()` 向客户端广播虚拟会话(带 `_meta.parentSessionId`) |
|
|
98
|
-
| `permission.asked`
|
|
99
|
-
| `question.asked`
|
|
100
|
-
| `message.part.delta`
|
|
101
|
-
| `message.part.updated`
|
|
98
|
+
| `permission.asked` | 转发给 ACP 客户端请求权限,支持 edit 的 diff 应用(用 `diff` 库的 `applyPatch`) |
|
|
99
|
+
| `question.asked` | 通过 `extMethod("questionAsked")` 转发给客户端,60 秒超时 |
|
|
100
|
+
| `message.part.delta` | 实时推送 `agent_message_chunk` / `agent_thought_chunk`,同时累积到 buffer |
|
|
101
|
+
| `message.part.updated` | 处理工具调用状态转换:pending → running → completed/error |
|
|
102
102
|
|
|
103
103
|
#### 消息缓冲机制
|
|
104
104
|
|
|
@@ -121,13 +121,13 @@ JSONL 格式日志系统,写入 `~/.harmony-acp/logs/`,每次启动一个文
|
|
|
121
121
|
|
|
122
122
|
#### 5 个分类 API
|
|
123
123
|
|
|
124
|
-
| 函数
|
|
125
|
-
|
|
126
|
-
| `acpIn()`
|
|
127
|
-
| `acpOut()`
|
|
128
|
-
| `ocCall()`
|
|
129
|
-
| `ocEvent()` | `oc.event` | OpenCode 的事件(opencode → harmony-acp)
|
|
130
|
-
| `sysLog()`
|
|
124
|
+
| 函数 | 分类 | 用途 |
|
|
125
|
+
| ----------- | ---------- | ---------------------------------------------- |
|
|
126
|
+
| `acpIn()` | `acp.in` | 客户端请求(editor → harmony-acp) |
|
|
127
|
+
| `acpOut()` | `acp.out` | 返回客户端的响应(harmony-acp → editor) |
|
|
128
|
+
| `ocCall()` | `oc.call` | 调用 OpenCode 的请求(harmony-acp → opencode) |
|
|
129
|
+
| `ocEvent()` | `oc.event` | OpenCode 的事件(opencode → harmony-acp) |
|
|
130
|
+
| `sysLog()` | `system` | 生命周期事件 |
|
|
131
131
|
|
|
132
132
|
#### 特性
|
|
133
133
|
|
|
@@ -142,12 +142,12 @@ JSONL 格式日志系统,写入 `~/.harmony-acp/logs/`,每次启动一个文
|
|
|
142
142
|
|
|
143
143
|
定义核心接口:
|
|
144
144
|
|
|
145
|
-
| 接口
|
|
146
|
-
|
|
145
|
+
| 接口 | 用途 |
|
|
146
|
+
| -------------- | ------------------------------------------------- |
|
|
147
147
|
| `SessionState` | 增强会话状态(含 parentID、title、model、modeId) |
|
|
148
|
-
| `ACPConfig`
|
|
149
|
-
| `ModeOption`
|
|
150
|
-
| `ModelOption`
|
|
148
|
+
| `ACPConfig` | 服务器配置(sdk、serverUrl、cwd、defaultModel) |
|
|
149
|
+
| `ModeOption` | 会话模式选项 |
|
|
150
|
+
| `ModelOption` | 模型选项 |
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
@@ -155,10 +155,10 @@ JSONL 格式日志系统,写入 `~/.harmony-acp/logs/`,每次启动一个文
|
|
|
155
155
|
|
|
156
156
|
两个纯函数:
|
|
157
157
|
|
|
158
|
-
| 函数
|
|
159
|
-
|
|
160
|
-
| `toToolKind()`
|
|
161
|
-
| `toLocations()` | 从工具输入元数据中提取文件路径位置信息
|
|
158
|
+
| 函数 | 用途 |
|
|
159
|
+
| --------------- | ----------------------------------------------------------------------------------------- |
|
|
160
|
+
| `toToolKind()` | OpenCode 工具名 → ACP ToolKind 映射(bash→execute, edit/write→edit, grep/glob→search 等) |
|
|
161
|
+
| `toLocations()` | 从工具输入元数据中提取文件路径位置信息 |
|
|
162
162
|
|
|
163
163
|
---
|
|
164
164
|
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# MCP API 暴露方案:通过 ACP extMethod
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
当前 harmony-acp 在 session 创建时通过 `sdk.mcp.add()` 注册 MCP 服务器,但 ACP client 无法在运行时动态查看/管理 MCP 服务器。需要将 OpenCode 服务端的 MCP API 通过 ACP 协议的 extMethod 机制暴露给 ACP client,使其可以动态查看状态、添加/删除/连接/断开 MCP 服务器。
|
|
6
|
+
|
|
7
|
+
## 方案:ACP extMethod
|
|
8
|
+
|
|
9
|
+
利用 ACP 协议已有的 `extMethod` 机制(当前已用于 `questionAsked`),定义 MCP 相关的 extension method。ACP client 通过 JSON-RPC 调用,harmony-acp 代理到 OpenCode SDK。
|
|
10
|
+
|
|
11
|
+
### 架构
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
ACP Client (Editor) ← stdio/JSON-RPC → Harmony-ACP ← HTTP/SSE → OpenCode Server
|
|
15
|
+
│ │ │
|
|
16
|
+
│ extMethod("mcp/status") │ │
|
|
17
|
+
│──────────────────────────────────>│ │
|
|
18
|
+
│ │ sdk.mcp.status() │
|
|
19
|
+
│ │─────────────────────────>│
|
|
20
|
+
│ │ Record<string, Status> │
|
|
21
|
+
│ │<─────────────────────────│
|
|
22
|
+
│ { status: {...} } │ │
|
|
23
|
+
│<──────────────────────────────────│ │
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 暴露的 MCP 操作
|
|
29
|
+
|
|
30
|
+
| extMethod 名 | 对应 SDK 调用 | 说明 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `mcp/status` | `sdk.mcp.status()` | 获取所有 MCP 服务器状态 |
|
|
33
|
+
| `mcp/add` | `sdk.mcp.add()` | 添加 MCP 服务器 |
|
|
34
|
+
| `mcp/connect` | `sdk.mcp.connect()` | 连接已禁用的服务器 |
|
|
35
|
+
| `mcp/disconnect` | `sdk.mcp.disconnect()` | 断开服务器 |
|
|
36
|
+
| `mcp/auth/start` | `sdk.mcp.auth.start()` | 启动 OAuth 认证 |
|
|
37
|
+
| `mcp/auth/callback` | `sdk.mcp.auth.callback()` | 完成 OAuth 认证 |
|
|
38
|
+
| `mcp/auth/remove` | `sdk.mcp.auth.remove()` | 删除 OAuth 凭据 |
|
|
39
|
+
|
|
40
|
+
### 各操作详细签名
|
|
41
|
+
|
|
42
|
+
#### `mcp/status`
|
|
43
|
+
|
|
44
|
+
获取所有已配置 MCP 服务器的连接状态。
|
|
45
|
+
|
|
46
|
+
**请求参数:**
|
|
47
|
+
```typescript
|
|
48
|
+
{
|
|
49
|
+
sessionId: string // 用于获取 cwd
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**响应:**
|
|
54
|
+
```typescript
|
|
55
|
+
{
|
|
56
|
+
[serverName: string]: {
|
|
57
|
+
status: "connected" | "disabled" | "failed" | "needs_auth" | "needs_client_registration"
|
|
58
|
+
error?: string // 仅 failed 和 needs_client_registration 状态
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `mcp/add`
|
|
64
|
+
|
|
65
|
+
动态添加 MCP 服务器。
|
|
66
|
+
|
|
67
|
+
**请求参数:**
|
|
68
|
+
```typescript
|
|
69
|
+
{
|
|
70
|
+
sessionId: string
|
|
71
|
+
name: string
|
|
72
|
+
config: {
|
|
73
|
+
// Local 类型
|
|
74
|
+
type: "local"
|
|
75
|
+
command: string[]
|
|
76
|
+
environment?: Record<string, string>
|
|
77
|
+
enabled?: boolean
|
|
78
|
+
timeout?: number
|
|
79
|
+
} | {
|
|
80
|
+
// Remote 类型
|
|
81
|
+
type: "remote"
|
|
82
|
+
url: string
|
|
83
|
+
headers?: Record<string, string>
|
|
84
|
+
enabled?: boolean
|
|
85
|
+
oauth?: { clientId?: string, clientSecret?: string, scope?: string, redirectUri?: string } | false
|
|
86
|
+
timeout?: number
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**响应:**
|
|
92
|
+
```typescript
|
|
93
|
+
{
|
|
94
|
+
[serverName: string]: McpStatus // 添加后所有服务器状态
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `mcp/connect`
|
|
99
|
+
|
|
100
|
+
连接一个已禁用/断开的 MCP 服务器。
|
|
101
|
+
|
|
102
|
+
**请求参数:**
|
|
103
|
+
```typescript
|
|
104
|
+
{
|
|
105
|
+
sessionId: string
|
|
106
|
+
name: string
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**响应:**
|
|
111
|
+
```typescript
|
|
112
|
+
{
|
|
113
|
+
success: true
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `mcp/disconnect`
|
|
118
|
+
|
|
119
|
+
断开一个 MCP 服务器。
|
|
120
|
+
|
|
121
|
+
**请求参数:**
|
|
122
|
+
```typescript
|
|
123
|
+
{
|
|
124
|
+
sessionId: string
|
|
125
|
+
name: string
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**响应:**
|
|
130
|
+
```typescript
|
|
131
|
+
{
|
|
132
|
+
success: true
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `mcp/auth/start`
|
|
137
|
+
|
|
138
|
+
启动 OAuth 认证流程,返回授权 URL。
|
|
139
|
+
|
|
140
|
+
**请求参数:**
|
|
141
|
+
```typescript
|
|
142
|
+
{
|
|
143
|
+
sessionId: string
|
|
144
|
+
name: string
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**响应:**
|
|
149
|
+
```typescript
|
|
150
|
+
{
|
|
151
|
+
authorizationUrl: string
|
|
152
|
+
oauthState: string
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**错误:** 如果服务器不支持 OAuth,返回错误。
|
|
157
|
+
|
|
158
|
+
#### `mcp/auth/callback`
|
|
159
|
+
|
|
160
|
+
用授权码完成 OAuth 认证。
|
|
161
|
+
|
|
162
|
+
**请求参数:**
|
|
163
|
+
```typescript
|
|
164
|
+
{
|
|
165
|
+
sessionId: string
|
|
166
|
+
name: string
|
|
167
|
+
code: string // OAuth 授权码
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**响应:**
|
|
172
|
+
```typescript
|
|
173
|
+
McpStatus // 认证后的服务器状态
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `mcp/auth/remove`
|
|
177
|
+
|
|
178
|
+
删除 MCP 服务器的 OAuth 凭据。
|
|
179
|
+
|
|
180
|
+
**请求参数:**
|
|
181
|
+
```typescript
|
|
182
|
+
{
|
|
183
|
+
sessionId: string
|
|
184
|
+
name: string
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**响应:**
|
|
189
|
+
```typescript
|
|
190
|
+
{
|
|
191
|
+
success: true
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 实现步骤
|
|
198
|
+
|
|
199
|
+
### Step 1: 新建 `src/mcp-manager.ts` — MCP 操作封装
|
|
200
|
+
|
|
201
|
+
创建 MCP 管理模块,封装所有 MCP 相关的 SDK 调用,统一处理错误和日志。
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// src/mcp-manager.ts
|
|
205
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
|
206
|
+
import { ocCall } from "./logger.js"
|
|
207
|
+
|
|
208
|
+
export class McpManager {
|
|
209
|
+
constructor(private sdk: OpencodeClient) {}
|
|
210
|
+
|
|
211
|
+
async status(directory: string) {
|
|
212
|
+
ocCall("mcp.status", { directory })
|
|
213
|
+
const result = await this.sdk.mcp.status({ directory })
|
|
214
|
+
return result.data ?? {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async add(directory: string, name: string, config: any) {
|
|
218
|
+
ocCall("mcp.add", { directory, name })
|
|
219
|
+
const result = await this.sdk.mcp.add({ directory, name, config })
|
|
220
|
+
return result.data ?? {}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async connect(directory: string, name: string) {
|
|
224
|
+
ocCall("mcp.connect", { directory, name })
|
|
225
|
+
await this.sdk.mcp.connect({ directory, name })
|
|
226
|
+
return { success: true }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async disconnect(directory: string, name: string) {
|
|
230
|
+
ocCall("mcp.disconnect", { directory, name })
|
|
231
|
+
await this.sdk.mcp.disconnect({ directory, name })
|
|
232
|
+
return { success: true }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async startAuth(directory: string, name: string) {
|
|
236
|
+
ocCall("mcp.auth.start", { directory, name })
|
|
237
|
+
const result = await this.sdk.mcp.auth.start({ directory, name })
|
|
238
|
+
return result.data
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async callbackAuth(directory: string, name: string, code: string) {
|
|
242
|
+
ocCall("mcp.auth.callback", { directory, name })
|
|
243
|
+
const result = await this.sdk.mcp.auth.callback({ directory, name, code })
|
|
244
|
+
return result.data
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async removeAuth(directory: string, name: string) {
|
|
248
|
+
ocCall("mcp.auth.remove", { directory, name })
|
|
249
|
+
await this.sdk.mcp.auth.remove({ directory, name })
|
|
250
|
+
return { success: true }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Step 2: 修改 `src/agent.ts` — 注册 extMethod 处理器
|
|
256
|
+
|
|
257
|
+
在 Agent 类中添加 `extMethod` 方法(ACP SDK 的 Agent 接口支持可选的 `extMethod?` 回调),路由 MCP 方法到 `McpManager`。
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// agent.ts 中添加
|
|
261
|
+
|
|
262
|
+
import { McpManager } from "./mcp-manager.js"
|
|
263
|
+
|
|
264
|
+
export class Agent implements ACPAgent {
|
|
265
|
+
private mcpManager: McpManager
|
|
266
|
+
|
|
267
|
+
constructor(config: ACPConfig) {
|
|
268
|
+
// ...
|
|
269
|
+
this.mcpManager = new McpManager(config.sdk)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
273
|
+
if (method.startsWith("mcp/")) {
|
|
274
|
+
return this.handleMcpMethod(method, params)
|
|
275
|
+
}
|
|
276
|
+
throw new Error(`Unknown extMethod: ${method}`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private async handleMcpMethod(method: string, params: Record<string, unknown>) {
|
|
280
|
+
const sessionId = params.sessionId as string
|
|
281
|
+
const session = this.sessionManager.get(sessionId)
|
|
282
|
+
const directory = session.cwd
|
|
283
|
+
|
|
284
|
+
switch (method) {
|
|
285
|
+
case "mcp/status":
|
|
286
|
+
return this.mcpManager.status(directory) as Promise<Record<string, unknown>>
|
|
287
|
+
case "mcp/add":
|
|
288
|
+
return this.mcpManager.add(directory, params.name as string, params.config) as Promise<Record<string, unknown>>
|
|
289
|
+
case "mcp/connect":
|
|
290
|
+
return this.mcpManager.connect(directory, params.name as string)
|
|
291
|
+
case "mcp/disconnect":
|
|
292
|
+
return this.mcpManager.disconnect(directory, params.name as string)
|
|
293
|
+
case "mcp/auth/start":
|
|
294
|
+
return this.mcpManager.startAuth(directory, params.name as string) as Promise<Record<string, unknown>>
|
|
295
|
+
case "mcp/auth/callback":
|
|
296
|
+
return this.mcpManager.callbackAuth(directory, params.name as string, params.code as string) as Promise<Record<string, unknown>>
|
|
297
|
+
case "mcp/auth/remove":
|
|
298
|
+
return this.mcpManager.removeAuth(directory, params.name as string)
|
|
299
|
+
default:
|
|
300
|
+
throw new Error(`Unknown MCP method: ${method}`)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Step 3: 迁移现有 MCP 注册逻辑
|
|
307
|
+
|
|
308
|
+
将 `agent.ts:loadSessionMode()` 中 727-757 行的 MCP 注册代码使用 `McpManager.add()` 替代,消除重复代码。
|
|
309
|
+
|
|
310
|
+
### Step 4: 更新 `src/__tests__/helpers/mock-sdk.ts`
|
|
311
|
+
|
|
312
|
+
在 mock SDK 中补全 MCP 相关方法:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
mcp: {
|
|
316
|
+
add: vi.fn().mockResolvedValue({}),
|
|
317
|
+
status: vi.fn().mockResolvedValue({ data: {} }),
|
|
318
|
+
connect: vi.fn().mockResolvedValue({ data: true }),
|
|
319
|
+
disconnect: vi.fn().mockResolvedValue({ data: true }),
|
|
320
|
+
auth: {
|
|
321
|
+
start: vi.fn().mockResolvedValue({ data: { authorizationUrl: "", oauthState: "" } }),
|
|
322
|
+
callback: vi.fn().mockResolvedValue({ data: { status: "connected" } }),
|
|
323
|
+
remove: vi.fn().mockResolvedValue({ data: { success: true } }),
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Step 5: 新建 `src/__tests__/mcp-manager.test.ts`
|
|
329
|
+
|
|
330
|
+
为 `McpManager` 编写单元测试,覆盖所有 MCP 操作。
|
|
331
|
+
|
|
332
|
+
### Step 6: 更新 `src/__tests__/agent.test.ts`
|
|
333
|
+
|
|
334
|
+
添加 extMethod MCP 路由的测试用例,验证方法分发和参数传递。
|
|
335
|
+
|
|
336
|
+
### Step 7: 更新文档
|
|
337
|
+
|
|
338
|
+
- 更新 `README.md` 说明 MCP extMethod 接口
|
|
339
|
+
- 更新 `CHANGELOG.md`
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 关键文件清单
|
|
344
|
+
|
|
345
|
+
| 文件 | 操作 | 说明 |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `src/mcp-manager.ts` | **新建** | MCP 操作封装,代理 SDK 调用 |
|
|
348
|
+
| `src/agent.ts` | **修改** | 添加 extMethod 路由、集成 McpManager、迁移 MCP 注册逻辑 |
|
|
349
|
+
| `src/types.ts` | **修改** | 添加 MCP 相关类型(可选) |
|
|
350
|
+
| `src/__tests__/mcp-manager.test.ts` | **新建** | McpManager 单元测试 |
|
|
351
|
+
| `src/__tests__/agent.test.ts` | **修改** | 添加 extMethod 测试 |
|
|
352
|
+
| `src/__tests__/helpers/mock-sdk.ts` | **修改** | 补全 MCP mock 方法 |
|
|
353
|
+
| `README.md` | **修改** | 文档更新 |
|
|
354
|
+
| `CHANGELOG.md` | **修改** | 记录变更 |
|
|
355
|
+
|
|
356
|
+
## 复用
|
|
357
|
+
|
|
358
|
+
- `@opencode-ai/sdk` 的 `Mcp` 类(`sdk.mcp.*`):所有 MCP 操作直接代理
|
|
359
|
+
- ACP SDK 的 `extMethod` 机制:已有协议支持,无需修改
|
|
360
|
+
- `SessionManager`:复用 `get()` 获取 session 的 `cwd` 用于 SDK 调用
|
|
361
|
+
- 现有日志工具:`ocCall` 记录 OpenCode 调用
|
|
362
|
+
|
|
363
|
+
## 验证方式
|
|
364
|
+
|
|
365
|
+
1. `cd /Users/liwenwei/Documents/Code/harmony-acp && pnpm test` — 所有测试通过
|
|
366
|
+
2. 手动测试:启动 opencode server → 启动 harmony-acp → 通过 ACP client 发送 extMethod 调用验证 MCP 操作
|