@vrs-soft/wecom-aibot-mcp 1.5.0 → 2.3.0
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/README.md +50 -337
- package/dist/bin.js +172 -85
- package/dist/channel-server.d.ts +15 -0
- package/dist/channel-server.js +492 -0
- package/dist/channel-server.test.d.ts +5 -0
- package/dist/channel-server.test.js +324 -0
- package/dist/client-pool.js +4 -3
- package/dist/client.js +49 -49
- package/dist/config-wizard.d.ts +13 -3
- package/dist/config-wizard.js +424 -141
- package/dist/connection-log.js +7 -6
- package/dist/connection-manager.d.ts +0 -8
- package/dist/connection-manager.js +11 -23
- package/dist/daemon.js +7 -6
- package/dist/headless-state.d.ts +0 -12
- package/dist/headless-state.js +11 -35
- package/dist/http-server.d.ts +21 -2
- package/dist/http-server.js +421 -88
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/keepalive-monitor.js +5 -4
- package/dist/logger.d.ts +51 -0
- package/dist/logger.js +84 -0
- package/dist/message-bus.d.ts +13 -1
- package/dist/message-bus.js +56 -3
- package/dist/project-config.d.ts +16 -0
- package/dist/project-config.js +91 -12
- package/dist/tools/index.js +226 -57
- package/package.json +1 -1
- package/skills/headless-mode/SKILL.md +136 -181
package/dist/tools/index.js
CHANGED
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
* - 从 Session 自动获取 robotName
|
|
21
21
|
*/
|
|
22
22
|
import { z } from 'zod';
|
|
23
|
-
import { listAllRobots } from '../config-wizard.js';
|
|
24
|
-
import { connectRobot, disconnectRobot, getClient, getConnectionState,
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { updateWechatModeConfig, addPermissionHook, removePermissionHook } from '../project-config.js';
|
|
23
|
+
import { listAllRobots, installSkill } from '../config-wizard.js';
|
|
24
|
+
import { connectRobot, disconnectRobot, getClient, getConnectionState, } from '../connection-manager.js';
|
|
25
|
+
import { registerCcId, unregisterCcId, getRobotByCcId, getProjectDirByCcId, generateCcId, } from '../http-server.js';
|
|
26
|
+
import { subscribeWecomMessageByCcId } from '../message-bus.js';
|
|
27
|
+
import { updateWechatModeConfig, loadWechatModeConfig, addPermissionHook, removePermissionHook, addTaskCompletedHook, removeTaskCompletedHook } from '../project-config.js';
|
|
28
|
+
import { logger } from '../logger.js';
|
|
28
29
|
// 辅助函数:从 ccId 获取客户端
|
|
29
30
|
async function getConnectedClient(ccId) {
|
|
30
31
|
if (!ccId) {
|
|
@@ -47,15 +48,49 @@ export function registerTools(server) {
|
|
|
47
48
|
server.tool('send_message', '向企业微信发送消息(用于通知用户)。群聊时传入 chatid 可回复到群里。', {
|
|
48
49
|
content: z.string().describe('消息内容(支持 Markdown)'),
|
|
49
50
|
target_user: z.string().optional().describe('目标用户/群 ID(可选)'),
|
|
50
|
-
cc_id: z.string().
|
|
51
|
+
cc_id: z.string().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
|
|
51
52
|
}, async ({ content, target_user, cc_id }, extra) => {
|
|
52
53
|
const { error, client } = await getConnectedClient(cc_id);
|
|
53
54
|
if (error || !client) {
|
|
54
55
|
return { content: [{ type: 'text', text: JSON.stringify({ error }) }] };
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
+
// 自动在消息头部添加 ccId 标识(多对一场景区分)
|
|
58
|
+
const prefixedContent = `【${cc_id}】${content}`;
|
|
59
|
+
const success = await client.sendText(prefixedContent, target_user);
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: success
|
|
64
|
+
? '✅ 消息已发送,继续轮询,继续等待'
|
|
65
|
+
: '发送失败,请检查连接状态',
|
|
66
|
+
}],
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// ============================================
|
|
70
|
+
// 工具 2: 心跳检查(HTTP 模式)
|
|
71
|
+
// ============================================
|
|
72
|
+
server.tool('heartbeat_check', '心跳检查,提示智能体继续轮询(仅 HTTP 模式使用)', {}, async () => {
|
|
57
73
|
return {
|
|
58
|
-
content: [{
|
|
74
|
+
content: [{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: '心跳检查:如有执行结果请发送,如果没有应继续轮询 get_pending_messages 接口!',
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
// ============================================
|
|
81
|
+
// 工具 3: 保存心跳 job ID(HTTP 模式)
|
|
82
|
+
// ============================================
|
|
83
|
+
server.tool('update_heartbeat_job_id', '保存心跳定时任务 job ID 到配置文件(HTTP 模式用,/loop 创建后调用)', {
|
|
84
|
+
cc_id: z.string().describe('CC 唯一标识'),
|
|
85
|
+
job_id: z.string().describe('由 /loop 命令返回的 job ID'),
|
|
86
|
+
}, async ({ cc_id, job_id }) => {
|
|
87
|
+
const projectDir = getProjectDirByCcId(cc_id) || process.cwd();
|
|
88
|
+
updateWechatModeConfig(projectDir, { heartbeatJobId: job_id });
|
|
89
|
+
return {
|
|
90
|
+
content: [{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: JSON.stringify({ success: true, projectDir, job_id }),
|
|
93
|
+
}],
|
|
59
94
|
};
|
|
60
95
|
});
|
|
61
96
|
// ============================================
|
|
@@ -77,10 +112,10 @@ export function registerTools(server) {
|
|
|
77
112
|
// ============================================
|
|
78
113
|
// 工具 6: 获取待处理消息
|
|
79
114
|
// ============================================
|
|
80
|
-
server.tool('get_pending_messages', '获取待处理的微信消息。支持长轮询:传入 timeout_ms
|
|
115
|
+
server.tool('get_pending_messages', '获取待处理的微信消息。支持长轮询:传入 timeout_ms 后阻塞等待,有消息立即返回,无消息等到超时。超时后继续轮询,不要停止。', {
|
|
81
116
|
clear: z.boolean().optional().default(true).describe('是否清除已获取的消息'),
|
|
82
|
-
timeout_ms: z.number().optional().default(
|
|
83
|
-
cc_id: z.string().
|
|
117
|
+
timeout_ms: z.number().optional().default(30000).describe('长轮询超时(毫秒),默认 30000,最大 60000'),
|
|
118
|
+
cc_id: z.string().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
|
|
84
119
|
}, async ({ clear, timeout_ms = 0, cc_id }) => {
|
|
85
120
|
const { error, client, robotName } = await getConnectedClient(cc_id);
|
|
86
121
|
if (error || !client) {
|
|
@@ -91,12 +126,21 @@ export function registerTools(server) {
|
|
|
91
126
|
}],
|
|
92
127
|
};
|
|
93
128
|
}
|
|
129
|
+
// 消息格式化:返回给智能体的数据结构
|
|
130
|
+
// 字段说明:
|
|
131
|
+
// - content: 用户发送的消息内容
|
|
132
|
+
// - from: 发送者用户 ID
|
|
133
|
+
// - chatid: 会话 ID(单聊=用户ID,群聊=群ID)
|
|
134
|
+
// - chattype: 会话类型(single/group)
|
|
135
|
+
// - time: 消息时间(ISO 格式)
|
|
136
|
+
// - quoteContent: 用户引用的消息内容(可选,用户回复时引用的上一条消息)
|
|
94
137
|
const formatMessages = (msgs) => msgs.map(m => ({
|
|
95
138
|
content: m.content,
|
|
96
139
|
from: m.from_userid,
|
|
97
140
|
chatid: m.chatid,
|
|
98
141
|
chattype: m.chattype,
|
|
99
142
|
time: new Date(m.timestamp).toISOString(),
|
|
143
|
+
quoteContent: m.quoteContent,
|
|
100
144
|
}));
|
|
101
145
|
// 先检查是否已有积压消息
|
|
102
146
|
const existing = client.getPendingMessages(false);
|
|
@@ -110,20 +154,27 @@ export function registerTools(server) {
|
|
|
110
154
|
}],
|
|
111
155
|
};
|
|
112
156
|
}
|
|
113
|
-
//
|
|
157
|
+
// 无积压且不等待,立即返回空(timeout_ms 传 0 时)
|
|
114
158
|
const waitMs = Math.min(timeout_ms, 60000);
|
|
115
159
|
if (waitMs <= 0) {
|
|
116
160
|
return {
|
|
117
|
-
content: [{
|
|
161
|
+
content: [{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: JSON.stringify({
|
|
164
|
+
count: 0,
|
|
165
|
+
messages: [],
|
|
166
|
+
hint: 'timeout_ms=0 仅用于检查积压消息,请传入 timeout_ms=30000 开始长轮询'
|
|
167
|
+
}),
|
|
168
|
+
}],
|
|
118
169
|
};
|
|
119
170
|
}
|
|
120
|
-
//
|
|
171
|
+
// 长轮询:订阅消息总线,等待该机器人且匹配 ccId 的消息
|
|
121
172
|
const arrived = await new Promise(resolve => {
|
|
122
173
|
const timer = setTimeout(() => {
|
|
123
174
|
sub.unsubscribe();
|
|
124
175
|
resolve(null);
|
|
125
176
|
}, waitMs);
|
|
126
|
-
const sub =
|
|
177
|
+
const sub = subscribeWecomMessageByCcId(robotName, cc_id, (msg) => {
|
|
127
178
|
clearTimeout(timer);
|
|
128
179
|
sub.unsubscribe();
|
|
129
180
|
resolve(msg);
|
|
@@ -131,7 +182,15 @@ export function registerTools(server) {
|
|
|
131
182
|
});
|
|
132
183
|
if (arrived === null) {
|
|
133
184
|
return {
|
|
134
|
-
content: [{
|
|
185
|
+
content: [{
|
|
186
|
+
type: 'text',
|
|
187
|
+
text: JSON.stringify({
|
|
188
|
+
count: 0,
|
|
189
|
+
messages: [],
|
|
190
|
+
waiting: true,
|
|
191
|
+
hint: '继续等待,继续轮询'
|
|
192
|
+
}),
|
|
193
|
+
}],
|
|
135
194
|
};
|
|
136
195
|
}
|
|
137
196
|
// 消息到了,取出所有积压(含刚到的)
|
|
@@ -183,6 +242,69 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
183
242
|
return { content: [{ type: 'text', text: guide }] };
|
|
184
243
|
});
|
|
185
244
|
// ============================================
|
|
245
|
+
// 工具: 获取配置需求(供 skill 自动配置)
|
|
246
|
+
// ============================================
|
|
247
|
+
server.tool('get_setup_requirements', '获取 MCP 配置需求,用于 skill 自动配置本地环境(权限、Hook、skill)。启动时调用检查配置是否完整。', {}, async () => {
|
|
248
|
+
return {
|
|
249
|
+
content: [{
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: JSON.stringify({
|
|
252
|
+
version: '2.0.0',
|
|
253
|
+
requirements: {
|
|
254
|
+
// 权限配置需求
|
|
255
|
+
permissions: {
|
|
256
|
+
file: '~/.claude/settings.local.json',
|
|
257
|
+
allow: [
|
|
258
|
+
'mcp__wecom-aibot__send_message',
|
|
259
|
+
'mcp__wecom-aibot__heartbeat_check',
|
|
260
|
+
'mcp__wecom-aibot__get_pending_messages',
|
|
261
|
+
'mcp__wecom-aibot__check_connection',
|
|
262
|
+
'mcp__wecom-aibot__list_robots',
|
|
263
|
+
'mcp__wecom-aibot__enter_headless_mode',
|
|
264
|
+
'mcp__wecom-aibot__exit_headless_mode',
|
|
265
|
+
'mcp__wecom-aibot__get_connection_stats',
|
|
266
|
+
'mcp__wecom-aibot__get_setup_requirements',
|
|
267
|
+
'mcp__wecom-aibot__get_skill',
|
|
268
|
+
'mcp__wecom-aibot__update_heartbeat_job_id',
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
// Hook 配置需求
|
|
272
|
+
hooks: {
|
|
273
|
+
file: '~/.claude/settings.local.json',
|
|
274
|
+
PermissionRequest: {
|
|
275
|
+
script: '~/.wecom-aibot-mcp/permission-hook.sh',
|
|
276
|
+
description: '审批请求通过微信发送',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
// Skill 安装需求
|
|
280
|
+
skills: {
|
|
281
|
+
projectDir: '.claude/skills/headless-mode',
|
|
282
|
+
files: ['SKILL.md'],
|
|
283
|
+
skillUrl: `${process.env.MCP_URL || 'http://127.0.0.1:18963'}/skill`, // 远程部署下载 URL
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
// 检查命令(供 skill 验证)
|
|
287
|
+
checkCommands: {
|
|
288
|
+
permissions: '检查 ~/.claude/settings.local.json 是否包含 mcp__wecom-aibot__ 权限',
|
|
289
|
+
hook: '检查 ~/.claude/settings.local.json 是否包含 PermissionRequest hook',
|
|
290
|
+
skill: '检查 ~/.claude/skills/headless-mode/SKILL.md 是否存在',
|
|
291
|
+
},
|
|
292
|
+
// 模式说明
|
|
293
|
+
modes: {
|
|
294
|
+
channel: {
|
|
295
|
+
description: 'SSE 推送模式,微信消息自动唤醒 Agent',
|
|
296
|
+
capability: 'claude/channel',
|
|
297
|
+
},
|
|
298
|
+
http: {
|
|
299
|
+
description: '轮询模式,Agent 需调用 get_pending_messages 和 heartbeat_check',
|
|
300
|
+
capability: null,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
}, null, 2),
|
|
304
|
+
}],
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
// ============================================
|
|
186
308
|
// 工具 7: 添加新机器人配置
|
|
187
309
|
// ============================================
|
|
188
310
|
server.tool('add_robot_config', '添加新机器人配置', {
|
|
@@ -204,43 +326,40 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
204
326
|
// ============================================
|
|
205
327
|
// 工具 8: 列出所有机器人
|
|
206
328
|
// ============================================
|
|
207
|
-
server.tool('list_robots', '
|
|
329
|
+
server.tool('list_robots', '列出配置中的所有机器人。多 CC 可共享同一机器人,直接选择使用即可。', {}, async () => {
|
|
208
330
|
const allRobots = listAllRobots();
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
botId: robot.botId,
|
|
217
|
-
targetUser: robot.targetUserId,
|
|
218
|
-
status: connected ? 'connected' : (occupied ? 'occupied' : 'available'),
|
|
219
|
-
occupiedBy,
|
|
220
|
-
};
|
|
221
|
-
});
|
|
331
|
+
// 返回完整信息,包括 botId 用于区分同名机器人
|
|
332
|
+
const robotList = allRobots.map((robot, index) => ({
|
|
333
|
+
index: index + 1,
|
|
334
|
+
name: robot.name,
|
|
335
|
+
botId: robot.botId?.slice(0, 12) + '...', // 只显示前12位
|
|
336
|
+
targetUser: robot.targetUserId,
|
|
337
|
+
}));
|
|
222
338
|
return {
|
|
223
339
|
content: [{
|
|
224
340
|
type: 'text',
|
|
225
|
-
text: JSON.stringify({
|
|
226
|
-
robots,
|
|
227
|
-
total: robots.length,
|
|
228
|
-
connected: robots.filter(r => r.status === 'connected').length,
|
|
229
|
-
occupied: robots.filter(r => r.status === 'occupied').length,
|
|
230
|
-
}, null, 2),
|
|
341
|
+
text: JSON.stringify({ robots: robotList }, null, 2),
|
|
231
342
|
}],
|
|
232
343
|
};
|
|
233
344
|
});
|
|
234
345
|
// ============================================
|
|
235
|
-
// 工具
|
|
346
|
+
// 工具 9: 进入 headless 模式
|
|
236
347
|
// ============================================
|
|
237
348
|
server.tool('enter_headless_mode', '进入微信模式,建立 WebSocket 连接。当用户说「现在开始通过微信联系」时调用。', {
|
|
238
|
-
agent_name: z.string().describe('
|
|
349
|
+
agent_name: z.string().optional().describe('智能体/项目名称(用于生成 ccId,如项目名)'),
|
|
350
|
+
cc_id: z.string().optional().describe('CC 唯一标识(可选,未传入时服务端自动生成)'),
|
|
239
351
|
robot_id: z.string().optional().describe('指定机器人名称或序号'),
|
|
240
352
|
project_dir: z.string().optional().describe('项目目录路径(用于写入配置文件)'),
|
|
353
|
+
mode: z.enum(['channel', 'http']).optional().default('http')
|
|
354
|
+
.describe('运行模式:channel=SSE推送(推荐),http=轮询(兼容)'),
|
|
241
355
|
auto_approve: z.boolean().optional().default(true).describe('超时自动审批(默认 true)'),
|
|
242
356
|
auto_approve_timeout: z.number().optional().default(600).describe('自动审批超时时间(秒,默认 600 即 10 分钟)'),
|
|
243
|
-
}, async ({ agent_name, robot_id, project_dir, auto_approve, auto_approve_timeout }, extra) => {
|
|
357
|
+
}, async ({ agent_name, cc_id, robot_id, project_dir, mode, auto_approve, auto_approve_timeout }, extra) => {
|
|
358
|
+
// 获取项目目录
|
|
359
|
+
const projectDir = project_dir || process.cwd();
|
|
360
|
+
// 智能体名称(用于生成 ccId)
|
|
361
|
+
// 优先级:agent_name > cc_id > 项目目录名 > 'cc'
|
|
362
|
+
const effectiveAgentName = agent_name || cc_id || projectDir.split('/').pop() || 'cc';
|
|
244
363
|
const allRobots = listAllRobots();
|
|
245
364
|
if (allRobots.length === 0) {
|
|
246
365
|
return {
|
|
@@ -282,7 +401,7 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
282
401
|
}
|
|
283
402
|
}
|
|
284
403
|
// 连接机器人
|
|
285
|
-
const result = await connectRobot(selectedRobot.name,
|
|
404
|
+
const result = await connectRobot(selectedRobot.name, effectiveAgentName);
|
|
286
405
|
if (!result.success || !result.client) {
|
|
287
406
|
return {
|
|
288
407
|
content: [{
|
|
@@ -294,22 +413,30 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
294
413
|
}],
|
|
295
414
|
};
|
|
296
415
|
}
|
|
297
|
-
//
|
|
298
|
-
const
|
|
299
|
-
|
|
416
|
+
// 如果用户传入 cc_id,直接使用;否则自动生成
|
|
417
|
+
const finalCcId = cc_id || generateCcId(effectiveAgentName);
|
|
418
|
+
// 检查 wecom-aibot.json 是否已有匹配的 ccId(重连场景)
|
|
419
|
+
const existingConfig = loadWechatModeConfig(projectDir);
|
|
420
|
+
const isReconnect = existingConfig?.ccId === finalCcId;
|
|
421
|
+
// 注册 ccId 到 CC 注册表(重连直接覆盖,首次注册清理超时条目)
|
|
422
|
+
registerCcId(finalCcId, selectedRobot.name, effectiveAgentName, mode, projectDir, isReconnect);
|
|
300
423
|
// 更新项目配置文件中的 wechatMode 为 true
|
|
301
|
-
const projectDir = project_dir || process.cwd();
|
|
302
424
|
updateWechatModeConfig(projectDir, {
|
|
303
425
|
wechatMode: true,
|
|
304
426
|
robotName: selectedRobot.name,
|
|
305
|
-
ccId,
|
|
427
|
+
ccId: finalCcId,
|
|
306
428
|
autoApprove: auto_approve,
|
|
307
429
|
autoApproveTimeout: auto_approve_timeout,
|
|
308
430
|
});
|
|
431
|
+
// 安装 skill 到项目本地(支持远程部署 MCP)
|
|
432
|
+
const skillResult = installSkill(projectDir);
|
|
309
433
|
// 添加 PermissionRequest hook 到项目 settings.json
|
|
310
434
|
const hookResult = addPermissionHook(projectDir);
|
|
311
|
-
//
|
|
312
|
-
|
|
435
|
+
// HTTP 模式添加 TaskCompleted hook(Channel 模式不需要,消息自动推送)
|
|
436
|
+
const taskCompletedHookResult = mode === 'http' ? addTaskCompletedHook(projectDir) : { added: false, message: 'Channel 模式不需要 TaskCompleted hook' };
|
|
437
|
+
// 发送确认消息(头部标注来源 ccId 和 mode)
|
|
438
|
+
const modeDesc = mode === 'channel' ? 'Channel模式,消息自动推送' : 'HTTP模式,请定期轮询获取消息';
|
|
439
|
+
await result.client.sendText(`【${finalCcId}】已进入微信模式(${modeDesc}),使用机器人「${selectedRobot.name}」。`);
|
|
313
440
|
return {
|
|
314
441
|
content: [{
|
|
315
442
|
type: 'text',
|
|
@@ -317,9 +444,16 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
317
444
|
status: 'entered',
|
|
318
445
|
headless: true,
|
|
319
446
|
robotName: selectedRobot.name,
|
|
320
|
-
ccId,
|
|
447
|
+
ccId: finalCcId,
|
|
448
|
+
agentName: effectiveAgentName, // 返回使用的 agentName
|
|
449
|
+
mode,
|
|
321
450
|
hook: hookResult,
|
|
322
|
-
|
|
451
|
+
taskCompletedHook: taskCompletedHookResult,
|
|
452
|
+
skill: skillResult, // skill 安装结果(如果 success=false,包含 skillUrl)
|
|
453
|
+
sseEndpoint: mode === 'channel' ? `http://127.0.0.1:18963/sse/${finalCcId}` : undefined,
|
|
454
|
+
message: mode === 'channel'
|
|
455
|
+
? `连接 SSE endpoint: http://127.0.0.1:18963/sse/${finalCcId} 接收推送消息`
|
|
456
|
+
: '已进入微信模式(HTTP)',
|
|
323
457
|
}),
|
|
324
458
|
}],
|
|
325
459
|
};
|
|
@@ -328,10 +462,9 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
328
462
|
// 工具 11: 退出 headless 模式
|
|
329
463
|
// ============================================
|
|
330
464
|
server.tool('exit_headless_mode', '退出微信模式,断开连接。当用户说「结束微信模式」或「我回来了」时调用。', {
|
|
331
|
-
|
|
332
|
-
cc_id: z.string().optional().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
|
|
465
|
+
cc_id: z.string().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
|
|
333
466
|
project_dir: z.string().optional().describe('项目目录路径(用于更新配置文件)'),
|
|
334
|
-
}, async ({
|
|
467
|
+
}, async ({ cc_id, project_dir }) => {
|
|
335
468
|
const { error, client, robotName } = await getConnectedClient(cc_id);
|
|
336
469
|
if (error || !client || !robotName) {
|
|
337
470
|
return {
|
|
@@ -341,10 +474,8 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
341
474
|
}],
|
|
342
475
|
};
|
|
343
476
|
}
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
const name = agent_name || entry?.agentName || '智能体';
|
|
347
|
-
await client.sendText(`【${name}】已退出微信模式,恢复终端交互。`);
|
|
477
|
+
// 发送退出通知(使用 ccId 作为标识)
|
|
478
|
+
await client.sendText(`【${cc_id}】已退出微信模式,恢复终端交互。`);
|
|
348
479
|
// 注销 ccId
|
|
349
480
|
if (cc_id) {
|
|
350
481
|
unregisterCcId(cc_id);
|
|
@@ -356,6 +487,8 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
356
487
|
updateWechatModeConfig(projectDir, { wechatMode: false });
|
|
357
488
|
// 删除 PermissionRequest hook 从项目 settings.json
|
|
358
489
|
const hookResult = removePermissionHook(projectDir);
|
|
490
|
+
// 删除 TaskCompleted hook 从项目 settings.json
|
|
491
|
+
const taskCompletedHookResult = removeTaskCompletedHook(projectDir);
|
|
359
492
|
return {
|
|
360
493
|
content: [{
|
|
361
494
|
type: 'text',
|
|
@@ -364,6 +497,7 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
364
497
|
headless: false,
|
|
365
498
|
robotName,
|
|
366
499
|
hook: hookResult,
|
|
500
|
+
taskCompletedHook: taskCompletedHookResult,
|
|
367
501
|
message: '审批将使用默认 UI',
|
|
368
502
|
}),
|
|
369
503
|
}],
|
|
@@ -429,5 +563,40 @@ npx @vrs-soft/wecom-aibot-mcp
|
|
|
429
563
|
}],
|
|
430
564
|
};
|
|
431
565
|
});
|
|
432
|
-
|
|
566
|
+
// ============================================
|
|
567
|
+
// 工具 14: 获取 skill 文件内容
|
|
568
|
+
// ============================================
|
|
569
|
+
server.tool('get_skill', '获取 headless-mode skill 文件内容,用于写入本地项目目录。远程部署 HTTP MCP 时使用此工具获取 skill 文件。', {}, async () => {
|
|
570
|
+
const { fileURLToPath } = await import('url');
|
|
571
|
+
const fs = await import('fs');
|
|
572
|
+
const path = await import('path');
|
|
573
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
574
|
+
const __dirname = path.dirname(__filename);
|
|
575
|
+
const skillPath = path.join(__dirname, '..', '..', 'skills', 'headless-mode', 'SKILL.md');
|
|
576
|
+
if (!fs.existsSync(skillPath)) {
|
|
577
|
+
return {
|
|
578
|
+
content: [{
|
|
579
|
+
type: 'text',
|
|
580
|
+
text: JSON.stringify({
|
|
581
|
+
success: false,
|
|
582
|
+
error: 'Skill 文件不存在',
|
|
583
|
+
skillUrl: `${process.env.MCP_URL || 'http://127.0.0.1:18963'}/skill`,
|
|
584
|
+
}),
|
|
585
|
+
}],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
589
|
+
return {
|
|
590
|
+
content: [{
|
|
591
|
+
type: 'text',
|
|
592
|
+
text: JSON.stringify({
|
|
593
|
+
success: true,
|
|
594
|
+
content,
|
|
595
|
+
filename: 'SKILL.md',
|
|
596
|
+
installPath: '.claude/skills/headless-mode/SKILL.md',
|
|
597
|
+
}),
|
|
598
|
+
}],
|
|
599
|
+
};
|
|
600
|
+
});
|
|
601
|
+
logger.log('[mcp] 已注册 14 个工具');
|
|
433
602
|
}
|