@vrs-soft/wecom-aibot-mcp 1.4.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 +52 -307
- package/dist/approval-manager.d.ts +38 -0
- package/dist/approval-manager.js +129 -0
- package/dist/bin.js +233 -65
- package/dist/cc-registry.d.ts +62 -0
- package/dist/cc-registry.js +278 -0
- 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 +16 -2
- package/dist/config-wizard.js +542 -141
- package/dist/connection-log.js +7 -6
- package/dist/connection-manager.d.ts +2 -8
- package/dist/connection-manager.js +22 -33
- 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 +30 -18
- package/dist/http-server.js +465 -177
- 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 +57 -0
- package/dist/project-config.js +218 -7
- package/dist/tools/headless.d.ts +8 -0
- package/dist/tools/headless.js +248 -0
- package/dist/tools/index.js +271 -115
- package/dist/tools/messaging.d.ts +7 -0
- package/dist/tools/messaging.js +170 -0
- package/dist/tools/utils-tools.d.ts +11 -0
- package/dist/tools/utils-tools.js +249 -0
- package/dist/utils/atomic-write.d.ts +4 -0
- package/dist/utils/atomic-write.js +9 -0
- package/dist/utils/sanitize.d.ts +59 -0
- package/dist/utils/sanitize.js +246 -0
- package/package.json +1 -1
- package/skills/headless-mode/SKILL.md +144 -134
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,
|
|
16
|
+
import { runConfigWizard, loadConfig, saveConfig, deleteRobotConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, ensureGlobalConfigs, } 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';
|
|
@@ -21,7 +21,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
21
21
|
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';
|
|
25
26
|
const PID_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'server.pid');
|
|
26
27
|
function showHelp() {
|
|
27
28
|
console.log(`
|
|
@@ -36,15 +37,22 @@ function showHelp() {
|
|
|
36
37
|
选项:
|
|
37
38
|
--help, -h 显示帮助信息
|
|
38
39
|
--version, -v 显示版本号
|
|
40
|
+
--upgrade 强制升级全局配置(覆盖 MCP 配置、权限、skill)
|
|
41
|
+
--reinstall 重新安装全局配置(删除后重新写入,保留机器人配置)
|
|
39
42
|
--start 启动 MCP Server(后台服务模式)
|
|
40
43
|
--stop 停止 MCP Server
|
|
41
44
|
--debug 前台启动 MCP Server(日志直接输出到终端,用于调试)
|
|
45
|
+
--channel 启动 Channel MCP Proxy(stdio 代理 + SSE 唤醒)
|
|
46
|
+
--http-only 仅启动 HTTP Server(远程部署场景,不安装 Channel MCP 配置)
|
|
47
|
+
--channel-only 仅配置 Channel MCP(本地连接远程 HTTP Server)
|
|
42
48
|
--status 显示服务状态和机器人配置
|
|
43
49
|
--config 重新配置默认机器人(修改 Bot ID / Secret / 目标用户)
|
|
44
50
|
--add 添加新的机器人配置(多机器人场景)
|
|
51
|
+
--rename [名称] 重命名机器人(可选参数:旧名称,交互式输入新名称)
|
|
45
52
|
--list 列出所有已配置的机器人及其占用状态
|
|
46
|
-
--delete [名称]
|
|
53
|
+
--delete [名称] 删除指定的机器人配置(保留 MCP 配置)
|
|
47
54
|
--uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
|
|
55
|
+
--clean-cache 清空 CC 注册表缓存(清理异常断线残留的 ccId)
|
|
48
56
|
|
|
49
57
|
使用流程:
|
|
50
58
|
1. 首次安装: npx @vrs-soft/wecom-aibot-mcp
|
|
@@ -58,18 +66,34 @@ function showHelp() {
|
|
|
58
66
|
|
|
59
67
|
4. 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
拆分部署(远程 HTTP + 本地 Channel):
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
远程服务器:
|
|
72
|
+
npx @vrs-soft/wecom-aibot-mcp --http-only --start
|
|
73
|
+
# 只启动 HTTP Server,不写入本地 MCP 配置
|
|
64
74
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
本地机器:
|
|
76
|
+
MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only
|
|
77
|
+
# 必须通过 MCP_URL 指定远程 HTTP MCP 地址
|
|
78
|
+
# 只配置 Channel MCP,连接远程 HTTP Server
|
|
79
|
+
|
|
80
|
+
MCP 配置(默认安装同时配置两种模式):
|
|
81
|
+
|
|
82
|
+
HTTP Transport(轮询模式):
|
|
83
|
+
"wecom-aibot": {
|
|
84
|
+
"type": "http",
|
|
85
|
+
"url": "http://127.0.0.1:18963/mcp"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Channel Transport(SSE 推送模式):
|
|
89
|
+
"wecom-aibot-channel": {
|
|
90
|
+
"command": "npx",
|
|
91
|
+
"args": ["@vrs-soft/wecom-aibot-mcp", "--channel"]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Channel 模式优势:微信消息自动唤醒 agent,无需主动轮询
|
|
95
|
+
启动 Channel 模式(研究预览):
|
|
96
|
+
claude --dangerously-load-development-channels server:wecom-aibot-channel
|
|
73
97
|
|
|
74
98
|
更多信息: https://github.com/eric2877/wecom-aibot-mcp
|
|
75
99
|
`);
|
|
@@ -98,11 +122,11 @@ function showStatus() {
|
|
|
98
122
|
for (const robot of allRobots) {
|
|
99
123
|
const usage = robotUsage.get(robot.name);
|
|
100
124
|
const statusTag = usage ? ` [使用中]` : '';
|
|
101
|
-
console.log(` ${robot.name}${statusTag}`);
|
|
102
|
-
console.log(` Bot ID
|
|
103
|
-
console.log(`
|
|
125
|
+
console.log(` Bot名称: ${robot.name}${statusTag}`);
|
|
126
|
+
console.log(` Bot ID: ${robot.botId}`);
|
|
127
|
+
console.log(` 目标用户:${robot.targetUserId}`);
|
|
104
128
|
if (usage) {
|
|
105
|
-
console.log(`
|
|
129
|
+
console.log(` 使用者: ${usage.agentName}`);
|
|
106
130
|
}
|
|
107
131
|
console.log('');
|
|
108
132
|
}
|
|
@@ -157,7 +181,7 @@ function stopServer() {
|
|
|
157
181
|
return true;
|
|
158
182
|
}
|
|
159
183
|
catch (err) {
|
|
160
|
-
|
|
184
|
+
logger.error('[mcp] 停止服务失败:', err);
|
|
161
185
|
if (fs.existsSync(PID_FILE)) {
|
|
162
186
|
fs.unlinkSync(PID_FILE);
|
|
163
187
|
}
|
|
@@ -181,14 +205,20 @@ async function waitForConnection(client, timeoutMs = 10000) {
|
|
|
181
205
|
});
|
|
182
206
|
}
|
|
183
207
|
// 启动 MCP Server(前台运行,供 --start 使用)
|
|
184
|
-
async function startMcpServerForeground() {
|
|
208
|
+
async function startMcpServerForeground(isDebug = false) {
|
|
185
209
|
const savedConfig = loadConfig();
|
|
186
210
|
if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
|
|
187
|
-
|
|
211
|
+
logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
|
|
188
212
|
process.exit(1);
|
|
189
213
|
}
|
|
190
214
|
// 写入 PID 文件
|
|
191
215
|
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
216
|
+
// Debug 模式:创建 debug 标记文件
|
|
217
|
+
if (isDebug) {
|
|
218
|
+
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
219
|
+
fs.writeFileSync(debugFile, 'true');
|
|
220
|
+
console.log('[mcp] Debug 标记文件已创建');
|
|
221
|
+
}
|
|
192
222
|
// 确保 hook 已安装
|
|
193
223
|
ensureHookInstalled();
|
|
194
224
|
// 加载统计并清理旧日志
|
|
@@ -201,20 +231,20 @@ async function startMcpServerForeground() {
|
|
|
201
231
|
});
|
|
202
232
|
registerTools(server);
|
|
203
233
|
// 启动 HTTP 服务
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
234
|
+
logger.log('');
|
|
235
|
+
logger.log(' ╔════════════════════════════════════════════════════════╗');
|
|
236
|
+
logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
|
|
237
|
+
logger.log(' ║ Claude Code 审批通道 ║');
|
|
238
|
+
logger.log(' ╚════════════════════════════════════════════════════════╝');
|
|
239
|
+
logger.log('');
|
|
240
|
+
logger.log(`[mcp] 启动 MCP HTTP Server (端口: ${HTTP_PORT})...`);
|
|
211
241
|
await startHttpServer(server);
|
|
212
242
|
startKeepaliveMonitor();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
243
|
+
logger.log(`[mcp] MCP Server 已就绪`);
|
|
244
|
+
logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
|
|
245
|
+
logger.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
|
|
246
|
+
logger.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
|
|
247
|
+
logger.log(`[mcp] PID: ${process.pid}`);
|
|
218
248
|
// 退出处理
|
|
219
249
|
const gracefulShutdown = () => {
|
|
220
250
|
console.log('[mcp] 正在关闭...');
|
|
@@ -223,6 +253,14 @@ async function startMcpServerForeground() {
|
|
|
223
253
|
if (fs.existsSync(PID_FILE)) {
|
|
224
254
|
fs.unlinkSync(PID_FILE);
|
|
225
255
|
}
|
|
256
|
+
// Debug 模式:删除 debug 标记文件
|
|
257
|
+
if (isDebug) {
|
|
258
|
+
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
259
|
+
if (fs.existsSync(debugFile)) {
|
|
260
|
+
fs.unlinkSync(debugFile);
|
|
261
|
+
console.log('[mcp] Debug 标记文件已删除');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
226
264
|
process.exit(0);
|
|
227
265
|
};
|
|
228
266
|
process.on('SIGINT', gracefulShutdown);
|
|
@@ -233,7 +271,7 @@ function startMcpServerBackground() {
|
|
|
233
271
|
// 检查配置是否存在
|
|
234
272
|
const savedConfig = loadConfig();
|
|
235
273
|
if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
|
|
236
|
-
|
|
274
|
+
logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
|
|
237
275
|
process.exit(1);
|
|
238
276
|
}
|
|
239
277
|
// 检查是否已运行
|
|
@@ -249,13 +287,22 @@ function startMcpServerBackground() {
|
|
|
249
287
|
});
|
|
250
288
|
child.unref();
|
|
251
289
|
console.log('[mcp] MCP Server 已在后台启动');
|
|
252
|
-
|
|
290
|
+
logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:18963/mcp`);
|
|
253
291
|
console.log('[mcp] 健康检查: curl http://127.0.0.1:18963/health');
|
|
254
292
|
console.log('[mcp] 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop');
|
|
255
293
|
console.log('[mcp] 调试模式: npx @vrs-soft/wecom-aibot-mcp --debug');
|
|
256
294
|
}
|
|
257
295
|
async function main() {
|
|
258
296
|
const args = process.argv.slice(2);
|
|
297
|
+
// 确定安装模式
|
|
298
|
+
const installMode = args.includes('--http-only') ? 'http-only' :
|
|
299
|
+
args.includes('--channel-only') ? 'channel-only' : 'full';
|
|
300
|
+
// --reinstall 命令需要先删除再安装,跳过开头的 ensureGlobalConfigs
|
|
301
|
+
// --http-only 模式不需要写 MCP 配置
|
|
302
|
+
if (!args.includes('--reinstall') && !args.includes('--http-only')) {
|
|
303
|
+
// 强制覆盖所有全局配置(不依赖智能体)
|
|
304
|
+
ensureGlobalConfigs(installMode);
|
|
305
|
+
}
|
|
259
306
|
// 解析命令行参数
|
|
260
307
|
if (args.includes('--help') || args.includes('-h')) {
|
|
261
308
|
showHelp();
|
|
@@ -265,6 +312,68 @@ async function main() {
|
|
|
265
312
|
showVersion();
|
|
266
313
|
process.exit(0);
|
|
267
314
|
}
|
|
315
|
+
// --upgrade 命令:强制升级全局配置(已在启动时执行,这里显示结果)
|
|
316
|
+
if (args.includes('--upgrade')) {
|
|
317
|
+
console.log('\n[mcp] ✅ 全局配置已更新完成!');
|
|
318
|
+
console.log('[mcp] 配置位置:');
|
|
319
|
+
console.log(' - ~/.claude.json (MCP Server 配置)');
|
|
320
|
+
console.log(' - ~/.claude/settings.local.json (权限和 Hook)');
|
|
321
|
+
console.log(' - ~/.wecom-aibot-mcp/version.json (版本记录)');
|
|
322
|
+
console.log('\n[mcp] 请重启 Claude Code 以加载最新配置');
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}
|
|
325
|
+
// --reinstall 命令:删除所有全局配置(保留机器人配置)后重新安装
|
|
326
|
+
if (args.includes('--reinstall')) {
|
|
327
|
+
logger.log('\n[mcp] 重新安装全局配置...');
|
|
328
|
+
console.log('[mcp] 保留所有机器人配置: ~/.wecom-aibot-mcp/config.json 和 robot-*.json');
|
|
329
|
+
const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
330
|
+
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
|
|
331
|
+
const VERSION_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'version.json');
|
|
332
|
+
const HOOK_SCRIPT = path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.sh');
|
|
333
|
+
// 1. 删除 ~/.claude.json 中的 wecom-aibot 配置
|
|
334
|
+
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
335
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
336
|
+
const config = JSON.parse(content);
|
|
337
|
+
if (config.mcpServers?.['wecom-aibot']) {
|
|
338
|
+
delete config.mcpServers['wecom-aibot'];
|
|
339
|
+
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
340
|
+
console.log('[mcp] 已删除 ~/.claude.json 中的 wecom-aibot 配置');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// 2. 删除 ~/.claude/settings.local.json 中的权限和 Hook
|
|
344
|
+
if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
|
|
345
|
+
const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8');
|
|
346
|
+
const config = JSON.parse(content);
|
|
347
|
+
if (config.permissions?.allow) {
|
|
348
|
+
config.permissions.allow = config.permissions.allow.filter((p) => !p.startsWith('mcp__wecom-aibot__'));
|
|
349
|
+
console.log('[mcp] 已删除 wecom-aibot 工具权限');
|
|
350
|
+
}
|
|
351
|
+
if (config.hooks?.PermissionRequest) {
|
|
352
|
+
config.hooks.PermissionRequest = config.hooks.PermissionRequest.filter((h) => !h.hooks?.some?.((hook) => hook.command?.includes?.('wecom-aibot-mcp')));
|
|
353
|
+
if (config.hooks.PermissionRequest.length === 0) {
|
|
354
|
+
delete config.hooks.PermissionRequest;
|
|
355
|
+
}
|
|
356
|
+
console.log('[mcp] 已删除 PermissionRequest hook');
|
|
357
|
+
}
|
|
358
|
+
fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(config, null, 2));
|
|
359
|
+
}
|
|
360
|
+
// 3. 删除版本文件
|
|
361
|
+
if (fs.existsSync(VERSION_FILE)) {
|
|
362
|
+
fs.unlinkSync(VERSION_FILE);
|
|
363
|
+
console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/version.json');
|
|
364
|
+
}
|
|
365
|
+
// 4. 删除 hook 脚本
|
|
366
|
+
if (fs.existsSync(HOOK_SCRIPT)) {
|
|
367
|
+
fs.unlinkSync(HOOK_SCRIPT);
|
|
368
|
+
console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/permission-hook.sh');
|
|
369
|
+
}
|
|
370
|
+
// 5. 重新安装全局配置
|
|
371
|
+
logger.log('\n[mcp] 正在重新安装...');
|
|
372
|
+
ensureGlobalConfigs();
|
|
373
|
+
logger.log('\n[mcp] ✅ 重新安装完成!');
|
|
374
|
+
console.log('[mcp] 请重启 Claude Code 以加载最新配置');
|
|
375
|
+
process.exit(0);
|
|
376
|
+
}
|
|
268
377
|
if (args.includes('--status') || args.includes('--list')) {
|
|
269
378
|
showStatus();
|
|
270
379
|
process.exit(0);
|
|
@@ -274,6 +383,27 @@ async function main() {
|
|
|
274
383
|
stopServer();
|
|
275
384
|
process.exit(0);
|
|
276
385
|
}
|
|
386
|
+
// --clean-cache 命令:清空 CC 注册表缓存
|
|
387
|
+
if (args.includes('--clean-cache')) {
|
|
388
|
+
if (!isServerRunning()) {
|
|
389
|
+
console.log('[mcp] 服务未运行,无需清理缓存');
|
|
390
|
+
process.exit(0);
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const res = await fetch(`http://127.0.0.1:${HTTP_PORT}/admin/clean-cache`, { method: 'POST' });
|
|
394
|
+
const data = await res.json();
|
|
395
|
+
if (data.ok) {
|
|
396
|
+
console.log(`[mcp] 已清空 CC 注册表,共清理 ${data.cleared} 条`);
|
|
397
|
+
if (data.entries.length > 0) {
|
|
398
|
+
console.log(`[mcp] 已清理: ${data.entries.join(', ')}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
console.error('[mcp] 清理失败:', err);
|
|
404
|
+
}
|
|
405
|
+
process.exit(0);
|
|
406
|
+
}
|
|
277
407
|
// --uninstall 命令:先停止服务再卸载
|
|
278
408
|
if (args.includes('--uninstall')) {
|
|
279
409
|
if (isServerRunning()) {
|
|
@@ -290,8 +420,8 @@ async function main() {
|
|
|
290
420
|
// --delete 命令:删除单个机器人配置
|
|
291
421
|
const deleteIndex = args.indexOf('--delete');
|
|
292
422
|
if (deleteIndex !== -1) {
|
|
293
|
-
const
|
|
294
|
-
await
|
|
423
|
+
const robotName = args[deleteIndex + 1]; // 可选参数:机器人名称
|
|
424
|
+
await deleteRobotConfigInteractive(robotName);
|
|
295
425
|
process.exit(0);
|
|
296
426
|
}
|
|
297
427
|
// --start --foreground:前台启动(内部调用,输出到日志文件)
|
|
@@ -299,12 +429,52 @@ async function main() {
|
|
|
299
429
|
await startMcpServerForeground();
|
|
300
430
|
return; // 保持运行,不 exit
|
|
301
431
|
}
|
|
432
|
+
// --channel:启动 Channel MCP 代理(stdio)
|
|
433
|
+
// 注意:必须在 --debug 之前检查,否则 --channel --debug 会先触发 HTTP Server
|
|
434
|
+
if (args.includes('--channel')) {
|
|
435
|
+
// 检查 HTTP MCP 的 debug 标记文件
|
|
436
|
+
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
437
|
+
const isDebug = fs.existsSync(debugFile) || args.includes('--debug');
|
|
438
|
+
if (isDebug) {
|
|
439
|
+
console.log('[channel] Debug 模式:日志输出到 stderr(跟随 HTTP MCP debug)');
|
|
440
|
+
if (!fs.existsSync(debugFile)) {
|
|
441
|
+
fs.writeFileSync(debugFile, 'true');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
console.log('[channel] Starting Channel MCP Proxy...');
|
|
445
|
+
const { startChannelServer } = await import('./channel-server.js');
|
|
446
|
+
await startChannelServer();
|
|
447
|
+
// Channel MCP 退出时不删除 debug 文件(由 HTTP MCP 管理)
|
|
448
|
+
return; // 保持运行,不 exit
|
|
449
|
+
}
|
|
302
450
|
// --debug:前台启动,日志直接输出到终端
|
|
303
451
|
if (args.includes('--debug')) {
|
|
304
452
|
console.log('[mcp] Debug 模式:前台运行,Ctrl+C 退出');
|
|
305
|
-
await startMcpServerForeground();
|
|
453
|
+
await startMcpServerForeground(true);
|
|
306
454
|
return;
|
|
307
455
|
}
|
|
456
|
+
// --http-only:仅启动 HTTP Server(远程部署场景)
|
|
457
|
+
if (args.includes('--http-only') && !args.includes('--start')) {
|
|
458
|
+
console.log('[mcp] HTTP-only 模式:仅启动 HTTP Server');
|
|
459
|
+
console.log('[mcp] 不写入 MCP 配置(远程部署场景)');
|
|
460
|
+
console.log('[mcp] 使用 --http-only --start 启动服务');
|
|
461
|
+
process.exit(0);
|
|
462
|
+
}
|
|
463
|
+
// --channel-only:仅配置 Channel MCP(本地连接远程 HTTP Server)
|
|
464
|
+
if (args.includes('--channel-only')) {
|
|
465
|
+
const mcpUrl = process.env.MCP_URL;
|
|
466
|
+
if (!mcpUrl) {
|
|
467
|
+
console.log('[mcp] ❌ Channel-only 模式需要指定远程 HTTP MCP 地址');
|
|
468
|
+
console.log('[mcp] 请设置环境变量 MCP_URL:');
|
|
469
|
+
console.log('[mcp] MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only');
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
console.log(`[mcp] Channel-only 模式:Channel MCP 已配置`);
|
|
473
|
+
console.log(`[mcp] 连接地址: ${mcpUrl}`);
|
|
474
|
+
console.log('[mcp] 请确保远程 HTTP Server 已启动');
|
|
475
|
+
console.log('[mcp] 启动 Channel: npx @vrs-soft/wecom-aibot-mcp --channel');
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
308
478
|
// --start:后台启动
|
|
309
479
|
if (args.includes('--start')) {
|
|
310
480
|
startMcpServerBackground();
|
|
@@ -312,12 +482,12 @@ async function main() {
|
|
|
312
482
|
}
|
|
313
483
|
const reconfig = args.includes('--config');
|
|
314
484
|
const isInteractive = process.stdin.isTTY; // 是否为用户交互模式
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
485
|
+
logger.log('');
|
|
486
|
+
logger.log(' ╔════════════════════════════════════════════════════════╗');
|
|
487
|
+
logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
|
|
488
|
+
logger.log(' ║ Claude Code 审批通道 ║');
|
|
489
|
+
logger.log(' ╚════════════════════════════════════════════════════════╝');
|
|
490
|
+
logger.log('');
|
|
321
491
|
// 加载统计并清理旧日志(保留 1 小时)
|
|
322
492
|
loadStats();
|
|
323
493
|
cleanupOldLogs(1 / 24);
|
|
@@ -348,8 +518,8 @@ async function main() {
|
|
|
348
518
|
}
|
|
349
519
|
else {
|
|
350
520
|
// 非 TTY 模式(MCP HTTP),必须有配置
|
|
351
|
-
|
|
352
|
-
|
|
521
|
+
logger.error('[config] 未找到配置,且当前为非交互模式。');
|
|
522
|
+
logger.error('[config] 请在终端运行: npx @vrs-soft/wecom-aibot-mcp --config');
|
|
353
523
|
process.exit(1);
|
|
354
524
|
}
|
|
355
525
|
}
|
|
@@ -362,11 +532,7 @@ async function main() {
|
|
|
362
532
|
const tempClient = initClient(config.botId, config.secret, config.targetUserId || 'placeholder', 'temp-validation');
|
|
363
533
|
const connected = await waitForConnection(tempClient, 10000);
|
|
364
534
|
if (!connected) {
|
|
365
|
-
console.log('[mcp]
|
|
366
|
-
console.log('[mcp] 请检查上面的错误提示,修复后重新配置');
|
|
367
|
-
// 删除无效配置,让用户重新输入
|
|
368
|
-
deleteConfig();
|
|
369
|
-
console.log('\n请检查:');
|
|
535
|
+
console.log('[mcp] ❌ 连接失败,请检查:');
|
|
370
536
|
console.log(' 1. Bot ID 和 Secret 是否正确');
|
|
371
537
|
console.log(' 2. 新建机器人需等待约 2 分钟同步');
|
|
372
538
|
console.log(' 3. 是否已完成授权(机器人详情 → 可使用权限 → 授权)');
|
|
@@ -375,35 +541,37 @@ async function main() {
|
|
|
375
541
|
process.exit(1);
|
|
376
542
|
}
|
|
377
543
|
// 连接成功
|
|
378
|
-
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
544
|
+
logger.log('\n[mcp] ✅ 机器人凭证验证成功!');
|
|
545
|
+
// 保存配置(使用原用户 ID 或等待识别)
|
|
546
|
+
if (!config.targetUserId || config.targetUserId === 'placeholder' || config.targetUserId === '') {
|
|
547
|
+
// 新机器人,需要识别用户 ID
|
|
548
|
+
const userId = await detectUserIdFromMessage(tempClient, 180);
|
|
549
|
+
if (!userId) {
|
|
550
|
+
logger.log('\n[mcp] 未能在规定时间内识别用户 ID');
|
|
551
|
+
console.log('[mcp] 请重新运行配置:npx @vrs-soft/wecom-aibot-mcp --config');
|
|
552
|
+
tempClient.disconnect();
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
config.targetUserId = userId;
|
|
386
556
|
}
|
|
387
|
-
// 更新配置中的用户 ID
|
|
388
|
-
config.targetUserId = userId;
|
|
389
557
|
// 保存最终配置
|
|
390
558
|
saveConfig(config, instanceName);
|
|
391
|
-
|
|
392
|
-
|
|
559
|
+
logger.log('\n[mcp] ✅ 配置完成!');
|
|
560
|
+
logger.log(`[mcp] 用户 ID: ${config.targetUserId}`);
|
|
393
561
|
// 配置完成后断开连接
|
|
394
562
|
tempClient.disconnect();
|
|
395
563
|
// 首次安装后自动后台启动服务
|
|
396
|
-
|
|
564
|
+
logger.log('\n[mcp] 正在后台启动 MCP Server...');
|
|
397
565
|
startMcpServerBackground();
|
|
398
566
|
console.log('[mcp] 请重启 Claude Code 以加载 MCP 服务\n');
|
|
399
567
|
process.exit(0);
|
|
400
568
|
}
|
|
401
569
|
// 已有配置,显示状态并提示启动命令
|
|
402
570
|
showStatus();
|
|
403
|
-
|
|
571
|
+
logger.log('\n[mcp] 使用 --start 启动服务,--stop 停止服务');
|
|
404
572
|
console.log('[mcp] 命令: npx @vrs-soft/wecom-aibot-mcp --start\n');
|
|
405
573
|
}
|
|
406
574
|
main().catch((err) => {
|
|
407
|
-
|
|
575
|
+
logger.error('[mcp] 启动失败:', err);
|
|
408
576
|
process.exit(1);
|
|
409
577
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ccId 注册表
|
|
3
|
+
*
|
|
4
|
+
* 管理 ~/.wecom-aibot-mcp/cc-registry.json
|
|
5
|
+
* 维护 ccId → { robotName, lastActive, createdAt } 的映射
|
|
6
|
+
*
|
|
7
|
+
* 文件锁通过 .lock 文件实现(EEXIST 原子性)
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 设置配置目录(仅用于测试)
|
|
11
|
+
*/
|
|
12
|
+
export declare function setConfigDir(dir: string): void;
|
|
13
|
+
export interface CcRegistryEntry {
|
|
14
|
+
robotName: string;
|
|
15
|
+
lastActive: number;
|
|
16
|
+
createdAt: number;
|
|
17
|
+
lastNotified?: number;
|
|
18
|
+
}
|
|
19
|
+
type Registry = Record<string, CcRegistryEntry>;
|
|
20
|
+
/**
|
|
21
|
+
* 公开接口:独立调用时使用(内部会获取锁)
|
|
22
|
+
*/
|
|
23
|
+
export declare function cleanupExpiredEntries(): void;
|
|
24
|
+
export type RegisterResult = 'registered' | 'renewed' | 'occupied';
|
|
25
|
+
/**
|
|
26
|
+
* 注册 ccId
|
|
27
|
+
* - 新 ccId → registered
|
|
28
|
+
* - 已存在且 robotName 相同 → renewed(续期)
|
|
29
|
+
* - 已存在且 robotName 不同 → occupied(被占用)
|
|
30
|
+
*/
|
|
31
|
+
export declare function registerCcId(ccId: string, robotName: string): RegisterResult;
|
|
32
|
+
/**
|
|
33
|
+
* 注销 ccId
|
|
34
|
+
*/
|
|
35
|
+
export declare function unregisterCcId(ccId: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* 检查 ccId 是否已注册
|
|
38
|
+
*/
|
|
39
|
+
export declare function isCcIdRegistered(ccId: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* 更新 ccId 的最后活跃时间
|
|
42
|
+
*/
|
|
43
|
+
export declare function touchCcId(ccId: string): void;
|
|
44
|
+
/**
|
|
45
|
+
* 获取 ccId 绑定的机器人名称
|
|
46
|
+
*/
|
|
47
|
+
export declare function getCcIdBinding(ccId: string): {
|
|
48
|
+
robotName: string;
|
|
49
|
+
} | null;
|
|
50
|
+
/**
|
|
51
|
+
* 获取完整注册表(调试用)
|
|
52
|
+
*/
|
|
53
|
+
export declare function getRegistry(): Registry;
|
|
54
|
+
/**
|
|
55
|
+
* 启动心跳检测(5 分钟扫描一次)
|
|
56
|
+
*/
|
|
57
|
+
export declare function startHeartbeatMonitor(): void;
|
|
58
|
+
/**
|
|
59
|
+
* 停止心跳检测
|
|
60
|
+
*/
|
|
61
|
+
export declare function stopHeartbeatMonitor(): void;
|
|
62
|
+
export {};
|