@vrs-soft/wecom-aibot-mcp 2.6.0 → 3.1.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/dist/bin.d.ts +7 -6
- package/dist/bin.js +43 -800
- package/dist/channel-server.js +115 -17
- package/dist/config-wizard.d.ts +2 -61
- package/dist/config-wizard.js +22 -915
- package/dist/http-server.d.ts +6 -0
- package/dist/http-server.js +56 -0
- package/dist/index.d.ts +5 -15
- package/dist/index.js +5 -21
- package/dist/tools/index.js +14 -4
- package/package.json +4 -7
package/dist/channel-server.js
CHANGED
|
@@ -327,19 +327,48 @@ function connectSSE(ccId) {
|
|
|
327
327
|
let notification;
|
|
328
328
|
if (currentEvent === 'cc_message') {
|
|
329
329
|
// CC 间消息:用 cc:<fromCc> 作为 source 前缀,便于 agent 区分非 wecom 来源
|
|
330
|
+
const meta = {
|
|
331
|
+
source: `cc:${msg.fromCc || ''}`,
|
|
332
|
+
from_cc: msg.fromCc || '',
|
|
333
|
+
to_cc: msg.toCc || '',
|
|
334
|
+
chattype: 'cc',
|
|
335
|
+
cc_id: msg.toCc || '',
|
|
336
|
+
kind: msg.kind || 'notify',
|
|
337
|
+
reply_to: msg.replyTo || '',
|
|
338
|
+
msg_id: msg.msgId || '',
|
|
339
|
+
};
|
|
340
|
+
if (Array.isArray(msg.attachments) && msg.attachments.length > 0) {
|
|
341
|
+
meta.attachments_json = JSON.stringify(msg.attachments);
|
|
342
|
+
meta.attachment_count = String(msg.attachments.length);
|
|
343
|
+
}
|
|
330
344
|
notification = {
|
|
331
345
|
method: 'notifications/claude/channel',
|
|
332
346
|
params: {
|
|
333
347
|
content: msg.content || '',
|
|
348
|
+
meta,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
else if (currentEvent === 'cc_document_notify') {
|
|
353
|
+
// CC 间文档通知:只含元数据,agent 收到后按需 fetch_document(doc_id)
|
|
354
|
+
const sizeKb = Math.max(1, Math.round((msg.size || 0) / 1024));
|
|
355
|
+
const text = `📎 收到文档「${msg.title || ''}」(${msg.mimeType || ''}, ~${sizeKb} KB),发送方=${msg.fromCc || ''},docId=${msg.docId || ''}。调用 fetch_document(cc_id, doc_id="${msg.docId || ''}") 取内容。`;
|
|
356
|
+
notification = {
|
|
357
|
+
method: 'notifications/claude/channel',
|
|
358
|
+
params: {
|
|
359
|
+
content: text,
|
|
334
360
|
meta: {
|
|
335
361
|
source: `cc:${msg.fromCc || ''}`,
|
|
336
362
|
from_cc: msg.fromCc || '',
|
|
337
363
|
to_cc: msg.toCc || '',
|
|
338
364
|
chattype: 'cc',
|
|
339
365
|
cc_id: msg.toCc || '',
|
|
340
|
-
kind:
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
kind: 'document',
|
|
367
|
+
doc_id: msg.docId || '',
|
|
368
|
+
title: msg.title || '',
|
|
369
|
+
mime_type: msg.mimeType || '',
|
|
370
|
+
size: String(msg.size || 0),
|
|
371
|
+
expires_at: String(msg.expiresAt || 0),
|
|
343
372
|
},
|
|
344
373
|
},
|
|
345
374
|
};
|
|
@@ -454,17 +483,80 @@ function registerChannelTools(server) {
|
|
|
454
483
|
// ============================================
|
|
455
484
|
// 工具 4a: CC 间通信 — send_to_cc / list_active_ccs(v2.6.0+)
|
|
456
485
|
// ============================================
|
|
457
|
-
server.tool('send_to_cc', '向同一 daemon 上的另一个 CC 发送消息。目标 CC 收到时会作为 <channel source="cc:..."> 推送唤醒。仅支持同 daemon
|
|
486
|
+
server.tool('send_to_cc', '向同一 daemon 上的另一个 CC 发送消息。目标 CC 收到时会作为 <channel source="cc:..."> 推送唤醒。仅支持同 daemon 间互通。支持 attachments 内联小文档(每个 < 16 KB);大文档请改用 upload_document。', {
|
|
458
487
|
cc_id: z.string().describe('自己的 CC 标识'),
|
|
459
488
|
to_cc: z.string().describe('目标 CC 标识'),
|
|
460
489
|
content: z.string().describe('消息内容(支持 Markdown)'),
|
|
461
490
|
kind: z.enum(['request', 'reply', 'notify']).optional().default('notify').describe('消息语义'),
|
|
462
491
|
reply_to: z.string().optional().describe('可选:关联的请求 msgId'),
|
|
492
|
+
attachments: z.array(z.object({
|
|
493
|
+
title: z.string(),
|
|
494
|
+
content: z.string(),
|
|
495
|
+
mimeType: z.string().optional(),
|
|
496
|
+
})).optional().describe('可选:内联附件,每个 content < 16 KB'),
|
|
463
497
|
}, async (params) => forwardToHttpMcp('send_to_cc', params));
|
|
464
498
|
server.tool('list_active_ccs', '列出同一 daemon 上当前在线的所有 CC', {
|
|
465
499
|
cc_id: z.string().describe('自己的 CC 标识'),
|
|
466
500
|
}, async (params) => forwardToHttpMcp('list_active_ccs', params));
|
|
467
501
|
// ============================================
|
|
502
|
+
// CC 间文档传输(v2.7.0+)— upload_document / fetch_document / list_documents
|
|
503
|
+
// ============================================
|
|
504
|
+
server.tool('upload_document', 'CC 间传输较大文档(> 16 KB 或二进制)。服务端暂存(默认 30 分钟 TTL),向目标 CC 推送 cc_document_notify 事件,接收方按需 fetch_document(docId) 取内容。', {
|
|
505
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
506
|
+
to_cc: z.string().describe('目标 CC 标识'),
|
|
507
|
+
title: z.string().describe('文档标题'),
|
|
508
|
+
content: z.string().describe('文档内容。文本直接传字符串;二进制(图片、PDF 等)传 base64 并设 encoding=base64'),
|
|
509
|
+
mime_type: z.string().optional().describe('MIME 类型,如 text/markdown / image/png'),
|
|
510
|
+
encoding: z.enum(['utf8', 'base64']).optional().default('utf8').describe('内容编码'),
|
|
511
|
+
ttl_seconds: z.number().int().min(60).max(86400).optional().describe('暂存秒数,默认 1800,最大 86400'),
|
|
512
|
+
}, async (params) => forwardToHttpMcp('upload_document', params));
|
|
513
|
+
server.tool('fetch_document', '按 docId 拉取 CC 间暂存的文档内容。仅文档接收方(to_cc)可取。图片自动返回 MCP image content(Claude 可视觉识别)。', {
|
|
514
|
+
cc_id: z.string().describe('自己的 CC 标识(必须 = 文档的 to_cc)'),
|
|
515
|
+
doc_id: z.string().describe('文档 ID(从 cc_document_notify 或 list_documents 获得)'),
|
|
516
|
+
}, async (params) => forwardToHttpMcp('fetch_document', params));
|
|
517
|
+
server.tool('list_documents', '列出当前 CC 收到的所有未过期文档元数据(不含 content)。', {
|
|
518
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
519
|
+
from_cc: z.string().optional().describe('可选:只列指定发送方的文档'),
|
|
520
|
+
include_expired: z.boolean().optional().describe('是否包含已过期,默认 false'),
|
|
521
|
+
}, async (params) => forwardToHttpMcp('list_documents', params));
|
|
522
|
+
// ============================================
|
|
523
|
+
// 共享文件池(v2.7.0+)— share_file / fetch_shared_file / list_shared_files
|
|
524
|
+
// 与 upload_document 不同:无指定接收方,所有 CC 可见
|
|
525
|
+
// ============================================
|
|
526
|
+
server.tool('share_file', '上传一个共享文件到 MCP 服务端,所有在线 CC 都可通过 fetch_shared_file 取用。支持 inline content 或 file_path(server 直读,避免大文件经 Claude context)。默认 5 MB / 30 分钟 TTL。', {
|
|
527
|
+
cc_id: z.string().describe('自己的 CC 标识(owner)'),
|
|
528
|
+
title: z.string().describe('文件标题'),
|
|
529
|
+
content: z.string().optional().describe('二选一:内联内容'),
|
|
530
|
+
file_path: z.string().optional().describe('二选一:server 端文件绝对路径(大文件用,避免走 Claude context)'),
|
|
531
|
+
mime_type: z.string().optional().describe('MIME 类型;file_path 模式按扩展名推断'),
|
|
532
|
+
encoding: z.enum(['utf8', 'base64']).optional().describe('content 模式下的编码;file_path 自动判定'),
|
|
533
|
+
ttl_seconds: z.number().int().min(60).max(86400).optional().describe('暂存秒数,默认 1800'),
|
|
534
|
+
tags: z.array(z.string()).optional().describe('可选标签'),
|
|
535
|
+
}, async (params) => forwardToHttpMcp('share_file', params));
|
|
536
|
+
server.tool('fetch_shared_file', '按 fileId 拉取共享文件内容。任何在线 CC 都能取。图片自动返回 MCP image content(Claude 可视觉识别)。', {
|
|
537
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
538
|
+
file_id: z.string().describe('共享文件 ID'),
|
|
539
|
+
}, async (params) => forwardToHttpMcp('fetch_shared_file', params));
|
|
540
|
+
server.tool('list_shared_files', '列出共享文件池中未过期的文件元数据(不含 content)。', {
|
|
541
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
542
|
+
owner: z.string().optional().describe('可选:按 owner 过滤'),
|
|
543
|
+
tag: z.string().optional().describe('可选:按标签过滤'),
|
|
544
|
+
include_expired: z.boolean().optional().describe('是否包含已过期,默认 false'),
|
|
545
|
+
}, async (params) => forwardToHttpMcp('list_shared_files', params));
|
|
546
|
+
// ============================================
|
|
547
|
+
// 接收方落盘工具(v2.7.0+)
|
|
548
|
+
// ============================================
|
|
549
|
+
server.tool('accept_document', '接受 upload_document 推来的文档:fetch 内容 + 落盘到 {projectDir}/received-file/。【强制】必须先通过 send_message 询问用户、得到肯定回复后才能调用。', {
|
|
550
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
551
|
+
doc_id: z.string().describe('文档 ID'),
|
|
552
|
+
save_as: z.string().optional().describe('可选:自定义文件名'),
|
|
553
|
+
}, async (params) => forwardToHttpMcp('accept_document', params));
|
|
554
|
+
server.tool('accept_shared_file', '从共享池下载文件并落盘到 {projectDir}/received-file/。共享池为 pull 模型,无强制询问。', {
|
|
555
|
+
cc_id: z.string().describe('自己的 CC 标识'),
|
|
556
|
+
file_id: z.string().describe('共享文件 ID'),
|
|
557
|
+
save_as: z.string().optional().describe('可选:自定义文件名'),
|
|
558
|
+
}, async (params) => forwardToHttpMcp('accept_shared_file', params));
|
|
559
|
+
// ============================================
|
|
468
560
|
// 工具 4: 获取待处理消息
|
|
469
561
|
// ============================================
|
|
470
562
|
server.tool('get_pending_messages', '获取待处理的微信消息。支持长轮询:传入 timeout_ms 后阻塞等待,有消息立即返回,无消息等到超时。超时后继续轮询,不要停止。', {
|
|
@@ -542,15 +634,21 @@ function registerChannelTools(server) {
|
|
|
542
634
|
const hookResult = addPermissionHook(localProjectDir);
|
|
543
635
|
logger.info('本地 PermissionRequest hook 已写入', { path: hookResult.path, success: hookResult.success });
|
|
544
636
|
// 注册 Claude TUI 的 PID(不能用 process.ppid,npx 部署时 ppid 是 npx 不是 Claude)
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
637
|
+
// WECOM_TEST_NO_REGISTER=1 时跳过注册,防止集成测试污染宿主机 active-projects.json
|
|
638
|
+
if (!process.env.WECOM_TEST_NO_REGISTER) {
|
|
639
|
+
const startPid = process.ppid ?? process.pid;
|
|
640
|
+
const claudePid = findClaudePid(startPid);
|
|
641
|
+
registerActiveProject(claudePid, localProjectDir);
|
|
642
|
+
logger.info('本地 active-projects 已注册', {
|
|
643
|
+
pid: claudePid,
|
|
644
|
+
rawPpid: startPid,
|
|
645
|
+
resolvedClaudePid: claudePid !== startPid,
|
|
646
|
+
projectDir: localProjectDir,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
logger.info('WECOM_TEST_NO_REGISTER=1,跳过 active-projects 注册', { projectDir: localProjectDir });
|
|
651
|
+
}
|
|
554
652
|
// 写入本地 wecom-aibot.json(远程 HTTP MCP 写在远端 fs,agent 本地需要自己落地)
|
|
555
653
|
updateWechatModeConfig(localProjectDir, {
|
|
556
654
|
wechatMode: true,
|
|
@@ -643,19 +741,19 @@ function registerChannelTools(server) {
|
|
|
643
741
|
// 文档代理工具(转发到 HTTP MCP)
|
|
644
742
|
// ============================================
|
|
645
743
|
const docTools = [
|
|
646
|
-
['
|
|
744
|
+
['create_wecom_doc', '新建企业微信在线文档或智能表格', {
|
|
647
745
|
doc_type: z.number().int().describe('文档类型:3=文档,10=智能表格'),
|
|
648
746
|
doc_name: z.string().describe('文档名称'),
|
|
649
747
|
robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
|
|
650
748
|
}],
|
|
651
|
-
['
|
|
749
|
+
['get_wecom_doc_content', '获取企业微信在线文档内容(Markdown 格式)', {
|
|
652
750
|
type: z.number().int().describe('内容格式:2=Markdown'),
|
|
653
751
|
url: z.string().optional().describe('文档链接'),
|
|
654
752
|
docid: z.string().optional().describe('文档 docid'),
|
|
655
753
|
task_id: z.string().optional().describe('任务 ID(轮询时填写)'),
|
|
656
754
|
robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
|
|
657
755
|
}],
|
|
658
|
-
['
|
|
756
|
+
['edit_wecom_doc_content', '编辑企业微信在线文档内容(Markdown 格式覆写)', {
|
|
659
757
|
content: z.string().describe('覆写的文档内容'),
|
|
660
758
|
content_type: z.number().int().describe('内容类型:1=Markdown'),
|
|
661
759
|
url: z.string().optional().describe('文档链接'),
|
|
@@ -747,7 +845,7 @@ export async function startChannelServer() {
|
|
|
747
845
|
tools: {},
|
|
748
846
|
},
|
|
749
847
|
// 告知 Claude 如何处理 channel 事件
|
|
750
|
-
instructions: '企业微信消息通过 <channel> 标签推送。属性说明:from=发送者userid, chatid=会话ID(单聊=用户ID,群聊=群ID), chattype=single|group, cc_id
|
|
848
|
+
instructions: '企业微信消息通过 <channel> 标签推送。属性说明:from=发送者userid, chatid=会话ID(单聊=用户ID,群聊=群ID), chattype=single|group, cc_id=当前会话标识。【强制规则1·用户消息】收到任何用户消息后,必须先执行步骤1再执行步骤2,禁止跳过:1) 立即发送确认 send_message(cc_id, "收到,正在处理...", target_user=chatid);2) 处理任务;3) 发送结果 send_message(cc_id, "【完成】...", target_user=chatid)。【强制规则2·CC 间文档】收到 <channel kind="document"> 通知(cc_document_notify)时,禁止擅自落盘:1) 立即 send_message 告知用户("CC <from_cc> 想发送文件「<title>」(<size>, <mime_type>),是否接收?"),target_user 取当前 cc 的 chatid;2) 等用户明确肯定回复(是/接受/yes/同意等);3) 同意后调 accept_document(cc_id, doc_id) 落盘到 {projectDir}/received-file/;4) 发送完成消息并附 saved_path;拒绝则忽略 doc_id 不调 fetch_document。共享池 share_file/fetch_shared_file 为 pull 模型,agent 主动决定,无需询问。',
|
|
751
849
|
});
|
|
752
850
|
// 注册工具
|
|
753
851
|
registerChannelTools(mcpServer);
|
package/dist/config-wizard.d.ts
CHANGED
|
@@ -1,46 +1,11 @@
|
|
|
1
|
-
export interface WecomConfig {
|
|
2
|
-
botId: string;
|
|
3
|
-
secret: string;
|
|
4
|
-
targetUserId: string;
|
|
5
|
-
targetUserName?: string;
|
|
6
|
-
nameTag?: string;
|
|
7
|
-
doc_mcp_url?: string;
|
|
8
|
-
}
|
|
9
1
|
export declare const VERSION: string;
|
|
10
|
-
export declare function loadConfig(): WecomConfig | null;
|
|
11
2
|
export declare function getAuthToken(): string | undefined;
|
|
12
|
-
export declare function setAuthToken(token: string | undefined): boolean;
|
|
13
|
-
export declare function getHttpsConfig(): {
|
|
14
|
-
certPath: string;
|
|
15
|
-
keyPath: string;
|
|
16
|
-
} | null;
|
|
17
|
-
export declare function setHttpsConfig(certPath: string, keyPath: string): boolean;
|
|
18
|
-
export declare function updateMcpAuthHeaders(token?: string): void;
|
|
19
|
-
export declare function listAllMcpInstances(): Array<{
|
|
20
|
-
name: string;
|
|
21
|
-
config: WecomConfig;
|
|
22
|
-
}>;
|
|
23
3
|
export declare function deleteConfig(): void;
|
|
24
4
|
export declare function deleteHook(): void;
|
|
25
5
|
export declare function deleteSkills(): void;
|
|
26
|
-
export declare function deleteRobotConfig(robotName: string): boolean;
|
|
27
|
-
export declare function deleteMcpConfig(instanceName: string): boolean;
|
|
28
|
-
export declare function deleteRobotConfigInteractive(robotName?: string): Promise<void>;
|
|
29
|
-
export declare function deleteMcpConfigInteractive(instanceName?: string): Promise<void>;
|
|
30
6
|
export declare function uninstall(): void;
|
|
31
|
-
export declare function addMcpConfig(): Promise<void>;
|
|
32
|
-
export declare function listAllRobots(): Array<{
|
|
33
|
-
name: string;
|
|
34
|
-
botId: string;
|
|
35
|
-
targetUserId: string;
|
|
36
|
-
doc_mcp_url?: string;
|
|
37
|
-
}>;
|
|
38
|
-
export declare function getDocMcpUrl(robotName?: string): {
|
|
39
|
-
url: string | null;
|
|
40
|
-
error?: string;
|
|
41
|
-
};
|
|
42
7
|
export declare function ensureHookInstalled(): void;
|
|
43
|
-
export type InstallMode = '
|
|
8
|
+
export type InstallMode = 'channel-only' | 'remote' | 'remote-channel';
|
|
44
9
|
export declare function getInstalledMode(): {
|
|
45
10
|
mode?: InstallMode;
|
|
46
11
|
remote?: {
|
|
@@ -55,8 +20,7 @@ export declare function ensureGlobalConfigs(mode?: InstallMode, remoteOptions?:
|
|
|
55
20
|
upgraded: boolean;
|
|
56
21
|
previousVersion?: string;
|
|
57
22
|
};
|
|
58
|
-
export declare function runRemoteInstallWizard(): Promise<'remote' | 'remote-channel' |
|
|
59
|
-
export declare function saveConfig(config: WecomConfig, instanceName?: string): boolean;
|
|
23
|
+
export declare function runRemoteInstallWizard(): Promise<'remote' | 'remote-channel' | null>;
|
|
60
24
|
/**
|
|
61
25
|
* 安装 headless-mode skill 到项目目录
|
|
62
26
|
* 返回:{ success, skillUrl? } - 如果模板不存在,返回 HTTP endpoint URL
|
|
@@ -66,26 +30,3 @@ export declare function installSkill(projectDir: string): {
|
|
|
66
30
|
skillUrl?: string;
|
|
67
31
|
message?: string;
|
|
68
32
|
};
|
|
69
|
-
/**
|
|
70
|
-
* 运行配置向导
|
|
71
|
-
*/
|
|
72
|
-
export declare function runConfigWizard(): Promise<{
|
|
73
|
-
config: WecomConfig;
|
|
74
|
-
instanceName: string;
|
|
75
|
-
}>;
|
|
76
|
-
export declare function findRobotConfigFile(robotName: string): string | null;
|
|
77
|
-
export declare function findRobotConfigFileByBotId(botId: string): string | null;
|
|
78
|
-
export declare function isRobotNameExists(name: string, excludeBotId?: string): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* 通过等待用户消息来识别用户 ID(使用已有的 client)
|
|
81
|
-
*/
|
|
82
|
-
export declare function detectUserIdFromMessage(client: any, timeoutSeconds?: number): Promise<string | null>;
|
|
83
|
-
/**
|
|
84
|
-
* 检查并获取配置
|
|
85
|
-
*
|
|
86
|
-
* 优先级:
|
|
87
|
-
* 1. 环境变量(WECOM_BOT_ID, WECOM_SECRET, WECOM_TARGET_USER)
|
|
88
|
-
* 2. 保存的配置文件(~/.wecom-aibot-mcp/robot-*.json)
|
|
89
|
-
* 3. 运行配置向导
|
|
90
|
-
*/
|
|
91
|
-
export declare function getOrInitConfig(): Promise<WecomConfig>;
|