@vrs-soft/wecom-aibot-mcp 2.3.2 → 2.4.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 +51 -46
- package/dist/bin.js +154 -22
- package/dist/channel-server.js +125 -6
- package/dist/client-pool.d.ts +1 -0
- package/dist/config-wizard.d.ts +16 -2
- package/dist/config-wizard.js +283 -115
- package/dist/connection-manager.js +2 -2
- package/dist/daemon.js +1 -17
- package/dist/doc-proxy.d.ts +21 -0
- package/dist/doc-proxy.js +46 -0
- package/dist/http-server.js +11 -2
- package/dist/tools/index.js +212 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,34 +4,62 @@
|
|
|
4
4
|
|
|
5
5
|
企业微信智能机器人 MCP 服务 - 让 Claude Code 通过微信远程审批和交互。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
**核心功能**:
|
|
9
8
|
- 远程审批敏感操作(Bash/Write/Edit),微信卡片一键通过/拒绝
|
|
10
9
|
- 离开电脑后通过微信下达任务,实时接收进度通知
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
10
|
+
- 支持群聊 @机器人,多机器人、多用户并发
|
|
11
|
+
- 代理企业微信文档 MCP,支持文档和智能表格操作
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 前置条件
|
|
16
|
+
|
|
17
|
+
企业微信管理后台创建智能机器人,连接方式选「使用长连接」,记录 **Bot ID** 和 **Secret**。
|
|
18
|
+
|
|
19
|
+
---
|
|
14
20
|
|
|
15
21
|
## 安装
|
|
16
22
|
|
|
17
23
|
```bash
|
|
18
|
-
npx @vrs-soft/wecom-aibot-mcp
|
|
24
|
+
npx @vrs-soft/wecom-aibot-mcp --setup
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
根据部署角色选择参数:
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
| 命令 | 角色 | 说明 |
|
|
30
|
+
|------|------|------|
|
|
31
|
+
| `--setup` | 交互式 | 询问本地 / 远程,自动引导 |
|
|
32
|
+
| `--setup --server` | 服务器端 | 配置机器人 + Token,不写本机 MCP 配置 |
|
|
33
|
+
| `--setup --channel` | Channel 客户端 | 连接远程 Server,写入 Channel MCP |
|
|
34
|
+
| `--setup --server --channel` | 本地完整 | HTTP + Channel 全安装 |
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
**Server 端安装后启动**:
|
|
26
37
|
|
|
27
38
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
npx @vrs-soft/wecom-aibot-mcp --http-only --start
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**后台启动 / 停止(本地或 Server 端)**:
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
44
|
+
```bash
|
|
45
|
+
npx @vrs-soft/wecom-aibot-mcp --start # 后台启动
|
|
46
|
+
npx @vrs-soft/wecom-aibot-mcp --stop # 停止
|
|
33
47
|
```
|
|
34
48
|
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 运行模式对比
|
|
52
|
+
|
|
53
|
+
| | Channel 模式 | HTTP 模式 |
|
|
54
|
+
|-|-------------|----------|
|
|
55
|
+
| 消息接收 | SSE 自动推送唤醒 | `/loop` 心跳轮询 |
|
|
56
|
+
| 响应延迟 | 即时 | ≤1 分钟 |
|
|
57
|
+
| 账号要求 | claude.ai 直连 | 任意(含 API 中转)|
|
|
58
|
+
|
|
59
|
+
使用微信模式时告诉 Claude「**现在开始通过微信联系**」,会自动触发 `headless-mode` skill。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
35
63
|
## 常用命令
|
|
36
64
|
|
|
37
65
|
| 命令 | 说明 |
|
|
@@ -40,60 +68,37 @@ claude --dangerously-load-development-channels server:wecom-aibot-channel
|
|
|
40
68
|
| `--status` | 查看服务状态和机器人列表 |
|
|
41
69
|
| `--config` | 修改默认机器人配置 |
|
|
42
70
|
| `--add / --delete` | 添加/删除机器人 |
|
|
71
|
+
| `--set-token [token]` | 设置 Auth Token(远程部署用) |
|
|
72
|
+
| `--set-token --clear` | 清除 Auth Token |
|
|
43
73
|
| `--debug` | 前台启动,输出调试日志 |
|
|
74
|
+
| `--http-only` | 仅启动 HTTP MCP Server(服务器端用) |
|
|
75
|
+
| `--channel-only` | 仅配置 Channel MCP(需 `MCP_URL` 环境变量) |
|
|
44
76
|
| `--clean-cache` | 清空 CC 注册表缓存 |
|
|
45
77
|
| `--upgrade` | 强制升级全局配置 |
|
|
46
78
|
| `--uninstall` | 完全卸载 |
|
|
47
79
|
|
|
48
|
-
## 运行模式
|
|
49
|
-
|
|
50
|
-
| | Channel 模式 | HTTP 模式 |
|
|
51
|
-
|-|-------------|----------|
|
|
52
|
-
| 消息接收 | SSE 自动推送唤醒 | `/loop` 心跳轮询 |
|
|
53
|
-
| 响应延迟 | 即时 | ≤1 分钟 |
|
|
54
|
-
| 账号要求 | claude.ai 直连 | 任意(含 API 中转)|
|
|
55
|
-
|
|
56
|
-
使用微信模式时告诉 Claude「现在开始通过微信联系」,会自动触发 `headless-mode` skill。
|
|
57
|
-
|
|
58
|
-
## 配置说明
|
|
59
|
-
|
|
60
|
-
机器人配置保存在 `~/.wecom-aibot-mcp/`,支持多个机器人并发:
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
npx @vrs-soft/wecom-aibot-mcp --add # 添加机器人
|
|
64
|
-
npx @vrs-soft/wecom-aibot-mcp --status # 查看占用情况
|
|
65
|
-
```
|
|
66
|
-
|
|
67
80
|
超时自动审批(默认 10 分钟):在机器人配置中设置 `"autoApproveTimeout": 600`。
|
|
68
81
|
|
|
82
|
+
---
|
|
83
|
+
|
|
69
84
|
## 故障排查
|
|
70
85
|
|
|
71
86
|
```bash
|
|
72
|
-
#
|
|
87
|
+
# 检查服务是否运行
|
|
73
88
|
curl http://127.0.0.1:18963/health
|
|
74
89
|
|
|
75
90
|
# Channel 不可用("Channels are not currently available")
|
|
76
91
|
# → 使用 API Key 或中转服务,改用 HTTP 模式
|
|
77
92
|
|
|
78
93
|
# 端口占用
|
|
79
|
-
lsof -i :18963 | grep LISTEN
|
|
94
|
+
lsof -i :18963 | grep LISTEN
|
|
80
95
|
kill <PID>
|
|
81
96
|
|
|
82
|
-
#
|
|
97
|
+
# 清理断线残留的 ccId 注册
|
|
83
98
|
npx @vrs-soft/wecom-aibot-mcp --clean-cache
|
|
84
99
|
```
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
HTTP MCP 跑在远程服务器,Channel 代理跑在本地:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
# 远程服务器
|
|
92
|
-
npx @vrs-soft/wecom-aibot-mcp --http-only --start
|
|
93
|
-
|
|
94
|
-
# 本地
|
|
95
|
-
MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only
|
|
96
|
-
```
|
|
101
|
+
---
|
|
97
102
|
|
|
98
103
|
## License
|
|
99
104
|
|
package/dist/bin.js
CHANGED
|
@@ -13,7 +13,7 @@ import { spawn } from 'child_process';
|
|
|
13
13
|
import * as fs from 'fs';
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import * as os from 'os';
|
|
16
|
-
import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, } from './config-wizard.js';
|
|
16
|
+
import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, getAuthToken, setAuthToken, updateMcpAuthHeaders, runRemoteInstallWizard, VERSION, } from './config-wizard.js';
|
|
17
17
|
import { initClient } from './client.js';
|
|
18
18
|
import { registerTools } from './tools/index.js';
|
|
19
19
|
import { startHttpServer, stopHttpServer, HTTP_PORT } from './http-server.js';
|
|
@@ -22,7 +22,6 @@ import { getAllConnectionStates } from './connection-manager.js';
|
|
|
22
22
|
import { loadStats, cleanupOldLogs } from './connection-log.js';
|
|
23
23
|
import { startKeepaliveMonitor, stopKeepaliveMonitor } from './keepalive-monitor.js';
|
|
24
24
|
import { logger } from './logger.js';
|
|
25
|
-
const VERSION = '2.0.0';
|
|
26
25
|
const PID_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'server.pid');
|
|
27
26
|
function showHelp() {
|
|
28
27
|
console.log(`
|
|
@@ -37,6 +36,10 @@ function showHelp() {
|
|
|
37
36
|
选项:
|
|
38
37
|
--help, -h 显示帮助信息
|
|
39
38
|
--version, -v 显示版本号
|
|
39
|
+
--setup 安装向导(交互式,询问本地 / 远程)
|
|
40
|
+
--setup --server 服务器端安装(配置机器人 + Token)
|
|
41
|
+
--setup --channel Channel 客户端安装(写入 Channel MCP)
|
|
42
|
+
--setup --server --channel 本地完整安装(HTTP + Channel)
|
|
40
43
|
--upgrade 强制升级全局配置(覆盖 MCP 配置、权限、skill)
|
|
41
44
|
--reinstall 重新安装全局配置(删除后重新写入,保留机器人配置)
|
|
42
45
|
--start 启动 MCP Server(后台服务模式)
|
|
@@ -52,19 +55,17 @@ function showHelp() {
|
|
|
52
55
|
--list 列出所有已配置的机器人及其占用状态
|
|
53
56
|
--delete [名称] 删除指定的机器人配置(保留 MCP 配置)
|
|
54
57
|
--uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
|
|
58
|
+
--set-token [token] 设置/清除 Auth Token(远程部署用,--set-token --clear 清除)
|
|
55
59
|
--clean-cache 清空 CC 注册表缓存(清理异常断线残留的 ccId)
|
|
56
60
|
|
|
57
61
|
使用流程:
|
|
58
|
-
1.
|
|
59
|
-
|
|
62
|
+
1. 安装: npx @vrs-soft/wecom-aibot-mcp --setup
|
|
63
|
+
(根据角色选择参数:--server / --channel / 两者都传 / 不传交互选择)
|
|
60
64
|
|
|
61
|
-
2.
|
|
62
|
-
(显示状态,提示使用 --start 启动)
|
|
63
|
-
|
|
64
|
-
3. 启动服务: npx @vrs-soft/wecom-aibot-mcp --start
|
|
65
|
+
2. 启动服务: npx @vrs-soft/wecom-aibot-mcp --start
|
|
65
66
|
(后台启动 MCP HTTP Server)
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
3. 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop
|
|
68
69
|
|
|
69
70
|
拆分部署(远程 HTTP + 本地 Channel):
|
|
70
71
|
|
|
@@ -104,9 +105,21 @@ function showVersion() {
|
|
|
104
105
|
function showStatus() {
|
|
105
106
|
const allRobots = listAllRobots();
|
|
106
107
|
const connections = getAllConnectionStates();
|
|
108
|
+
const authToken = getAuthToken();
|
|
107
109
|
// 检查服务是否运行
|
|
108
110
|
const serverRunning = isServerRunning();
|
|
109
|
-
console.log(`\n服务状态: ${serverRunning ? '✅ 运行中' : '❌ 未启动'}
|
|
111
|
+
console.log(`\n服务状态: ${serverRunning ? '✅ 运行中' : '❌ 未启动'}`);
|
|
112
|
+
// 显示 Auth Token 状态(带部分 token 显示)
|
|
113
|
+
if (authToken) {
|
|
114
|
+
const maskedToken = authToken.length > 12
|
|
115
|
+
? `${authToken.slice(0, 8)}...${authToken.slice(-4)}`
|
|
116
|
+
: `${authToken.slice(0, 4)}...`;
|
|
117
|
+
console.log(`Auth Token: ✅ 已配置 (${maskedToken})`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(`Auth Token: (未配置,本地部署无需 token)`);
|
|
121
|
+
}
|
|
122
|
+
console.log('');
|
|
110
123
|
if (allRobots.length === 0) {
|
|
111
124
|
console.log('尚未配置机器人,请运行 npx @vrs-soft/wecom-aibot-mcp 启动配置向导');
|
|
112
125
|
return;
|
|
@@ -122,7 +135,8 @@ function showStatus() {
|
|
|
122
135
|
for (const robot of allRobots) {
|
|
123
136
|
const usage = robotUsage.get(robot.name);
|
|
124
137
|
const statusTag = usage ? ` [使用中]` : '';
|
|
125
|
-
|
|
138
|
+
const docTag = robot.doc_mcp_url ? ' [文档✅]' : '';
|
|
139
|
+
console.log(` Bot名称: ${robot.name}${statusTag}${docTag}`);
|
|
126
140
|
console.log(` Bot ID: ${robot.botId}`);
|
|
127
141
|
console.log(` 目标用户:${robot.targetUserId}`);
|
|
128
142
|
if (usage) {
|
|
@@ -297,9 +311,9 @@ async function main() {
|
|
|
297
311
|
// 确定安装模式
|
|
298
312
|
const installMode = args.includes('--http-only') ? 'http-only' :
|
|
299
313
|
args.includes('--channel-only') ? 'channel-only' : 'full';
|
|
300
|
-
// --reinstall
|
|
301
|
-
//
|
|
302
|
-
if (!args.includes('--reinstall') && !args.includes('--http-only')) {
|
|
314
|
+
// --reinstall / --http-only / --setup 命令跳过顶部 ensureGlobalConfigs
|
|
315
|
+
// (--setup 自己在向导完成后调用)
|
|
316
|
+
if (!args.includes('--reinstall') && !args.includes('--http-only') && !args.includes('--setup')) {
|
|
303
317
|
// 强制覆盖所有全局配置(不依赖智能体)
|
|
304
318
|
ensureGlobalConfigs(installMode);
|
|
305
319
|
}
|
|
@@ -325,7 +339,7 @@ async function main() {
|
|
|
325
339
|
// --reinstall 命令:删除所有全局配置(保留机器人配置)后重新安装
|
|
326
340
|
if (args.includes('--reinstall')) {
|
|
327
341
|
logger.log('\n[mcp] 重新安装全局配置...');
|
|
328
|
-
console.log('[mcp] 保留所有机器人配置: ~/.wecom-aibot-mcp/
|
|
342
|
+
console.log('[mcp] 保留所有机器人配置: ~/.wecom-aibot-mcp/robot-*.json');
|
|
329
343
|
const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
330
344
|
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
|
|
331
345
|
const VERSION_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'version.json');
|
|
@@ -413,6 +427,48 @@ async function main() {
|
|
|
413
427
|
uninstall();
|
|
414
428
|
process.exit(0);
|
|
415
429
|
}
|
|
430
|
+
// --set-token 命令:设置/清除 Auth Token
|
|
431
|
+
if (args.includes('--set-token')) {
|
|
432
|
+
const tokenIndex = args.indexOf('--set-token');
|
|
433
|
+
const clearToken = args.includes('--clear');
|
|
434
|
+
if (clearToken) {
|
|
435
|
+
setAuthToken(undefined);
|
|
436
|
+
updateMcpAuthHeaders(undefined);
|
|
437
|
+
console.log('[mcp] ✅ Auth Token 已清除(服务端 + 客户端 MCP 配置)');
|
|
438
|
+
process.exit(0);
|
|
439
|
+
}
|
|
440
|
+
// 检查下一个参数是否是 token(不是另一个 --flag)
|
|
441
|
+
const nextArg = args[tokenIndex + 1];
|
|
442
|
+
const token = (nextArg && !nextArg.startsWith('--')) ? nextArg : undefined;
|
|
443
|
+
if (!token) {
|
|
444
|
+
// 交互式输入 token
|
|
445
|
+
const readline = await import('readline');
|
|
446
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
447
|
+
const input = await new Promise((resolve) => {
|
|
448
|
+
rl.question('请输入 Auth Token(留空取消): ', (answer) => {
|
|
449
|
+
rl.close();
|
|
450
|
+
resolve(answer.trim());
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
if (!input) {
|
|
454
|
+
console.log('[mcp] 已取消');
|
|
455
|
+
process.exit(0);
|
|
456
|
+
}
|
|
457
|
+
setAuthToken(input);
|
|
458
|
+
updateMcpAuthHeaders(input);
|
|
459
|
+
console.log('[mcp] ✅ Auth Token 已设置');
|
|
460
|
+
console.log(`[mcp] 服务端: ~/.wecom-aibot-mcp/server.json`);
|
|
461
|
+
console.log(`[mcp] 客户端: ~/.claude.json MCP headers 已同步`);
|
|
462
|
+
console.log(`[mcp] Token: ${input.slice(0, 8)}...${input.slice(-4)}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
setAuthToken(token);
|
|
466
|
+
updateMcpAuthHeaders(token);
|
|
467
|
+
console.log('[mcp] ✅ Auth Token 已设置');
|
|
468
|
+
console.log(`[mcp] Token: ${token.slice(0, 8)}...${token.slice(-4)}`);
|
|
469
|
+
}
|
|
470
|
+
process.exit(0);
|
|
471
|
+
}
|
|
416
472
|
if (args.includes('--add')) {
|
|
417
473
|
await addMcpConfig();
|
|
418
474
|
process.exit(0);
|
|
@@ -429,9 +485,87 @@ async function main() {
|
|
|
429
485
|
await startMcpServerForeground();
|
|
430
486
|
return; // 保持运行,不 exit
|
|
431
487
|
}
|
|
488
|
+
// --setup:统一安装向导
|
|
489
|
+
// --setup → 交互式(询问本地 / 远程)
|
|
490
|
+
// --setup --server → 服务器端(机器人配置 + Token)
|
|
491
|
+
// --setup --channel → Channel 客户端(写入 Channel MCP)
|
|
492
|
+
// --setup --server --channel → 本地完整安装(HTTP + Channel)
|
|
493
|
+
if (args.includes('--setup')) {
|
|
494
|
+
const wantServer = args.includes('--server');
|
|
495
|
+
const wantChannel = args.includes('--channel');
|
|
496
|
+
if (wantServer && wantChannel) {
|
|
497
|
+
// 本地完整安装
|
|
498
|
+
console.log('\n[setup] 本地完整安装模式\n');
|
|
499
|
+
const savedConfig = loadConfig();
|
|
500
|
+
if (!savedConfig?.botId)
|
|
501
|
+
await runConfigWizard();
|
|
502
|
+
ensureGlobalConfigs('full');
|
|
503
|
+
startMcpServerBackground();
|
|
504
|
+
console.log('[setup] 安装完成!请重启 Claude Code 以加载配置');
|
|
505
|
+
}
|
|
506
|
+
else if (wantServer) {
|
|
507
|
+
// 服务器端
|
|
508
|
+
console.log('\n[setup] Server 安装模式\n');
|
|
509
|
+
const savedConfig = loadConfig();
|
|
510
|
+
if (!savedConfig?.botId)
|
|
511
|
+
await runConfigWizard();
|
|
512
|
+
const readline = await import('readline');
|
|
513
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
514
|
+
const token = await new Promise(resolve => rl.question('Auth Token(Client 端需填写相同 Token,留空跳过): ', a => { rl.close(); resolve(a.trim()); }));
|
|
515
|
+
if (token)
|
|
516
|
+
setAuthToken(token);
|
|
517
|
+
console.log('\n[setup] Server 配置完成!');
|
|
518
|
+
console.log(' 启动: npx @vrs-soft/wecom-aibot-mcp --http-only --start');
|
|
519
|
+
}
|
|
520
|
+
else if (wantChannel) {
|
|
521
|
+
// Channel 客户端
|
|
522
|
+
console.log('\n[setup] Channel Client 安装模式\n');
|
|
523
|
+
let mcpUrl = process.env.MCP_URL;
|
|
524
|
+
if (!mcpUrl) {
|
|
525
|
+
const readline = await import('readline');
|
|
526
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
527
|
+
mcpUrl = await new Promise(resolve => rl.question('远程服务器地址(如 https://your-server:18963): ', a => { rl.close(); resolve(a.trim()); }));
|
|
528
|
+
if (!mcpUrl) {
|
|
529
|
+
console.log('[setup] ❌ 地址不能为空');
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
process.env.MCP_URL = mcpUrl;
|
|
533
|
+
}
|
|
534
|
+
if (!getAuthToken()) {
|
|
535
|
+
const readline = await import('readline');
|
|
536
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
537
|
+
const token = await new Promise(resolve => rl.question('Auth Token: ', a => { rl.close(); resolve(a.trim()); }));
|
|
538
|
+
if (token)
|
|
539
|
+
setAuthToken(token);
|
|
540
|
+
}
|
|
541
|
+
ensureGlobalConfigs('channel-only');
|
|
542
|
+
console.log('[setup] Channel MCP 配置完成!请重启 Claude Code 以加载配置');
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
// 交互式:1/2 模式选择
|
|
546
|
+
console.log('\n请选择安装模式:\n');
|
|
547
|
+
console.log(' 1. 本地安装(完整功能:HTTP + Channel MCP)');
|
|
548
|
+
console.log(' 2. 远程服务器(连接远程 HTTP MCP)\n');
|
|
549
|
+
const readline = await import('readline');
|
|
550
|
+
const modeChoice = await new Promise((resolve) => {
|
|
551
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
552
|
+
rl.question('请选择 (1/2,默认 1): ', a => { rl.close(); resolve(a.trim() || '1'); });
|
|
553
|
+
});
|
|
554
|
+
if (modeChoice === '2') {
|
|
555
|
+
await runRemoteInstallWizard();
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
await runConfigWizard();
|
|
559
|
+
ensureGlobalConfigs('full');
|
|
560
|
+
startMcpServerBackground();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
432
565
|
// --channel:启动 Channel MCP 代理(stdio)
|
|
433
566
|
// 注意:必须在 --debug 之前检查,否则 --channel --debug 会先触发 HTTP Server
|
|
434
|
-
|
|
567
|
+
// --setup --channel 已在上方处理,这里不拦截
|
|
568
|
+
if (args.includes('--channel') && !args.includes('--setup')) {
|
|
435
569
|
// 检查 HTTP MCP 的 debug 标记文件
|
|
436
570
|
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
437
571
|
const isDebug = fs.existsSync(debugFile) || args.includes('--debug');
|
|
@@ -509,12 +643,10 @@ async function main() {
|
|
|
509
643
|
config = savedConfig;
|
|
510
644
|
}
|
|
511
645
|
else if (isInteractive) {
|
|
512
|
-
// TTY
|
|
513
|
-
console.log('[config]
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
instanceName = result.instanceName;
|
|
517
|
-
ranWizard = true;
|
|
646
|
+
// TTY 模式下没有配置:提示使用 --setup,不再隐式弹向导
|
|
647
|
+
console.log('[config] 未找到机器人配置。');
|
|
648
|
+
console.log('[config] 请运行: npx @vrs-soft/wecom-aibot-mcp --setup');
|
|
649
|
+
process.exit(1);
|
|
518
650
|
}
|
|
519
651
|
else {
|
|
520
652
|
// 非 TTY 模式(MCP HTTP),必须有配置
|
package/dist/channel-server.js
CHANGED
|
@@ -15,7 +15,17 @@ import { z } from 'zod';
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import * as os from 'os';
|
|
18
|
+
import { VERSION } from './config-wizard.js';
|
|
18
19
|
const MCP_URL = process.env.MCP_URL || 'http://127.0.0.1:18963';
|
|
20
|
+
const MCP_AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
|
|
21
|
+
// 构建带 auth 的 fetch headers
|
|
22
|
+
function getAuthHeaders() {
|
|
23
|
+
const headers = {};
|
|
24
|
+
if (MCP_AUTH_TOKEN) {
|
|
25
|
+
headers['Authorization'] = `Bearer ${MCP_AUTH_TOKEN}`;
|
|
26
|
+
}
|
|
27
|
+
return headers;
|
|
28
|
+
}
|
|
19
29
|
// Channel 日志文件
|
|
20
30
|
const CHANNEL_LOG_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'channel.log');
|
|
21
31
|
/**
|
|
@@ -38,6 +48,7 @@ function logChannel(message, data) {
|
|
|
38
48
|
let sseConnected = false;
|
|
39
49
|
let sseAbortController = null;
|
|
40
50
|
let mcpServer = null;
|
|
51
|
+
let sseCurrentCcId = undefined;
|
|
41
52
|
// HTTP MCP session ID(需要在转发请求前初始化)
|
|
42
53
|
let httpSessionId = null;
|
|
43
54
|
/**
|
|
@@ -52,6 +63,7 @@ async function initHttpSession() {
|
|
|
52
63
|
headers: {
|
|
53
64
|
'Content-Type': 'application/json',
|
|
54
65
|
'Accept': 'application/json, text/event-stream',
|
|
66
|
+
...getAuthHeaders(),
|
|
55
67
|
},
|
|
56
68
|
body: JSON.stringify({
|
|
57
69
|
jsonrpc: '2.0',
|
|
@@ -109,6 +121,7 @@ async function forwardToHttpMcp(toolName, params) {
|
|
|
109
121
|
'Content-Type': 'application/json',
|
|
110
122
|
'Accept': 'application/json, text/event-stream',
|
|
111
123
|
'mcp-session-id': sessionId,
|
|
124
|
+
...getAuthHeaders(),
|
|
112
125
|
},
|
|
113
126
|
body: JSON.stringify({
|
|
114
127
|
jsonrpc: '2.0',
|
|
@@ -172,6 +185,7 @@ function connectSSE(ccId) {
|
|
|
172
185
|
return;
|
|
173
186
|
}
|
|
174
187
|
sseConnected = true;
|
|
188
|
+
sseCurrentCcId = ccId;
|
|
175
189
|
const sseUrl = ccId ? `${MCP_URL}/sse/${ccId}` : `${MCP_URL}/sse`;
|
|
176
190
|
logChannel('Connecting to SSE', { url: sseUrl, ccId, mcpServerReady: mcpServer ? 'yes' : 'no' });
|
|
177
191
|
sseAbortController = new AbortController();
|
|
@@ -183,6 +197,7 @@ function connectSSE(ccId) {
|
|
|
183
197
|
'Accept': 'text/event-stream',
|
|
184
198
|
'Cache-Control': 'no-cache',
|
|
185
199
|
'Connection': 'keep-alive',
|
|
200
|
+
...getAuthHeaders(),
|
|
186
201
|
},
|
|
187
202
|
}).then(async (res) => {
|
|
188
203
|
if (!res.ok) {
|
|
@@ -210,6 +225,11 @@ function connectSSE(ccId) {
|
|
|
210
225
|
logChannel('SSE stream ended');
|
|
211
226
|
clearInterval(heartbeatInterval);
|
|
212
227
|
sseConnected = false;
|
|
228
|
+
// 非主动断开时自动重连
|
|
229
|
+
if (!sseAbortController?.signal.aborted) {
|
|
230
|
+
logChannel('SSE 断线,3 秒后重连', { ccId });
|
|
231
|
+
setTimeout(() => connectSSE(ccId), 3000);
|
|
232
|
+
}
|
|
213
233
|
break;
|
|
214
234
|
}
|
|
215
235
|
const chunk = decoder.decode(value, { stream: true });
|
|
@@ -280,6 +300,11 @@ function connectSSE(ccId) {
|
|
|
280
300
|
}).catch((err) => {
|
|
281
301
|
logChannel('SSE error', { error: String(err) });
|
|
282
302
|
sseConnected = false;
|
|
303
|
+
// 非主动断开时自动重连
|
|
304
|
+
if (!sseAbortController?.signal.aborted) {
|
|
305
|
+
logChannel('SSE 出错,3 秒后重连', { ccId });
|
|
306
|
+
setTimeout(() => connectSSE(ccId), 3000);
|
|
307
|
+
}
|
|
283
308
|
});
|
|
284
309
|
}
|
|
285
310
|
/**
|
|
@@ -352,8 +377,9 @@ function registerChannelTools(server) {
|
|
|
352
377
|
bot_id: z.string().describe('企业微信 Bot ID'),
|
|
353
378
|
secret: z.string().describe('机器人密钥'),
|
|
354
379
|
default_user: z.string().optional().describe('默认目标用户'),
|
|
355
|
-
|
|
356
|
-
|
|
380
|
+
doc_mcp_url: z.string().optional().describe('机器人文档 MCP URL(企业微信文档能力)'),
|
|
381
|
+
}, async ({ name, bot_id, secret, default_user, doc_mcp_url }) => {
|
|
382
|
+
return forwardToHttpMcp('add_robot_config', { name, bot_id, secret, default_user, doc_mcp_url });
|
|
357
383
|
});
|
|
358
384
|
// ============================================
|
|
359
385
|
// 工具 8: 列出所有机器人
|
|
@@ -416,11 +442,12 @@ function registerChannelTools(server) {
|
|
|
416
442
|
cc_id: z.string().describe('CC 唯一标识(enter_headless_mode 返回的 ccId)'),
|
|
417
443
|
project_dir: z.string().optional().describe('项目目录路径(用于更新配置文件)'),
|
|
418
444
|
}, async ({ cc_id, project_dir }) => {
|
|
419
|
-
// 断开 SSE
|
|
445
|
+
// 断开 SSE 连接(abort 后重连逻辑不会触发)
|
|
420
446
|
if (sseAbortController) {
|
|
421
447
|
sseAbortController.abort();
|
|
422
448
|
sseAbortController = null;
|
|
423
449
|
sseConnected = false;
|
|
450
|
+
sseCurrentCcId = undefined;
|
|
424
451
|
logChannel('SSE disconnected', { cc_id });
|
|
425
452
|
}
|
|
426
453
|
return forwardToHttpMcp('exit_headless_mode', { cc_id, project_dir: project_dir || process.cwd() });
|
|
@@ -445,7 +472,9 @@ function registerChannelTools(server) {
|
|
|
445
472
|
// ============================================
|
|
446
473
|
server.tool('get_skill', '获取 headless-mode skill 文件内容,用于写入本地项目目录。远程部署时 HTTP MCP 可能不在本地,skill 文件需要从此接口获取。', {}, async () => {
|
|
447
474
|
// 直接请求 HTTP MCP 的 /skill 端点
|
|
448
|
-
const res = await fetch(`${MCP_URL}/skill
|
|
475
|
+
const res = await fetch(`${MCP_URL}/skill`, {
|
|
476
|
+
headers: getAuthHeaders(),
|
|
477
|
+
});
|
|
449
478
|
if (!res.ok) {
|
|
450
479
|
return {
|
|
451
480
|
content: [{
|
|
@@ -462,7 +491,97 @@ function registerChannelTools(server) {
|
|
|
462
491
|
}],
|
|
463
492
|
};
|
|
464
493
|
});
|
|
465
|
-
|
|
494
|
+
// ============================================
|
|
495
|
+
// 文档代理工具(转发到 HTTP MCP)
|
|
496
|
+
// ============================================
|
|
497
|
+
const docTools = [
|
|
498
|
+
['create_doc', '新建文档或智能表格', {
|
|
499
|
+
doc_type: z.number().int().describe('文档类型:3=文档,10=智能表格'),
|
|
500
|
+
doc_name: z.string().describe('文档名称'),
|
|
501
|
+
robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
|
|
502
|
+
}],
|
|
503
|
+
['get_doc_content', '获取文档内容(Markdown 格式)', {
|
|
504
|
+
type: z.number().int().describe('内容格式:2=Markdown'),
|
|
505
|
+
url: z.string().optional().describe('文档链接'),
|
|
506
|
+
docid: z.string().optional().describe('文档 docid'),
|
|
507
|
+
task_id: z.string().optional().describe('任务 ID(轮询时填写)'),
|
|
508
|
+
robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
|
|
509
|
+
}],
|
|
510
|
+
['edit_doc_content', '编辑文档内容(Markdown 格式覆写)', {
|
|
511
|
+
content: z.string().describe('覆写的文档内容'),
|
|
512
|
+
content_type: z.number().int().describe('内容类型:1=Markdown'),
|
|
513
|
+
url: z.string().optional().describe('文档链接'),
|
|
514
|
+
docid: z.string().optional().describe('文档 docid'),
|
|
515
|
+
robot_name: z.string().optional().describe('指定机器人名称(多机器人时必填)'),
|
|
516
|
+
}],
|
|
517
|
+
['smartsheet_get_sheet', '查询智能表格子表信息', {
|
|
518
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
519
|
+
robot_name: z.string().optional(),
|
|
520
|
+
}],
|
|
521
|
+
['smartsheet_add_sheet', '添加智能表格子表', {
|
|
522
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
523
|
+
properties: z.object({ title: z.string().optional() }).optional(),
|
|
524
|
+
robot_name: z.string().optional(),
|
|
525
|
+
}],
|
|
526
|
+
['smartsheet_update_sheet', '更新智能表格子表标题', {
|
|
527
|
+
properties: z.object({ sheet_id: z.string(), title: z.string() }),
|
|
528
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
529
|
+
robot_name: z.string().optional(),
|
|
530
|
+
}],
|
|
531
|
+
['smartsheet_delete_sheet', '删除智能表格子表', {
|
|
532
|
+
sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
|
|
533
|
+
robot_name: z.string().optional(),
|
|
534
|
+
}],
|
|
535
|
+
['smartsheet_get_fields', '查询智能表格字段', {
|
|
536
|
+
sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
|
|
537
|
+
robot_name: z.string().optional(),
|
|
538
|
+
}],
|
|
539
|
+
['smartsheet_add_fields', '添加智能表格字段', {
|
|
540
|
+
sheet_id: z.string(),
|
|
541
|
+
fields: z.array(z.object({ field_title: z.string(), field_type: z.string() })),
|
|
542
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
543
|
+
robot_name: z.string().optional(),
|
|
544
|
+
}],
|
|
545
|
+
['smartsheet_update_fields', '更新智能表格字段标题', {
|
|
546
|
+
sheet_id: z.string(),
|
|
547
|
+
fields: z.array(z.object({ field_id: z.string(), field_title: z.string(), field_type: z.string() })),
|
|
548
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
549
|
+
robot_name: z.string().optional(),
|
|
550
|
+
}],
|
|
551
|
+
['smartsheet_delete_fields', '删除智能表格字段', {
|
|
552
|
+
sheet_id: z.string(), field_ids: z.array(z.string()),
|
|
553
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
554
|
+
robot_name: z.string().optional(),
|
|
555
|
+
}],
|
|
556
|
+
['smartsheet_get_records', '查询智能表格记录', {
|
|
557
|
+
sheet_id: z.string(), url: z.string().optional(), docid: z.string().optional(),
|
|
558
|
+
robot_name: z.string().optional(),
|
|
559
|
+
}],
|
|
560
|
+
['smartsheet_add_records', '添加智能表格记录', {
|
|
561
|
+
sheet_id: z.string(),
|
|
562
|
+
records: z.array(z.object({ values: z.record(z.string(), z.unknown()) })),
|
|
563
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
564
|
+
robot_name: z.string().optional(),
|
|
565
|
+
}],
|
|
566
|
+
['smartsheet_update_records', '更新智能表格记录', {
|
|
567
|
+
sheet_id: z.string(),
|
|
568
|
+
records: z.array(z.object({ record_id: z.string(), values: z.record(z.string(), z.unknown()) })),
|
|
569
|
+
key_type: z.enum(['CELL_VALUE_KEY_TYPE_FIELD_TITLE', 'CELL_VALUE_KEY_TYPE_FIELD_ID']),
|
|
570
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
571
|
+
robot_name: z.string().optional(),
|
|
572
|
+
}],
|
|
573
|
+
['smartsheet_delete_records', '删除智能表格记录', {
|
|
574
|
+
sheet_id: z.string(), record_ids: z.array(z.string()),
|
|
575
|
+
url: z.string().optional(), docid: z.string().optional(),
|
|
576
|
+
robot_name: z.string().optional(),
|
|
577
|
+
}],
|
|
578
|
+
];
|
|
579
|
+
for (const [toolName, description, schema] of docTools) {
|
|
580
|
+
server.tool(toolName, description, schema, async (args) => {
|
|
581
|
+
return forwardToHttpMcp(toolName, args);
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
logChannel('Registered 28 tools (13 core + 15 doc proxy)');
|
|
466
585
|
}
|
|
467
586
|
/**
|
|
468
587
|
* 启动 Channel MCP Server
|
|
@@ -472,7 +591,7 @@ export async function startChannelServer() {
|
|
|
472
591
|
// 创建 MCP Server
|
|
473
592
|
mcpServer = new McpServer({
|
|
474
593
|
name: 'wecom-aibot-channel',
|
|
475
|
-
version:
|
|
594
|
+
version: VERSION,
|
|
476
595
|
}, {
|
|
477
596
|
capabilities: {
|
|
478
597
|
// 必须声明 experimental['claude/channel'],Claude Code 才会注册 notification listener
|