@vrs-soft/wecom-aibot-mcp 2.3.1 → 2.3.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.
@@ -38,6 +38,7 @@ function logChannel(message, data) {
38
38
  let sseConnected = false;
39
39
  let sseAbortController = null;
40
40
  let mcpServer = null;
41
+ let sseCurrentCcId = undefined;
41
42
  // HTTP MCP session ID(需要在转发请求前初始化)
42
43
  let httpSessionId = null;
43
44
  /**
@@ -172,6 +173,7 @@ function connectSSE(ccId) {
172
173
  return;
173
174
  }
174
175
  sseConnected = true;
176
+ sseCurrentCcId = ccId;
175
177
  const sseUrl = ccId ? `${MCP_URL}/sse/${ccId}` : `${MCP_URL}/sse`;
176
178
  logChannel('Connecting to SSE', { url: sseUrl, ccId, mcpServerReady: mcpServer ? 'yes' : 'no' });
177
179
  sseAbortController = new AbortController();
@@ -210,6 +212,11 @@ function connectSSE(ccId) {
210
212
  logChannel('SSE stream ended');
211
213
  clearInterval(heartbeatInterval);
212
214
  sseConnected = false;
215
+ // 非主动断开时自动重连
216
+ if (!sseAbortController?.signal.aborted) {
217
+ logChannel('SSE 断线,3 秒后重连', { ccId });
218
+ setTimeout(() => connectSSE(ccId), 3000);
219
+ }
213
220
  break;
214
221
  }
215
222
  const chunk = decoder.decode(value, { stream: true });
@@ -240,6 +247,7 @@ function connectSSE(ccId) {
240
247
  chatid: message.chatid || '',
241
248
  chattype: message.chattype || 'single',
242
249
  cc_id: msg.ccId || '',
250
+ quote_content: message.quoteContent || '',
243
251
  },
244
252
  },
245
253
  };
@@ -279,6 +287,11 @@ function connectSSE(ccId) {
279
287
  }).catch((err) => {
280
288
  logChannel('SSE error', { error: String(err) });
281
289
  sseConnected = false;
290
+ // 非主动断开时自动重连
291
+ if (!sseAbortController?.signal.aborted) {
292
+ logChannel('SSE 出错,3 秒后重连', { ccId });
293
+ setTimeout(() => connectSSE(ccId), 3000);
294
+ }
282
295
  });
283
296
  }
284
297
  /**
@@ -351,8 +364,9 @@ function registerChannelTools(server) {
351
364
  bot_id: z.string().describe('企业微信 Bot ID'),
352
365
  secret: z.string().describe('机器人密钥'),
353
366
  default_user: z.string().optional().describe('默认目标用户'),
354
- }, async ({ name, bot_id, secret, default_user }) => {
355
- return forwardToHttpMcp('add_robot_config', { name, bot_id, secret, default_user });
367
+ doc_mcp_url: z.string().optional().describe('机器人文档 MCP URL(企业微信文档能力)'),
368
+ }, async ({ name, bot_id, secret, default_user, doc_mcp_url }) => {
369
+ return forwardToHttpMcp('add_robot_config', { name, bot_id, secret, default_user, doc_mcp_url });
356
370
  });
357
371
  // ============================================
358
372
  // 工具 8: 列出所有机器人
@@ -378,7 +392,7 @@ function registerChannelTools(server) {
378
392
  agent_name,
379
393
  cc_id,
380
394
  robot_id,
381
- project_dir,
395
+ project_dir: project_dir || process.cwd(),
382
396
  mode,
383
397
  auto_approve,
384
398
  auto_approve_timeout,
@@ -415,14 +429,15 @@ function registerChannelTools(server) {
415
429
  cc_id: z.string().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
416
430
  project_dir: z.string().optional().describe('项目目录路径(用于更新配置文件)'),
417
431
  }, async ({ cc_id, project_dir }) => {
418
- // 断开 SSE 连接
432
+ // 断开 SSE 连接(abort 后重连逻辑不会触发)
419
433
  if (sseAbortController) {
420
434
  sseAbortController.abort();
421
435
  sseAbortController = null;
422
436
  sseConnected = false;
437
+ sseCurrentCcId = undefined;
423
438
  logChannel('SSE disconnected', { cc_id });
424
439
  }
425
- return forwardToHttpMcp('exit_headless_mode', { cc_id, project_dir });
440
+ return forwardToHttpMcp('exit_headless_mode', { cc_id, project_dir: project_dir || process.cwd() });
426
441
  });
427
442
  // ============================================
428
443
  // 工具 11: 从消息识别用户
@@ -461,7 +476,97 @@ function registerChannelTools(server) {
461
476
  }],
462
477
  };
463
478
  });
464
- logChannel('Registered 13 tools');
479
+ // ============================================
480
+ // 文档代理工具(转发到 HTTP MCP)
481
+ // ============================================
482
+ const docTools = [
483
+ ['create_doc', '新建文档或智能表格', {
484
+ doc_type: z.number().int().describe('文档类型:3=文档,10=智能表格'),
485
+ doc_name: z.string().describe('文档名称'),
486
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
487
+ }],
488
+ ['get_doc_content', '获取文档内容(Markdown 格式)', {
489
+ type: z.number().int().describe('内容格式:2=Markdown'),
490
+ url: z.string().optional().describe('文档链接'),
491
+ docid: z.string().optional().describe('文档 docid'),
492
+ task_id: z.string().optional().describe('任务 ID(轮询时填写)'),
493
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
494
+ }],
495
+ ['edit_doc_content', '编辑文档内容(Markdown 格式覆写)', {
496
+ content: z.string().describe('覆写的文档内容'),
497
+ content_type: z.number().int().describe('内容类型:1=Markdown'),
498
+ url: z.string().optional().describe('文档链接'),
499
+ docid: z.string().optional().describe('文档 docid'),
500
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
501
+ }],
502
+ ['smartsheet_get_sheet', '查询智能表格子表信息', {
503
+ url: z.string().optional(), docid: z.string().optional(),
504
+ robot_name: z.string().optional(),
505
+ }],
506
+ ['smartsheet_add_sheet', '添加智能表格子表', {
507
+ url: z.string().optional(), docid: z.string().optional(),
508
+ properties: z.object({ title: z.string().optional() }).optional(),
509
+ robot_name: z.string().optional(),
510
+ }],
511
+ ['smartsheet_update_sheet', '更新智能表格子表标题', {
512
+ properties: z.object({ sheet_id: z.string(), title: z.string() }),
513
+ url: z.string().optional(), docid: z.string().optional(),
514
+ robot_name: z.string().optional(),
515
+ }],
516
+ ['smartsheet_delete_sheet', '删除智能表格子表', {
517
+ sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
518
+ robot_name: z.string().optional(),
519
+ }],
520
+ ['smartsheet_get_fields', '查询智能表格字段', {
521
+ sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
522
+ robot_name: z.string().optional(),
523
+ }],
524
+ ['smartsheet_add_fields', '添加智能表格字段', {
525
+ sheet_id: z.string(),
526
+ fields: z.array(z.object({ field_title: z.string(), field_type: z.string() })),
527
+ url: z.string().optional(), docid: z.string().optional(),
528
+ robot_name: z.string().optional(),
529
+ }],
530
+ ['smartsheet_update_fields', '更新智能表格字段标题', {
531
+ sheet_id: z.string(),
532
+ fields: z.array(z.object({ field_id: z.string(), field_title: z.string(), field_type: z.string() })),
533
+ url: z.string().optional(), docid: z.string().optional(),
534
+ robot_name: z.string().optional(),
535
+ }],
536
+ ['smartsheet_delete_fields', '删除智能表格字段', {
537
+ sheet_id: z.string(), field_ids: z.array(z.string()),
538
+ url: z.string().optional(), docid: z.string().optional(),
539
+ robot_name: z.string().optional(),
540
+ }],
541
+ ['smartsheet_get_records', '查询智能表格记录', {
542
+ sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
543
+ robot_name: z.string().optional(),
544
+ }],
545
+ ['smartsheet_add_records', '添加智能表格记录', {
546
+ sheet_id: z.string(),
547
+ records: z.array(z.object({ values: z.record(z.string(), z.unknown()) })),
548
+ url: z.string().optional(), docid: z.string().optional(),
549
+ robot_name: z.string().optional(),
550
+ }],
551
+ ['smartsheet_update_records', '更新智能表格记录', {
552
+ sheet_id: z.string(),
553
+ records: z.array(z.object({ record_id: z.string(), values: z.record(z.string(), z.unknown()) })),
554
+ key_type: z.enum(['CELL_VALUE_KEY_TYPE_FIELD_TITLE', 'CELL_VALUE_KEY_TYPE_FIELD_ID']),
555
+ url: z.string().optional(), docid: z.string().optional(),
556
+ robot_name: z.string().optional(),
557
+ }],
558
+ ['smartsheet_delete_records', '删除智能表格记录', {
559
+ sheet_id: z.string(), record_ids: z.array(z.string()),
560
+ url: z.string().optional(), docid: z.string().optional(),
561
+ robot_name: z.string().optional(),
562
+ }],
563
+ ];
564
+ for (const [toolName, description, schema] of docTools) {
565
+ server.tool(toolName, description, schema, async (args) => {
566
+ return forwardToHttpMcp(toolName, args);
567
+ });
568
+ }
569
+ logChannel('Registered 28 tools (13 core + 15 doc proxy)');
465
570
  }
466
571
  /**
467
572
  * 启动 Channel MCP Server
@@ -14,6 +14,7 @@ export interface WecomConfig {
14
14
  secret: string;
15
15
  defaultUser: string;
16
16
  nameTag?: string;
17
+ doc_mcp_url?: string;
17
18
  }
18
19
  /**
19
20
  * 获取或创建 WecomClient 实例
@@ -4,6 +4,7 @@ export interface WecomConfig {
4
4
  targetUserId: string;
5
5
  targetUserName?: string;
6
6
  nameTag?: string;
7
+ doc_mcp_url?: string;
7
8
  }
8
9
  export declare function loadConfig(): WecomConfig | null;
9
10
  export declare function listAllMcpInstances(): Array<{
@@ -23,7 +24,12 @@ export declare function listAllRobots(): Array<{
23
24
  name: string;
24
25
  botId: string;
25
26
  targetUserId: string;
27
+ doc_mcp_url?: string;
26
28
  }>;
29
+ export declare function getDocMcpUrl(robotName?: string): {
30
+ url: string | null;
31
+ error?: string;
32
+ };
27
33
  export declare function ensureHookInstalled(): void;
28
34
  export declare function ensureGlobalConfigs(mode?: 'full' | 'http-only' | 'channel-only'): {
29
35
  upgraded: boolean;
@@ -45,11 +45,16 @@ export function loadConfig() {
45
45
  const content = fs.readFileSync(BOT_CONFIG_FILE, 'utf-8');
46
46
  const config = JSON.parse(content);
47
47
  if (config.botId && config.secret && config.targetUserId) {
48
- return {
48
+ const result = {
49
49
  botId: config.botId,
50
50
  secret: config.secret,
51
51
  targetUserId: config.targetUserId,
52
52
  };
53
+ if (config.nameTag)
54
+ result.nameTag = config.nameTag;
55
+ if (config.doc_mcp_url)
56
+ result.doc_mcp_url = config.doc_mcp_url;
57
+ return result;
53
58
  }
54
59
  }
55
60
  }
@@ -650,6 +655,9 @@ function writeMcpServerConfig(config, instanceName) {
650
655
  if (config.nameTag) {
651
656
  botConfig.nameTag = config.nameTag;
652
657
  }
658
+ if (config.doc_mcp_url) {
659
+ botConfig.doc_mcp_url = config.doc_mcp_url;
660
+ }
653
661
  // 检查名称唯一性(如果设置了新名称)
654
662
  if (config.nameTag && isRobotNameExists(config.nameTag, config.botId)) {
655
663
  console.log(`[config] ❌ 机器人名称 "${config.nameTag}" 已被其他机器人使用`);
@@ -843,6 +851,7 @@ export function listAllRobots() {
843
851
  name,
844
852
  botId: config.botId,
845
853
  targetUserId: config.targetUserId,
854
+ ...(config.doc_mcp_url ? { doc_mcp_url: config.doc_mcp_url } : {}),
846
855
  });
847
856
  }
848
857
  catch {
@@ -860,6 +869,7 @@ export function listAllRobots() {
860
869
  name,
861
870
  botId: config.botId,
862
871
  targetUserId: config.targetUserId,
872
+ ...(config.doc_mcp_url ? { doc_mcp_url: config.doc_mcp_url } : {}),
863
873
  });
864
874
  }
865
875
  catch {
@@ -869,6 +879,35 @@ export function listAllRobots() {
869
879
  }
870
880
  return robots;
871
881
  }
882
+ // 获取指定机器人(或唯一机器人)的文档 MCP URL
883
+ export function getDocMcpUrl(robotName) {
884
+ const robots = listAllRobots();
885
+ const robotsWithDoc = robots.filter(r => r.doc_mcp_url);
886
+ if (robotsWithDoc.length === 0) {
887
+ return {
888
+ url: null,
889
+ error: '未配置文档 MCP URL。请运行 `npx @vrs-soft/wecom-aibot-mcp --add` 添加机器人时填写文档 MCP URL,或通过 `add_robot_config` 工具设置。',
890
+ };
891
+ }
892
+ if (robotName) {
893
+ const robot = robotsWithDoc.find(r => r.name === robotName);
894
+ if (!robot) {
895
+ return {
896
+ url: null,
897
+ error: `未找到名为 "${robotName}" 的机器人,或该机器人未配置文档 MCP URL。已配置文档能力的机器人: ${robotsWithDoc.map(r => r.name).join(', ')}`,
898
+ };
899
+ }
900
+ return { url: robot.doc_mcp_url };
901
+ }
902
+ if (robotsWithDoc.length === 1) {
903
+ return { url: robotsWithDoc[0].doc_mcp_url };
904
+ }
905
+ // 多个机器人有 doc_mcp_url,需要用户指定
906
+ return {
907
+ url: null,
908
+ error: `有多个机器人配置了文档 MCP URL,请通过 robot_name 参数指定使用哪个机器人。已配置文档能力的机器人: ${robotsWithDoc.map(r => r.name).join(', ')}`,
909
+ };
910
+ }
872
911
  // 写入 MCP 工具权限 + 注册 PermissionRequest hook 到 Claude settings
873
912
  function writeMcpPermissions() {
874
913
  try {
@@ -1075,7 +1114,8 @@ export async function runConfigWizard() {
1075
1114
  else {
1076
1115
  console.log('\n请选择要操作的机器人:\n');
1077
1116
  robots.forEach((robot, idx) => {
1078
- console.log(` ${idx + 1}. ${robot.name} (Bot ID: ${robot.botId.slice(0, 12)}...)`);
1117
+ const docTag = robot.doc_mcp_url ? ' [文档✅]' : '';
1118
+ console.log(` ${idx + 1}. ${robot.name} (Bot ID: ${robot.botId.slice(0, 12)}...)${docTag}`);
1079
1119
  });
1080
1120
  console.log(` ${robots.length + 1}. 添加新机器人\n`);
1081
1121
  const choice = await question(rl, '请输入序号: ');
@@ -1150,12 +1190,23 @@ export async function runConfigWizard() {
1150
1190
  }
1151
1191
  }
1152
1192
  }
1153
- // 第五步:目标用户(稍后通过消息自动识别)
1193
+ // 第五步:文档 MCP URL(可选)
1194
+ const currentDocUrl = targetRobot?.doc_mcp_url ?? '';
1195
+ const docUrlPrompt = currentDocUrl
1196
+ ? `文档 MCP URL(当前: ${currentDocUrl.slice(0, 40)}...,留空保持不变): `
1197
+ : '文档 MCP URL(可选,企业微信管理后台获取,留空跳过): ';
1198
+ let docMcpUrl = await question(rl, docUrlPrompt);
1199
+ if (!docMcpUrl && currentDocUrl) {
1200
+ docMcpUrl = currentDocUrl;
1201
+ console.log('[config] 保持原文档 MCP URL');
1202
+ }
1203
+ // 第六步:目标用户(稍后通过消息自动识别)
1154
1204
  console.log('\n─────────────────────────────────────');
1155
1205
  console.log('配置确认:');
1156
1206
  console.log(` 机器人名称: ${robotName}`);
1157
1207
  console.log(` Bot ID: ${botId}`);
1158
1208
  console.log(` Secret: ${secret.slice(0, 8)}...${secret.slice(-4)}`);
1209
+ console.log(` 文档 MCP: ${docMcpUrl ? '✅ 已配置' : '(未配置)'}`);
1159
1210
  console.log(` 目标用户: (将通过消息自动识别)`);
1160
1211
  console.log('─────────────────────────────────────\n');
1161
1212
  const confirm = await question(rl, '确认配置?(Y/n): ');
@@ -1167,8 +1218,9 @@ export async function runConfigWizard() {
1167
1218
  const config = {
1168
1219
  botId,
1169
1220
  secret,
1170
- targetUserId: '', // 稍后通过消息识别
1221
+ targetUserId: targetRobot?.targetUserId || '', // 修改时保留原值,新建时稍后识别
1171
1222
  nameTag: robotName,
1223
+ ...(docMcpUrl ? { doc_mcp_url: docMcpUrl } : {}),
1172
1224
  };
1173
1225
  // 如果是修改现有机器人,返回其 instanceName(用于删除旧配置)
1174
1226
  const instanceName = targetRobot ? targetRobot.name : 'wecom-aibot';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 文档 MCP 代理模块
3
+ *
4
+ * 将文档工具调用代理转发到机器人专属的企业微信文档 MCP 服务(StreamableHTTP)。
5
+ * 每次调用建立独立连接,无需维护长连接状态。
6
+ */
7
+ export interface DocToolResult {
8
+ content: Array<{
9
+ type: string;
10
+ text: string;
11
+ }>;
12
+ isError?: boolean;
13
+ }
14
+ /**
15
+ * 调用机器人文档 MCP 工具
16
+ *
17
+ * @param docMcpUrl 机器人专属的文档 MCP URL(含 uaKey)
18
+ * @param toolName 工具名称,如 create_doc、smartsheet_add_records 等
19
+ * @param args 工具参数
20
+ */
21
+ export declare function callDocTool(docMcpUrl: string, toolName: string, args: Record<string, unknown>): Promise<DocToolResult>;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 文档 MCP 代理模块
3
+ *
4
+ * 将文档工具调用代理转发到机器人专属的企业微信文档 MCP 服务(StreamableHTTP)。
5
+ * 每次调用建立独立连接,无需维护长连接状态。
6
+ */
7
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
9
+ import { logger } from './logger.js';
10
+ /**
11
+ * 调用机器人文档 MCP 工具
12
+ *
13
+ * @param docMcpUrl 机器人专属的文档 MCP URL(含 uaKey)
14
+ * @param toolName 工具名称,如 create_doc、smartsheet_add_records 等
15
+ * @param args 工具参数
16
+ */
17
+ export async function callDocTool(docMcpUrl, toolName, args) {
18
+ const client = new Client({ name: 'wecom-aibot-doc-proxy', version: '1.0.0' }, { capabilities: {} });
19
+ try {
20
+ const transport = new StreamableHTTPClientTransport(new URL(docMcpUrl));
21
+ await client.connect(transport);
22
+ const result = await client.callTool({ name: toolName, arguments: args });
23
+ return {
24
+ content: result.content ?? [
25
+ { type: 'text', text: JSON.stringify(result) },
26
+ ],
27
+ isError: result.isError,
28
+ };
29
+ }
30
+ catch (err) {
31
+ logger.error(`[doc-proxy] 调用 ${toolName} 失败:`, err);
32
+ const message = err?.message ?? String(err);
33
+ return {
34
+ content: [{ type: 'text', text: `文档工具调用失败: ${message}\n\n请检查机器人的文档 MCP URL 是否正确,或该机器人是否已授权文档能力。` }],
35
+ isError: true,
36
+ };
37
+ }
38
+ finally {
39
+ try {
40
+ await client.close();
41
+ }
42
+ catch {
43
+ // 关闭时忽略错误
44
+ }
45
+ }
46
+ }
@@ -31,6 +31,7 @@ export declare function pushMessageToSSEClient(robotName: string, message: {
31
31
  chatid: string;
32
32
  chattype: 'single' | 'group';
33
33
  timestamp: number;
34
+ quoteContent?: string;
34
35
  }, targetCcId?: string): Promise<void>;
35
36
  export interface ApprovalRequest {
36
37
  tool_name: string;
@@ -109,6 +109,7 @@ export async function pushMessageToSSEClient(robotName, message, targetCcId) {
109
109
  chatid: message.chatid,
110
110
  chattype: message.chattype,
111
111
  time: new Date(message.timestamp).toISOString(),
112
+ quoteContent: message.quoteContent,
112
113
  },
113
114
  });
114
115
  client.res.write(`event: message\ndata: ${data}\n\n`);
@@ -227,6 +228,7 @@ async function handleWecomMessage(msg) {
227
228
  chatid: msg.chatid,
228
229
  chattype: msg.chattype,
229
230
  timestamp: msg.timestamp,
231
+ quoteContent: msg.quoteContent,
230
232
  }, targetCcId);
231
233
  return;
232
234
  }
@@ -251,6 +253,7 @@ async function handleWecomMessage(msg) {
251
253
  chatid: msg.chatid,
252
254
  chattype: msg.chattype,
253
255
  timestamp: msg.timestamp,
256
+ quoteContent: msg.quoteContent,
254
257
  }, ccId);
255
258
  return;
256
259
  }
@@ -267,6 +270,7 @@ async function handleWecomMessage(msg) {
267
270
  chatid: msg.chatid,
268
271
  chattype: msg.chattype,
269
272
  timestamp: msg.timestamp,
273
+ quoteContent: msg.quoteContent,
270
274
  }, targetCcId);
271
275
  return;
272
276
  }
@@ -284,6 +288,7 @@ async function handleWecomMessage(msg) {
284
288
  chatid: msg.chatid,
285
289
  chattype: msg.chattype,
286
290
  timestamp: msg.timestamp,
291
+ quoteContent: msg.quoteContent,
287
292
  }, matchedCcId);
288
293
  return;
289
294
  }
@@ -383,6 +388,7 @@ async function handleWecomMessage(msg) {
383
388
  chatid: msg.chatid,
384
389
  chattype: msg.chattype,
385
390
  timestamp: msg.timestamp,
391
+ quoteContent: msg.quoteContent,
386
392
  }, matchedCcId);
387
393
  }
388
394
  else {
@@ -20,7 +20,8 @@
20
20
  * - 从 Session 自动获取 robotName
21
21
  */
22
22
  import { z } from 'zod';
23
- import { listAllRobots, installSkill } from '../config-wizard.js';
23
+ import { listAllRobots, getDocMcpUrl, installSkill } from '../config-wizard.js';
24
+ import { callDocTool } from '../doc-proxy.js';
24
25
  import { connectRobot, disconnectRobot, getClient, getConnectionState, } from '../connection-manager.js';
25
26
  import { registerCcId, unregisterCcId, getRobotByCcId, getProjectDirByCcId, generateCcId, } from '../http-server.js';
26
27
  import { subscribeWecomMessageByCcId } from '../message-bus.js';
@@ -312,13 +313,15 @@ npx @vrs-soft/wecom-aibot-mcp
312
313
  bot_id: z.string().describe('企业微信 Bot ID'),
313
314
  secret: z.string().describe('机器人密钥'),
314
315
  default_user: z.string().optional().describe('默认目标用户'),
315
- }, async ({ name, bot_id, secret, default_user }) => {
316
+ doc_mcp_url: z.string().optional().describe('机器人文档 MCP URL(企业微信文档能力)'),
317
+ }, async ({ name, bot_id, secret, default_user, doc_mcp_url }) => {
316
318
  return {
317
319
  content: [{
318
320
  type: 'text',
319
321
  text: JSON.stringify({
320
322
  message: '请使用 --add 命令添加机器人配置',
321
323
  command: `npx @vrs-soft/wecom-aibot-mcp --add`,
324
+ tip: doc_mcp_url ? '运行向导时在"文档 MCP URL"提示处粘贴您的 URL' : undefined,
322
325
  }, null, 2),
323
326
  }],
324
327
  };
@@ -334,6 +337,7 @@ npx @vrs-soft/wecom-aibot-mcp
334
337
  name: robot.name,
335
338
  botId: robot.botId?.slice(0, 12) + '...', // 只显示前12位
336
339
  targetUser: robot.targetUserId,
340
+ hasDocMcp: !!robot.doc_mcp_url, // 是否配置了文档 MCP 能力
337
341
  }));
338
342
  return {
339
343
  content: [{
@@ -413,11 +417,11 @@ npx @vrs-soft/wecom-aibot-mcp
413
417
  }],
414
418
  };
415
419
  }
416
- // 如果用户传入 cc_id,直接使用;否则自动生成
417
- const finalCcId = cc_id || generateCcId(effectiveAgentName);
418
- // 检查 wecom-aibot.json 是否已有匹配的 ccId(重连场景)
420
+ // 检查 wecom-aibot.json 是否已有 ccId(重连场景)
419
421
  const existingConfig = loadWechatModeConfig(projectDir);
420
- const isReconnect = existingConfig?.ccId === finalCcId;
422
+ // 优先级:cc_id 参数 > (非 agent_name 指定时) 配置文件已有 ccId > 自动生成
423
+ const finalCcId = cc_id || (!agent_name && existingConfig?.ccId) || generateCcId(effectiveAgentName);
424
+ const isReconnect = !!existingConfig?.ccId && existingConfig.ccId === finalCcId;
421
425
  // 注册 ccId 到 CC 注册表(重连直接覆盖,首次注册清理超时条目)
422
426
  registerCcId(finalCcId, selectedRobot.name, effectiveAgentName, mode, projectDir, isReconnect);
423
427
  // 更新项目配置文件中的 wechatMode 为 true
@@ -598,5 +602,205 @@ npx @vrs-soft/wecom-aibot-mcp
598
602
  }],
599
603
  };
600
604
  });
601
- logger.log('[mcp] 已注册 14 个工具');
605
+ // ============================================
606
+ // 文档代理工具(企业微信文档 MCP 能力)
607
+ // 代理转发到机器人专属的 doc_mcp_url
608
+ // ============================================
609
+ // 公共辅助:解析 doc MCP URL
610
+ function resolveDocUrl(robotName) {
611
+ const { url, error } = getDocMcpUrl(robotName);
612
+ if (!url) {
613
+ return {
614
+ url: null,
615
+ errorContent: { content: [{ type: 'text', text: error ?? '未配置文档 MCP URL' }] },
616
+ };
617
+ }
618
+ return { url };
619
+ }
620
+ server.tool('create_doc', '新建文档或智能表格。doc_type: 3=文档, 10=智能表格。创建成功后返回文档链接。', {
621
+ doc_type: z.number().int().describe('文档类型:3=文档,10=智能表格'),
622
+ doc_name: z.string().describe('文档名称,最多 255 个字符'),
623
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
624
+ }, async ({ doc_type, doc_name, robot_name }) => {
625
+ const { url, errorContent } = resolveDocUrl(robot_name);
626
+ if (!url)
627
+ return errorContent;
628
+ return callDocTool(url, 'create_doc', { doc_type, doc_name });
629
+ });
630
+ server.tool('get_doc_content', '获取企业微信文档内容(Markdown 格式)。支持异步轮询:首次调用无需 task_id,若 task_done 为 false 则带 task_id 继续轮询。', {
631
+ type: z.number().int().describe('内容格式:2=Markdown'),
632
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
633
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
634
+ task_id: z.string().optional().describe('任务 ID(轮询时填写)'),
635
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
636
+ }, async ({ type, url: docUrl, docid, task_id, robot_name }) => {
637
+ const { url, errorContent } = resolveDocUrl(robot_name);
638
+ if (!url)
639
+ return errorContent;
640
+ return callDocTool(url, 'get_doc_content', { type, url: docUrl, docid, task_id });
641
+ });
642
+ server.tool('edit_doc_content', '编辑企业微信文档内容,支持 Markdown 格式覆写。', {
643
+ content: z.string().describe('覆写的文档内容'),
644
+ content_type: z.number().int().describe('内容类型:1=Markdown'),
645
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
646
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
647
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
648
+ }, async ({ content, content_type, url: docUrl, docid, robot_name }) => {
649
+ const { url, errorContent } = resolveDocUrl(robot_name);
650
+ if (!url)
651
+ return errorContent;
652
+ return callDocTool(url, 'edit_doc_content', { content, content_type, url: docUrl, docid });
653
+ });
654
+ server.tool('smartsheet_get_sheet', '查询智能表格中的子表信息。', {
655
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
656
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
657
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
658
+ }, async ({ url: docUrl, docid, robot_name }) => {
659
+ const { url, errorContent } = resolveDocUrl(robot_name);
660
+ if (!url)
661
+ return errorContent;
662
+ return callDocTool(url, 'smartsheet_get_sheet', { url: docUrl, docid });
663
+ });
664
+ server.tool('smartsheet_add_sheet', '在智能表格内添加新子表。', {
665
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
666
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
667
+ properties: z.object({ title: z.string().optional() }).optional().describe('子表属性,可设置 title'),
668
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
669
+ }, async ({ url: docUrl, docid, properties, robot_name }) => {
670
+ const { url, errorContent } = resolveDocUrl(robot_name);
671
+ if (!url)
672
+ return errorContent;
673
+ return callDocTool(url, 'smartsheet_add_sheet', { url: docUrl, docid, properties });
674
+ });
675
+ server.tool('smartsheet_update_sheet', '修改智能表格子表的标题。', {
676
+ properties: z.object({
677
+ sheet_id: z.string().describe('子表 ID'),
678
+ title: z.string().describe('新标题'),
679
+ }).describe('子表属性'),
680
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
681
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
682
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
683
+ }, async ({ properties, url: docUrl, docid, robot_name }) => {
684
+ const { url, errorContent } = resolveDocUrl(robot_name);
685
+ if (!url)
686
+ return errorContent;
687
+ return callDocTool(url, 'smartsheet_update_sheet', { properties, url: docUrl, docid });
688
+ });
689
+ server.tool('smartsheet_delete_sheet', '删除智能表格中的子表(不可逆)。', {
690
+ sheet_id: z.string().describe('要删除的子表 ID'),
691
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
692
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
693
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
694
+ }, async ({ sheet_id, url: docUrl, docid, robot_name }) => {
695
+ const { url, errorContent } = resolveDocUrl(robot_name);
696
+ if (!url)
697
+ return errorContent;
698
+ return callDocTool(url, 'smartsheet_delete_sheet', { sheet_id, url: docUrl, docid });
699
+ });
700
+ server.tool('smartsheet_get_fields', '获取智能表格子表的字段(列)信息。', {
701
+ sheet_id: z.string().describe('子表 ID'),
702
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
703
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
704
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
705
+ }, async ({ sheet_id, url: docUrl, docid, robot_name }) => {
706
+ const { url, errorContent } = resolveDocUrl(robot_name);
707
+ if (!url)
708
+ return errorContent;
709
+ return callDocTool(url, 'smartsheet_get_fields', { sheet_id, url: docUrl, docid });
710
+ });
711
+ server.tool('smartsheet_add_fields', '向智能表格子表添加字段(列)。调用前必须先用 smartsheet_get_fields 查看现有字段并用 smartsheet_update_fields 重命名默认字段,否则会多出无用列。', {
712
+ sheet_id: z.string().describe('子表 ID'),
713
+ fields: z.array(z.object({
714
+ field_title: z.string(),
715
+ field_type: z.string(),
716
+ })).describe('要添加的字段列表'),
717
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
718
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
719
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
720
+ }, async ({ sheet_id, fields, url: docUrl, docid, robot_name }) => {
721
+ const { url, errorContent } = resolveDocUrl(robot_name);
722
+ if (!url)
723
+ return errorContent;
724
+ return callDocTool(url, 'smartsheet_add_fields', { sheet_id, fields, url: docUrl, docid });
725
+ });
726
+ server.tool('smartsheet_update_fields', '更新智能表格子表字段的标题(不能更改字段类型)。', {
727
+ sheet_id: z.string().describe('子表 ID'),
728
+ fields: z.array(z.object({
729
+ field_id: z.string(),
730
+ field_title: z.string(),
731
+ field_type: z.string(),
732
+ })).describe('要更新的字段列表'),
733
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
734
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
735
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
736
+ }, async ({ sheet_id, fields, url: docUrl, docid, robot_name }) => {
737
+ const { url, errorContent } = resolveDocUrl(robot_name);
738
+ if (!url)
739
+ return errorContent;
740
+ return callDocTool(url, 'smartsheet_update_fields', { sheet_id, fields, url: docUrl, docid });
741
+ });
742
+ server.tool('smartsheet_delete_fields', '删除智能表格子表中的字段(列)(不可逆)。', {
743
+ sheet_id: z.string().describe('子表 ID'),
744
+ field_ids: z.array(z.string()).describe('要删除的字段 ID 列表'),
745
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
746
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
747
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
748
+ }, async ({ sheet_id, field_ids, url: docUrl, docid, robot_name }) => {
749
+ const { url, errorContent } = resolveDocUrl(robot_name);
750
+ if (!url)
751
+ return errorContent;
752
+ return callDocTool(url, 'smartsheet_delete_fields', { sheet_id, field_ids, url: docUrl, docid });
753
+ });
754
+ server.tool('smartsheet_get_records', '查询智能表格子表的所有记录。', {
755
+ sheet_id: z.string().describe('子表 ID'),
756
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
757
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
758
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
759
+ }, async ({ sheet_id, url: docUrl, docid, robot_name }) => {
760
+ const { url, errorContent } = resolveDocUrl(robot_name);
761
+ if (!url)
762
+ return errorContent;
763
+ return callDocTool(url, 'smartsheet_get_records', { sheet_id, url: docUrl, docid });
764
+ });
765
+ server.tool('smartsheet_add_records', '向智能表格子表添加记录(行)。单次建议不超过 500 行。values 的 key 必须是字段标题(field_title),不能用 field_id。', {
766
+ sheet_id: z.string().describe('子表 ID'),
767
+ records: z.array(z.object({ values: z.record(z.string(), z.unknown()) })).describe('记录列表,values key 为字段标题'),
768
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
769
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
770
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
771
+ }, async ({ sheet_id, records, url: docUrl, docid, robot_name }) => {
772
+ const { url, errorContent } = resolveDocUrl(robot_name);
773
+ if (!url)
774
+ return errorContent;
775
+ return callDocTool(url, 'smartsheet_add_records', { sheet_id, records, url: docUrl, docid });
776
+ });
777
+ server.tool('smartsheet_update_records', '更新智能表格子表中的记录(行)。单次建议不超过 500 行。', {
778
+ sheet_id: z.string().describe('子表 ID'),
779
+ records: z.array(z.object({
780
+ record_id: z.string(),
781
+ values: z.record(z.string(), z.unknown()),
782
+ })).describe('要更新的记录列表,含 record_id 和 values'),
783
+ key_type: z.enum(['CELL_VALUE_KEY_TYPE_FIELD_TITLE', 'CELL_VALUE_KEY_TYPE_FIELD_ID']).describe('values 的 key 类型'),
784
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
785
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
786
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
787
+ }, async ({ sheet_id, records, key_type, url: docUrl, docid, robot_name }) => {
788
+ const { url, errorContent } = resolveDocUrl(robot_name);
789
+ if (!url)
790
+ return errorContent;
791
+ return callDocTool(url, 'smartsheet_update_records', { sheet_id, records, key_type, url: docUrl, docid });
792
+ });
793
+ server.tool('smartsheet_delete_records', '删除智能表格子表中的记录(行)(不可逆)。单次建议不超过 500 行。', {
794
+ sheet_id: z.string().describe('子表 ID'),
795
+ record_ids: z.array(z.string()).describe('要删除的记录 ID 列表'),
796
+ url: z.string().optional().describe('文档访问链接,与 docid 二选一'),
797
+ docid: z.string().optional().describe('文档 docid,与 url 二选一'),
798
+ robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
799
+ }, async ({ sheet_id, record_ids, url: docUrl, docid, robot_name }) => {
800
+ const { url, errorContent } = resolveDocUrl(robot_name);
801
+ if (!url)
802
+ return errorContent;
803
+ return callDocTool(url, 'smartsheet_delete_records', { sheet_id, record_ids, url: docUrl, docid });
804
+ });
805
+ logger.log('[mcp] 已注册 29 个工具(含 15 个文档代理工具)');
602
806
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrs-soft/wecom-aibot-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "企业微信智能机器人 MCP 服务 - Claude Code 审批通道",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",