@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/bin.js
CHANGED
|
@@ -1,118 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* wecom-aibot-mcp - 企业微信智能机器人 MCP
|
|
3
|
+
* wecom-aibot-mcp - 企业微信智能机器人 MCP 客户端
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 连接远程 wecom-aibot-server daemon,为 Claude Code 提供微信消息通道。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 运行模式:
|
|
8
|
+
* --channel Channel MCP 代理(SSE 唤醒,推荐)
|
|
9
|
+
* --install 交互式安装向导(配置 daemon 地址 + Token)
|
|
10
|
+
* --version 版本号
|
|
11
|
+
* --help 帮助
|
|
11
12
|
*/
|
|
12
|
-
import { spawn, execSync } from 'child_process';
|
|
13
|
-
import * as fs from 'fs';
|
|
14
13
|
import * as path from 'path';
|
|
15
14
|
import * as os from 'os';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import { registerTools } from './tools/index.js';
|
|
19
|
-
import { startHttpServer, stopHttpServer, HTTP_PORT } from './http-server.js';
|
|
20
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
21
|
-
import { getAllConnectionStates } from './connection-manager.js';
|
|
22
|
-
import { loadStats, cleanupOldLogs } from './connection-log.js';
|
|
23
|
-
import { startKeepaliveMonitor, stopKeepaliveMonitor } from './keepalive-monitor.js';
|
|
15
|
+
import { VERSION, runRemoteInstallWizard, uninstall, getInstalledMode } from './config-wizard.js';
|
|
16
|
+
import { startChannelServer } from './channel-server.js';
|
|
24
17
|
import { logger } from './logger.js';
|
|
25
|
-
const
|
|
18
|
+
const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
|
|
26
19
|
function showHelp() {
|
|
27
20
|
console.log(`
|
|
28
|
-
企业微信智能机器人 MCP
|
|
21
|
+
企业微信智能机器人 MCP 客户端 v${VERSION}
|
|
29
22
|
|
|
30
23
|
安装:
|
|
31
|
-
npx @vrs-soft/wecom-aibot-mcp
|
|
24
|
+
npx @vrs-soft/wecom-aibot-mcp --install
|
|
32
25
|
|
|
33
26
|
用法:
|
|
34
27
|
npx @vrs-soft/wecom-aibot-mcp [选项]
|
|
35
28
|
|
|
36
29
|
选项:
|
|
37
|
-
--help, -h
|
|
30
|
+
--help, -h 显示帮助
|
|
38
31
|
--version, -v 显示版本号
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--setup --server --channel 本地完整安装(HTTP + Channel)
|
|
43
|
-
--upgrade 强制升级全局配置(覆盖 MCP 配置、权限、skill)
|
|
44
|
-
--reinstall 重新安装全局配置(删除后重新写入,保留机器人配置)
|
|
45
|
-
--start 启动 HTTP MCP Server(后台守护进程,日志写 server.log)
|
|
46
|
-
--stop 停止 MCP Server
|
|
47
|
-
--debug 前台启动 + debug 级日志(日志同时落 server.log,stdout 实时打印)
|
|
48
|
-
--channel 启动 Channel MCP Proxy(stdio 代理 + SSE 唤醒,日志写 channel.log)
|
|
49
|
-
--http-only 仅写 HTTP-only 配置(已废弃;--start 默认就是 daemon-only 行为)
|
|
50
|
-
--channel-only 仅配置 Channel MCP(本地连接远程 HTTP Server)
|
|
51
|
-
--status 显示服务状态和机器人配置
|
|
52
|
-
--config 重新配置默认机器人(修改 Bot ID / Secret / 目标用户)
|
|
53
|
-
--add 添加新的机器人配置(多机器人场景)
|
|
54
|
-
--rename [名称] 重命名机器人(可选参数:旧名称,交互式输入新名称)
|
|
55
|
-
--list 列出所有已配置的机器人及其占用状态
|
|
56
|
-
--delete [名称] 删除指定的机器人配置(保留 MCP 配置)
|
|
57
|
-
--uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
|
|
58
|
-
--set-token [token] 设置/清除 Auth Token(远程部署用,--set-token --clear 清除)
|
|
59
|
-
--clean-cache 清空 CC 注册表缓存(清理异常断线残留的 ccId)
|
|
32
|
+
--install 交互式安装向导(配置 daemon 地址 + Auth Token)
|
|
33
|
+
--channel 启动 Channel MCP 代理(stdio,日志写 channel.log)
|
|
34
|
+
--uninstall 卸载并清除所有本地配置
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
连接模式:
|
|
37
|
+
Channel(推荐): SSE 长连接,消息到达即唤醒 agent
|
|
38
|
+
配置写入 ~/.claude.json: wecom-aibot-channel
|
|
39
|
+
环境变量: MCP_URL=<daemon 地址>, MCP_AUTH_TOKEN=<token>
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
41
|
+
HTTP(直连): Claude Code 直接连接 daemon /mcp 端点
|
|
42
|
+
安装时选择 HTTP 模式,或手动写入 ~/.claude.json
|
|
67
43
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
远程服务器:
|
|
73
|
-
npx @vrs-soft/wecom-aibot-mcp --start
|
|
74
|
-
# 启动 HTTP Server(daemon 不会写本地 client MCP 配置)
|
|
75
|
-
|
|
76
|
-
本地机器:
|
|
77
|
-
MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only
|
|
78
|
-
# 必须通过 MCP_URL 指定远程 HTTP MCP 地址
|
|
79
|
-
# 只配置 Channel MCP,连接远程 HTTP Server
|
|
80
|
-
|
|
81
|
-
MCP 配置(默认安装同时配置两种模式):
|
|
82
|
-
|
|
83
|
-
HTTP Transport(轮询模式):
|
|
84
|
-
"wecom-aibot": {
|
|
85
|
-
"type": "http",
|
|
86
|
-
"url": "http://127.0.0.1:18963/mcp"
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
Channel Transport(SSE 推送模式):
|
|
90
|
-
"wecom-aibot-channel": {
|
|
91
|
-
"command": "npx",
|
|
92
|
-
"args": ["@vrs-soft/wecom-aibot-mcp", "--channel"]
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
Channel 模式优势:微信消息自动唤醒 agent,无需主动轮询
|
|
96
|
-
启动 Channel 模式(研究预览):
|
|
97
|
-
claude --dangerously-load-development-channels server:wecom-aibot-channel
|
|
98
|
-
|
|
99
|
-
日志:
|
|
100
|
-
|
|
101
|
-
Daemon (HTTP MCP):
|
|
102
|
-
~/.wecom-aibot-mcp/server.log info 级(永久)+ debug 级(仅 --debug 时写)
|
|
103
|
-
~/.wecom-aibot-mcp/server.log.1..5 自动滚动备份(每份 ≤10MB,共保留 5 份)
|
|
104
|
-
|
|
105
|
-
Channel MCP:
|
|
106
|
-
~/.wecom-aibot-mcp/channel.log info 级(永久)+ debug 级(仅 debug 模式时写)
|
|
107
|
-
~/.wecom-aibot-mcp/channel.log.1..5 自动滚动备份
|
|
108
|
-
|
|
109
|
-
WebSocket 连接事件:
|
|
110
|
-
~/.wecom-aibot-mcp/connection.log 专用文件(connect/disconnect/auth 事件)
|
|
111
|
-
|
|
112
|
-
Debug 标记:
|
|
113
|
-
~/.wecom-aibot-mcp/debug 存在则启用 debug 级输出(--debug 时自动创建)
|
|
114
|
-
|
|
115
|
-
日志格式: JSON Lines,每行一个 {ts, level, msg, data?},方便 jq/grep 检索
|
|
44
|
+
前提条件:
|
|
45
|
+
需要运行中的 wecom-aibot-server daemon(私有部署)
|
|
46
|
+
daemon 地址示例: https://your-server:18963
|
|
116
47
|
|
|
117
48
|
更多信息: https://github.com/eric2877/wecom-aibot-mcp
|
|
118
49
|
`);
|
|
@@ -120,294 +51,8 @@ MCP 配置(默认安装同时配置两种模式):
|
|
|
120
51
|
function showVersion() {
|
|
121
52
|
console.log(`wecom-aibot-mcp v${VERSION}`);
|
|
122
53
|
}
|
|
123
|
-
function showStatus() {
|
|
124
|
-
const allRobots = listAllRobots();
|
|
125
|
-
const connections = getAllConnectionStates();
|
|
126
|
-
const authToken = getAuthToken();
|
|
127
|
-
// 检查服务是否运行
|
|
128
|
-
const serverRunning = isServerRunning();
|
|
129
|
-
console.log(`\n服务状态: ${serverRunning ? '✅ 运行中' : '❌ 未启动'}`);
|
|
130
|
-
// 显示 Auth Token 状态(带部分 token 显示)
|
|
131
|
-
if (authToken) {
|
|
132
|
-
const maskedToken = authToken.length > 12
|
|
133
|
-
? `${authToken.slice(0, 8)}...${authToken.slice(-4)}`
|
|
134
|
-
: `${authToken.slice(0, 4)}...`;
|
|
135
|
-
console.log(`Auth Token: ✅ 已配置 (${maskedToken})`);
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
console.log(`Auth Token: (未配置,本地部署无需 token)`);
|
|
139
|
-
}
|
|
140
|
-
console.log('');
|
|
141
|
-
if (allRobots.length === 0) {
|
|
142
|
-
console.log('尚未配置机器人,请运行 npx @vrs-soft/wecom-aibot-mcp 启动配置向导');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
// 构建机器人占用信息
|
|
146
|
-
const robotUsage = new Map();
|
|
147
|
-
for (const conn of connections) {
|
|
148
|
-
if (conn.agentName) {
|
|
149
|
-
robotUsage.set(conn.robotName, { agentName: conn.agentName });
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
console.log(`已配置 ${allRobots.length} 个机器人:\n`);
|
|
153
|
-
for (const robot of allRobots) {
|
|
154
|
-
const usage = robotUsage.get(robot.name);
|
|
155
|
-
const statusTag = usage ? ` [使用中]` : '';
|
|
156
|
-
const docTag = robot.doc_mcp_url ? ' [文档✅]' : '';
|
|
157
|
-
console.log(` Bot名称: ${robot.name}${statusTag}${docTag}`);
|
|
158
|
-
console.log(` Bot ID: ${robot.botId}`);
|
|
159
|
-
console.log(` 目标用户:${robot.targetUserId}`);
|
|
160
|
-
if (usage) {
|
|
161
|
-
console.log(` 使用者: ${usage.agentName}`);
|
|
162
|
-
}
|
|
163
|
-
console.log('');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// 检查服务是否运行
|
|
167
|
-
function isServerRunning() {
|
|
168
|
-
if (!fs.existsSync(PID_FILE)) {
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
try {
|
|
172
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
173
|
-
// 检查进程是否存在
|
|
174
|
-
process.kill(pid, 0);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
// 进程不存在,清理 PID 文件(可能已被进程自身删除)
|
|
179
|
-
if (fs.existsSync(PID_FILE)) {
|
|
180
|
-
fs.unlinkSync(PID_FILE);
|
|
181
|
-
}
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// 通过端口查找进程 PID(fallback,当 PID 文件不存在时)
|
|
186
|
-
function findPidByPort(port) {
|
|
187
|
-
try {
|
|
188
|
-
// Linux: ss -tlnp | grep :18963
|
|
189
|
-
const output = execSync(`ss -tlnp 2>/dev/null | grep ':${port}'`, { encoding: 'utf-8' });
|
|
190
|
-
const match = output.match(/pid=(\d+)/);
|
|
191
|
-
if (match)
|
|
192
|
-
return parseInt(match[1]);
|
|
193
|
-
}
|
|
194
|
-
catch { /* ignore */ }
|
|
195
|
-
try {
|
|
196
|
-
// macOS: lsof -ti :18963
|
|
197
|
-
const output = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
198
|
-
if (output)
|
|
199
|
-
return parseInt(output.split('\n')[0]);
|
|
200
|
-
}
|
|
201
|
-
catch { /* ignore */ }
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
// 停止服务
|
|
205
|
-
function stopServer() {
|
|
206
|
-
let pid = null;
|
|
207
|
-
// 优先从 PID 文件获取
|
|
208
|
-
if (fs.existsSync(PID_FILE)) {
|
|
209
|
-
pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
|
|
210
|
-
// 检查进程是否存在
|
|
211
|
-
try {
|
|
212
|
-
process.kill(pid, 0);
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
// PID 文件残留但进程已死,清理 PID 文件
|
|
216
|
-
console.log('[mcp] PID 文件残留,进程已退出,清理中...');
|
|
217
|
-
fs.unlinkSync(PID_FILE);
|
|
218
|
-
pid = null;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// PID 文件不存在或残留:通过端口查找
|
|
222
|
-
if (pid === null) {
|
|
223
|
-
pid = findPidByPort(HTTP_PORT);
|
|
224
|
-
if (pid === null) {
|
|
225
|
-
console.log('[mcp] 服务未运行');
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
228
|
-
console.log(`[mcp] 通过端口 ${HTTP_PORT} 找到进程 PID: ${pid}`);
|
|
229
|
-
}
|
|
230
|
-
// 发送 SIGTERM
|
|
231
|
-
try {
|
|
232
|
-
process.kill(pid, 'SIGTERM');
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// ESRCH: 进程不存在,清理即可
|
|
236
|
-
if (fs.existsSync(PID_FILE))
|
|
237
|
-
fs.unlinkSync(PID_FILE);
|
|
238
|
-
console.log('[mcp] 服务已停止');
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
// 等待进程退出(最多 5 秒)
|
|
242
|
-
const deadline = Date.now() + 5000;
|
|
243
|
-
while (Date.now() < deadline) {
|
|
244
|
-
try {
|
|
245
|
-
process.kill(pid, 0);
|
|
246
|
-
// 进程还在,同步等待 100ms
|
|
247
|
-
const waitUntil = Date.now() + 100;
|
|
248
|
-
while (Date.now() < waitUntil) { /* busy wait */ }
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// 清理 PID 文件
|
|
255
|
-
if (fs.existsSync(PID_FILE)) {
|
|
256
|
-
fs.unlinkSync(PID_FILE);
|
|
257
|
-
}
|
|
258
|
-
console.log('[mcp] 服务已停止');
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
// 等待连接验证(用于配置向导验证凭证)
|
|
262
|
-
async function waitForConnection(client, timeoutMs = 10000) {
|
|
263
|
-
return new Promise((resolve) => {
|
|
264
|
-
const startTime = Date.now();
|
|
265
|
-
const checkInterval = setInterval(() => {
|
|
266
|
-
if (client.isConnected()) {
|
|
267
|
-
clearInterval(checkInterval);
|
|
268
|
-
resolve(true);
|
|
269
|
-
}
|
|
270
|
-
else if (Date.now() - startTime > timeoutMs) {
|
|
271
|
-
clearInterval(checkInterval);
|
|
272
|
-
resolve(false);
|
|
273
|
-
}
|
|
274
|
-
}, 500);
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
// 启动 MCP Server(前台运行,供 --start 使用)
|
|
278
|
-
async function startMcpServerForeground(isDebug = false) {
|
|
279
|
-
const savedConfig = loadConfig();
|
|
280
|
-
if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
|
|
281
|
-
logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
|
|
282
|
-
process.exit(1);
|
|
283
|
-
}
|
|
284
|
-
// 写入 PID 文件
|
|
285
|
-
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
286
|
-
// Debug 模式:创建 debug 标记文件
|
|
287
|
-
if (isDebug) {
|
|
288
|
-
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
289
|
-
fs.writeFileSync(debugFile, 'true');
|
|
290
|
-
console.log('[mcp] Debug 标记文件已创建');
|
|
291
|
-
}
|
|
292
|
-
// 配置统一日志输出到文件(JSON Lines + 自动滚动)
|
|
293
|
-
logger.setLogFile(path.join(os.homedir(), '.wecom-aibot-mcp', 'server.log'));
|
|
294
|
-
if (isDebug)
|
|
295
|
-
logger.setDebug(true);
|
|
296
|
-
// 确保 hook 已安装
|
|
297
|
-
ensureHookInstalled();
|
|
298
|
-
// 加载统计并清理旧日志
|
|
299
|
-
loadStats();
|
|
300
|
-
cleanupOldLogs(1 / 24);
|
|
301
|
-
// 创建 MCP Server
|
|
302
|
-
const server = new McpServer({
|
|
303
|
-
name: 'wecom-aibot-mcp',
|
|
304
|
-
version: VERSION,
|
|
305
|
-
});
|
|
306
|
-
registerTools(server);
|
|
307
|
-
// 启动 HTTP 服务
|
|
308
|
-
logger.log('');
|
|
309
|
-
logger.log(' ╔════════════════════════════════════════════════════════╗');
|
|
310
|
-
logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
|
|
311
|
-
logger.log(' ║ Claude Code 审批通道 ║');
|
|
312
|
-
logger.log(' ╚════════════════════════════════════════════════════════╝');
|
|
313
|
-
logger.log('');
|
|
314
|
-
const httpsConfig = getHttpsConfig() ?? undefined;
|
|
315
|
-
const protocol = httpsConfig ? 'HTTPS' : 'HTTP';
|
|
316
|
-
logger.log(`[mcp] 启动 MCP ${protocol} Server (端口: ${HTTP_PORT})...`);
|
|
317
|
-
await startHttpServer(server, HTTP_PORT, httpsConfig);
|
|
318
|
-
startKeepaliveMonitor();
|
|
319
|
-
logger.log(`[mcp] MCP Server 已就绪`);
|
|
320
|
-
logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
|
|
321
|
-
logger.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
|
|
322
|
-
logger.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
|
|
323
|
-
logger.log(`[mcp] PID: ${process.pid}`);
|
|
324
|
-
// 写入启动事件到 server.log(永久记录)
|
|
325
|
-
logger.info('daemon started', { version: VERSION, port: HTTP_PORT, protocol, pid: process.pid, debug: isDebug });
|
|
326
|
-
// 退出处理
|
|
327
|
-
const gracefulShutdown = () => {
|
|
328
|
-
console.log('[mcp] 正在关闭...');
|
|
329
|
-
logger.info('daemon shutdown', { pid: process.pid });
|
|
330
|
-
stopKeepaliveMonitor();
|
|
331
|
-
stopHttpServer();
|
|
332
|
-
if (fs.existsSync(PID_FILE)) {
|
|
333
|
-
fs.unlinkSync(PID_FILE);
|
|
334
|
-
}
|
|
335
|
-
// Debug 模式:删除 debug 标记文件
|
|
336
|
-
if (isDebug) {
|
|
337
|
-
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
338
|
-
if (fs.existsSync(debugFile)) {
|
|
339
|
-
fs.unlinkSync(debugFile);
|
|
340
|
-
console.log('[mcp] Debug 标记文件已删除');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
process.exit(0);
|
|
344
|
-
};
|
|
345
|
-
process.on('SIGINT', gracefulShutdown);
|
|
346
|
-
process.on('SIGTERM', gracefulShutdown);
|
|
347
|
-
}
|
|
348
|
-
// 后台启动 MCP Server(使用 spawn)
|
|
349
|
-
function startMcpServerBackground() {
|
|
350
|
-
// 检查配置是否存在
|
|
351
|
-
const savedConfig = loadConfig();
|
|
352
|
-
if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
|
|
353
|
-
logger.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
|
|
354
|
-
process.exit(1);
|
|
355
|
-
}
|
|
356
|
-
// 检查是否已运行
|
|
357
|
-
if (isServerRunning()) {
|
|
358
|
-
console.log('[mcp] 服务已在运行中');
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
const nodePath = process.execPath;
|
|
362
|
-
const scriptPath = process.argv[1];
|
|
363
|
-
const child = spawn(nodePath, [scriptPath, '--start', '--foreground'], {
|
|
364
|
-
detached: true,
|
|
365
|
-
stdio: 'ignore',
|
|
366
|
-
});
|
|
367
|
-
child.unref();
|
|
368
|
-
console.log('[mcp] MCP Server 已在后台启动');
|
|
369
|
-
logger.log(`[mcp] HTTP endpoint: http://127.0.0.1:18963/mcp`);
|
|
370
|
-
console.log('[mcp] 健康检查: curl http://127.0.0.1:18963/health');
|
|
371
|
-
console.log('[mcp] 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop');
|
|
372
|
-
console.log('[mcp] 调试模式: npx @vrs-soft/wecom-aibot-mcp --debug');
|
|
373
|
-
}
|
|
374
54
|
async function main() {
|
|
375
55
|
const args = process.argv.slice(2);
|
|
376
|
-
// 确定安装模式:优先 CLI flag,其次复用 version.json 里上次的 mode(保持 remote / channel-only 等模式不被 --upgrade 打回 full)
|
|
377
|
-
const explicitMode = args.includes('--http-only') ? 'http-only' :
|
|
378
|
-
args.includes('--channel-only') ? 'channel-only' : undefined;
|
|
379
|
-
const prior = getInstalledMode();
|
|
380
|
-
const installMode = explicitMode || prior.mode || 'full';
|
|
381
|
-
const remoteOptions = (installMode === 'remote' || installMode === 'remote-channel') && prior.remote?.url
|
|
382
|
-
? { url: prior.remote.url, token: prior.remote.token || '' }
|
|
383
|
-
: undefined;
|
|
384
|
-
// 以下命令跳过顶部 ensureGlobalConfigs,避免覆盖配置
|
|
385
|
-
// --setup: 向导完成后自己调用
|
|
386
|
-
// --channel: 作为 Channel MCP 代理运行,不应改写全局配置
|
|
387
|
-
// --reinstall / --http-only: 有自己的处理逻辑
|
|
388
|
-
// --version / -v: 只查版本,不写配置
|
|
389
|
-
// --stop / --status / --list / --clean-cache / --set-token / --config: 管理命令,不应改写配置
|
|
390
|
-
const skipEnsure = args.includes('--reinstall') || args.includes('--http-only') ||
|
|
391
|
-
args.includes('--setup') || args.includes('--channel') ||
|
|
392
|
-
args.includes('--version') || args.includes('-v') ||
|
|
393
|
-
args.includes('--start') || args.includes('--debug') ||
|
|
394
|
-
args.includes('--stop') || args.includes('--status') || args.includes('--list') ||
|
|
395
|
-
args.includes('--clean-cache') || args.includes('--set-token') || args.includes('--config');
|
|
396
|
-
if (!skipEnsure) {
|
|
397
|
-
// 强制覆盖所有全局配置(不依赖智能体)
|
|
398
|
-
if (installMode === 'remote' || installMode === 'remote-channel') {
|
|
399
|
-
if (remoteOptions) {
|
|
400
|
-
ensureGlobalConfigs(installMode, remoteOptions);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
console.log(`[mcp] 检测到上次安装模式 ${installMode},但缺少远程参数;跳过配置写入。如需变更请使用 --setup`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
ensureGlobalConfigs(installMode);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
// 解析命令行参数
|
|
411
56
|
if (args.includes('--help') || args.includes('-h')) {
|
|
412
57
|
showHelp();
|
|
413
58
|
process.exit(0);
|
|
@@ -416,438 +61,36 @@ async function main() {
|
|
|
416
61
|
showVersion();
|
|
417
62
|
process.exit(0);
|
|
418
63
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.log('[mcp] 配置位置:');
|
|
423
|
-
console.log(' - ~/.claude.json (MCP Server 配置)');
|
|
424
|
-
console.log(' - ~/.claude/settings.local.json (权限和 Hook)');
|
|
425
|
-
console.log(' - ~/.wecom-aibot-mcp/version.json (版本记录)');
|
|
426
|
-
console.log('\n[mcp] 请重启 Claude Code 以加载最新配置');
|
|
427
|
-
process.exit(0);
|
|
428
|
-
}
|
|
429
|
-
// --reinstall 命令:删除所有全局配置(保留机器人配置)后重新安装
|
|
430
|
-
if (args.includes('--reinstall')) {
|
|
431
|
-
logger.log('\n[mcp] 重新安装全局配置...');
|
|
432
|
-
console.log('[mcp] 保留所有机器人配置: ~/.wecom-aibot-mcp/robot-*.json');
|
|
433
|
-
const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
434
|
-
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
|
|
435
|
-
const VERSION_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'version.json');
|
|
436
|
-
const HOOK_SCRIPT = path.join(os.homedir(), '.wecom-aibot-mcp', 'permission-hook.sh');
|
|
437
|
-
// 1. 删除 ~/.claude.json 中的 wecom-aibot 配置
|
|
438
|
-
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
439
|
-
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
440
|
-
const config = JSON.parse(content);
|
|
441
|
-
if (config.mcpServers?.['wecom-aibot']) {
|
|
442
|
-
delete config.mcpServers['wecom-aibot'];
|
|
443
|
-
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
444
|
-
console.log('[mcp] 已删除 ~/.claude.json 中的 wecom-aibot 配置');
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
// 2. 删除 ~/.claude/settings.local.json 中的权限和 Hook
|
|
448
|
-
if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
|
|
449
|
-
const content = fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8');
|
|
450
|
-
const config = JSON.parse(content);
|
|
451
|
-
if (config.permissions?.allow) {
|
|
452
|
-
config.permissions.allow = config.permissions.allow.filter((p) => !p.startsWith('mcp__wecom-aibot__'));
|
|
453
|
-
console.log('[mcp] 已删除 wecom-aibot 工具权限');
|
|
454
|
-
}
|
|
455
|
-
if (config.hooks?.PermissionRequest) {
|
|
456
|
-
config.hooks.PermissionRequest = config.hooks.PermissionRequest.filter((h) => !h.hooks?.some?.((hook) => hook.command?.includes?.('wecom-aibot-mcp')));
|
|
457
|
-
if (config.hooks.PermissionRequest.length === 0) {
|
|
458
|
-
delete config.hooks.PermissionRequest;
|
|
459
|
-
}
|
|
460
|
-
console.log('[mcp] 已删除 PermissionRequest hook');
|
|
461
|
-
}
|
|
462
|
-
fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(config, null, 2));
|
|
463
|
-
}
|
|
464
|
-
// 3. 删除版本文件
|
|
465
|
-
if (fs.existsSync(VERSION_FILE)) {
|
|
466
|
-
fs.unlinkSync(VERSION_FILE);
|
|
467
|
-
console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/version.json');
|
|
468
|
-
}
|
|
469
|
-
// 4. 删除 hook 脚本
|
|
470
|
-
if (fs.existsSync(HOOK_SCRIPT)) {
|
|
471
|
-
fs.unlinkSync(HOOK_SCRIPT);
|
|
472
|
-
console.log('[mcp] 已删除 ~/.wecom-aibot-mcp/permission-hook.sh');
|
|
473
|
-
}
|
|
474
|
-
// 5. 重新安装全局配置
|
|
475
|
-
logger.log('\n[mcp] 正在重新安装...');
|
|
476
|
-
ensureGlobalConfigs();
|
|
477
|
-
logger.log('\n[mcp] ✅ 重新安装完成!');
|
|
478
|
-
console.log('[mcp] 请重启 Claude Code 以加载最新配置');
|
|
479
|
-
process.exit(0);
|
|
480
|
-
}
|
|
481
|
-
if (args.includes('--status') || args.includes('--list')) {
|
|
482
|
-
showStatus();
|
|
483
|
-
process.exit(0);
|
|
484
|
-
}
|
|
485
|
-
// --stop 命令:停止服务
|
|
486
|
-
if (args.includes('--stop')) {
|
|
487
|
-
stopServer();
|
|
488
|
-
process.exit(0);
|
|
489
|
-
}
|
|
490
|
-
// --clean-cache 命令:清空 CC 注册表缓存
|
|
491
|
-
if (args.includes('--clean-cache')) {
|
|
492
|
-
if (!isServerRunning()) {
|
|
493
|
-
console.log('[mcp] 服务未运行,无需清理缓存');
|
|
494
|
-
process.exit(0);
|
|
495
|
-
}
|
|
496
|
-
try {
|
|
497
|
-
const res = await fetch(`http://127.0.0.1:${HTTP_PORT}/admin/clean-cache`, { method: 'POST' });
|
|
498
|
-
const data = await res.json();
|
|
499
|
-
if (data.ok) {
|
|
500
|
-
console.log(`[mcp] 已清空 CC 注册表,共清理 ${data.cleared} 条`);
|
|
501
|
-
if (data.entries.length > 0) {
|
|
502
|
-
console.log(`[mcp] 已清理: ${data.entries.join(', ')}`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
catch (err) {
|
|
507
|
-
console.error('[mcp] 清理失败:', err);
|
|
508
|
-
}
|
|
509
|
-
process.exit(0);
|
|
510
|
-
}
|
|
511
|
-
// --uninstall 命令:先停止服务再卸载
|
|
512
|
-
if (args.includes('--uninstall')) {
|
|
513
|
-
if (isServerRunning()) {
|
|
514
|
-
console.log('[mcp] 正在停止服务...');
|
|
515
|
-
stopServer();
|
|
516
|
-
}
|
|
517
|
-
uninstall();
|
|
518
|
-
process.exit(0);
|
|
519
|
-
}
|
|
520
|
-
// --set-token 命令:设置/清除 Auth Token
|
|
521
|
-
if (args.includes('--set-token')) {
|
|
522
|
-
const tokenIndex = args.indexOf('--set-token');
|
|
523
|
-
const clearToken = args.includes('--clear');
|
|
524
|
-
if (clearToken) {
|
|
525
|
-
setAuthToken(undefined);
|
|
526
|
-
updateMcpAuthHeaders(undefined);
|
|
527
|
-
console.log('[mcp] ✅ Auth Token 已清除(服务端 + 客户端 MCP 配置)');
|
|
528
|
-
process.exit(0);
|
|
529
|
-
}
|
|
530
|
-
// 检查下一个参数是否是 token(不是另一个 --flag)
|
|
531
|
-
const nextArg = args[tokenIndex + 1];
|
|
532
|
-
const token = (nextArg && !nextArg.startsWith('--')) ? nextArg : undefined;
|
|
533
|
-
if (!token) {
|
|
534
|
-
// 交互式输入 token
|
|
535
|
-
const readline = await import('readline');
|
|
536
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
537
|
-
const input = await new Promise((resolve) => {
|
|
538
|
-
rl.question('请输入 Auth Token(留空取消): ', (answer) => {
|
|
539
|
-
rl.close();
|
|
540
|
-
resolve(answer.trim());
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
if (!input) {
|
|
544
|
-
console.log('[mcp] 已取消');
|
|
545
|
-
process.exit(0);
|
|
546
|
-
}
|
|
547
|
-
setAuthToken(input);
|
|
548
|
-
updateMcpAuthHeaders(input);
|
|
549
|
-
console.log('[mcp] ✅ Auth Token 已设置');
|
|
550
|
-
console.log(`[mcp] 服务端: ~/.wecom-aibot-mcp/server.json`);
|
|
551
|
-
console.log(`[mcp] 客户端: ~/.claude.json MCP headers 已同步`);
|
|
552
|
-
console.log(`[mcp] Token: ${input.slice(0, 8)}...${input.slice(-4)}`);
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
setAuthToken(token);
|
|
556
|
-
updateMcpAuthHeaders(token);
|
|
557
|
-
console.log('[mcp] ✅ Auth Token 已设置');
|
|
558
|
-
console.log(`[mcp] Token: ${token.slice(0, 8)}...${token.slice(-4)}`);
|
|
559
|
-
}
|
|
560
|
-
process.exit(0);
|
|
561
|
-
}
|
|
562
|
-
if (args.includes('--add')) {
|
|
563
|
-
await addMcpConfig();
|
|
564
|
-
process.exit(0);
|
|
565
|
-
}
|
|
566
|
-
// --delete 命令:删除单个机器人配置
|
|
567
|
-
const deleteIndex = args.indexOf('--delete');
|
|
568
|
-
if (deleteIndex !== -1) {
|
|
569
|
-
const robotName = args[deleteIndex + 1]; // 可选参数:机器人名称
|
|
570
|
-
await deleteRobotConfigInteractive(robotName);
|
|
571
|
-
process.exit(0);
|
|
572
|
-
}
|
|
573
|
-
// --start --foreground:前台启动(内部调用,输出到日志文件)
|
|
574
|
-
if (args.includes('--start') && args.includes('--foreground')) {
|
|
575
|
-
await startMcpServerForeground();
|
|
576
|
-
return; // 保持运行,不 exit
|
|
577
|
-
}
|
|
578
|
-
// --setup:统一安装向导
|
|
579
|
-
// --setup → 交互式(询问本地 / 远程)
|
|
580
|
-
// --setup --server → 服务器端(机器人配置 + Token)
|
|
581
|
-
// --setup --channel → Channel 客户端(写入 Channel MCP)
|
|
582
|
-
// --setup --server --channel → 本地完整安装(HTTP + Channel)
|
|
583
|
-
if (args.includes('--setup')) {
|
|
584
|
-
const wantServer = args.includes('--server');
|
|
585
|
-
const wantChannel = args.includes('--channel');
|
|
586
|
-
if (wantServer && wantChannel) {
|
|
587
|
-
// 本地完整安装
|
|
588
|
-
console.log('\n[setup] 本地完整安装模式\n');
|
|
589
|
-
const savedConfig = loadConfig();
|
|
590
|
-
if (!savedConfig?.botId)
|
|
591
|
-
await runConfigWizard();
|
|
592
|
-
ensureGlobalConfigs('full');
|
|
593
|
-
startMcpServerBackground();
|
|
594
|
-
console.log('[setup] 安装完成!请重启 Claude Code 以加载配置');
|
|
595
|
-
}
|
|
596
|
-
else if (wantServer) {
|
|
597
|
-
// 服务器端:分两步——先完成 Server 安装,再配置机器人
|
|
598
|
-
console.log('\n[setup] ─── 步骤 1/2:Server 安装 ───\n');
|
|
599
|
-
console.log(' Server 负责运行 HTTP MCP 服务,Bot 配置在下一步单独完成\n');
|
|
600
|
-
const readline = await import('readline');
|
|
601
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
602
|
-
const token = await new Promise(resolve => rl.question('Auth Token(Client 端连接时需填写相同 Token,留空跳过): ', a => { rl.close(); resolve(a.trim()); }));
|
|
603
|
-
if (token)
|
|
604
|
-
setAuthToken(token);
|
|
605
|
-
// HTTPS 证书配置
|
|
606
|
-
const defaultCertPath = path.join(os.homedir(), '.wecom-aibot-mcp', 'cert.pem');
|
|
607
|
-
console.log('\n HTTPS 证书配置(留空跳过,保持 HTTP 模式)');
|
|
608
|
-
console.log(' 请输入完整路径含文件名(.pem / .crt / .key 均可),例如:');
|
|
609
|
-
console.log(` ${defaultCertPath}`);
|
|
610
|
-
console.log(' /etc/letsencrypt/live/example.com/fullchain.pem');
|
|
611
|
-
console.log(' /etc/gitlab/ssl/gitlab.example.com.crt\n');
|
|
612
|
-
const checkFile = (p, label) => {
|
|
613
|
-
if (!fs.existsSync(p)) {
|
|
614
|
-
console.log(`[setup] ⚠️ ${label}文件不存在: ${p}`);
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
if (fs.statSync(p).isDirectory()) {
|
|
618
|
-
console.log(`[setup] ⚠️ ${label}路径是目录而非文件: ${p}`);
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
return true;
|
|
622
|
-
};
|
|
623
|
-
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
624
|
-
const certInput = await new Promise(resolve => rl2.question(`SSL 证书文件完整路径(留空跳过): `, a => { rl2.close(); resolve(a.trim()); }));
|
|
625
|
-
if (certInput) {
|
|
626
|
-
if (!checkFile(certInput, '证书')) {
|
|
627
|
-
console.log('[setup] 跳过 HTTPS 配置');
|
|
628
|
-
}
|
|
629
|
-
else {
|
|
630
|
-
const rl3 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
631
|
-
const keyInput = await new Promise(resolve => rl3.question(`SSL 私钥文件完整路径: `, a => { rl3.close(); resolve(a.trim()); }));
|
|
632
|
-
if (keyInput && checkFile(keyInput, '私钥')) {
|
|
633
|
-
setHttpsConfig(certInput, keyInput);
|
|
634
|
-
console.log(`[setup] HTTPS 已配置`);
|
|
635
|
-
console.log(` 证书: ${certInput}`);
|
|
636
|
-
console.log(` 私钥: ${keyInput}`);
|
|
637
|
-
}
|
|
638
|
-
else if (!keyInput) {
|
|
639
|
-
console.log('[setup] 私钥路径不能为空,跳过 HTTPS 配置');
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
else {
|
|
644
|
-
console.log(`[setup] 跳过 HTTPS,使用 HTTP 模式`);
|
|
645
|
-
console.log(`[setup] 如需启用 HTTPS,配置证书后重新运行 --setup --server`);
|
|
646
|
-
}
|
|
647
|
-
console.log('\n[setup] Server 配置完成!');
|
|
648
|
-
console.log(' 启动: npx @vrs-soft/wecom-aibot-mcp --start');
|
|
649
|
-
console.log('\n[setup] ─── 步骤 2/2:配置企业微信机器人 ───\n');
|
|
650
|
-
await addMcpConfig();
|
|
651
|
-
}
|
|
652
|
-
else if (wantChannel) {
|
|
653
|
-
// Channel 客户端
|
|
654
|
-
console.log('\n[setup] Channel Client 安装模式\n');
|
|
655
|
-
// 交互式安装必须每次都提示,不能直接用已有的环境变量(可能是旧值)
|
|
656
|
-
const readline = await import('readline');
|
|
657
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
658
|
-
const existingUrl = process.env.MCP_URL || '';
|
|
659
|
-
const urlPrompt = existingUrl
|
|
660
|
-
? `远程服务器地址(当前: ${existingUrl},直接回车保持不变): `
|
|
661
|
-
: `远程服务器地址(如 https://your-server:18963): `;
|
|
662
|
-
const urlInput = await new Promise(resolve => rl.question(urlPrompt, a => { rl.close(); resolve(a.trim()); }));
|
|
663
|
-
const mcpUrl = urlInput || existingUrl;
|
|
664
|
-
if (!mcpUrl) {
|
|
665
|
-
console.log('[setup] ❌ 地址不能为空');
|
|
666
|
-
process.exit(1);
|
|
667
|
-
}
|
|
668
|
-
process.env.MCP_URL = mcpUrl;
|
|
669
|
-
{
|
|
670
|
-
const existingToken = getAuthToken();
|
|
671
|
-
const tokenPrompt = existingToken
|
|
672
|
-
? `Auth Token(当前: ${existingToken.slice(0, 8)}...${existingToken.slice(-4)},直接回车保持不变): `
|
|
673
|
-
: 'Auth Token(留空跳过): ';
|
|
674
|
-
const readline2 = await import('readline');
|
|
675
|
-
const rl2 = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
676
|
-
const tokenInput = await new Promise(resolve => rl2.question(tokenPrompt, a => { rl2.close(); resolve(a.trim()); }));
|
|
677
|
-
const finalToken = tokenInput || existingToken || '';
|
|
678
|
-
if (finalToken)
|
|
679
|
-
setAuthToken(finalToken);
|
|
680
|
-
}
|
|
681
|
-
ensureGlobalConfigs('channel-only');
|
|
682
|
-
console.log('[setup] Channel MCP 配置完成!请重启 Claude Code 以加载配置');
|
|
683
|
-
}
|
|
684
|
-
else {
|
|
685
|
-
// 交互式:1/2 模式选择
|
|
686
|
-
console.log('\n请选择安装模式:\n');
|
|
687
|
-
console.log(' 1. 本地安装(完整功能:HTTP + Channel MCP)');
|
|
688
|
-
console.log(' 2. 远程服务器(连接远程 HTTP MCP)\n');
|
|
689
|
-
const readline = await import('readline');
|
|
690
|
-
const modeChoice = await new Promise((resolve) => {
|
|
691
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
692
|
-
rl.question('请选择 (1/2,默认 1): ', a => { rl.close(); resolve(a.trim() || '1'); });
|
|
693
|
-
});
|
|
694
|
-
if (modeChoice === '2') {
|
|
695
|
-
await runRemoteInstallWizard();
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
await runConfigWizard();
|
|
699
|
-
ensureGlobalConfigs('full');
|
|
700
|
-
startMcpServerBackground();
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
process.exit(0);
|
|
704
|
-
}
|
|
705
|
-
// --channel:启动 Channel MCP 代理(stdio)
|
|
706
|
-
// 注意:必须在 --debug 之前检查,否则 --channel --debug 会先触发 HTTP Server
|
|
707
|
-
// --setup --channel 已在上方处理,这里不拦截
|
|
708
|
-
if (args.includes('--channel') && !args.includes('--setup')) {
|
|
709
|
-
// 检查 HTTP MCP 的 debug 标记文件
|
|
710
|
-
const debugFile = path.join(os.homedir(), '.wecom-aibot-mcp', 'debug');
|
|
711
|
-
const isDebug = fs.existsSync(debugFile) || args.includes('--debug');
|
|
712
|
-
if (isDebug) {
|
|
713
|
-
console.log('[channel] Debug 模式:日志输出到 stderr(跟随 HTTP MCP debug)');
|
|
714
|
-
if (!fs.existsSync(debugFile)) {
|
|
715
|
-
fs.writeFileSync(debugFile, 'true');
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
// 配置统一日志输出到文件(每个 channel-server 进程都写同一份 channel.log,多进程并发追加)
|
|
719
|
-
logger.setLogFile(path.join(os.homedir(), '.wecom-aibot-mcp', 'channel.log'));
|
|
720
|
-
if (isDebug)
|
|
721
|
-
logger.setDebug(true);
|
|
722
|
-
console.log('[channel] Starting Channel MCP Proxy...');
|
|
723
|
-
const { startChannelServer } = await import('./channel-server.js');
|
|
64
|
+
if (args.includes('--channel')) {
|
|
65
|
+
// Channel MCP 代理模式(由 Claude Code 通过 stdio 启动)
|
|
66
|
+
logger.setLogFile(path.join(CONFIG_DIR, 'channel.log'));
|
|
724
67
|
await startChannelServer();
|
|
725
|
-
// Channel MCP 退出时不删除 debug 文件(由 HTTP MCP 管理)
|
|
726
|
-
return; // 保持运行,不 exit
|
|
727
|
-
}
|
|
728
|
-
// --debug:前台启动,日志直接输出到终端
|
|
729
|
-
if (args.includes('--debug')) {
|
|
730
|
-
console.log('[mcp] Debug 模式:前台运行,Ctrl+C 退出');
|
|
731
|
-
await startMcpServerForeground(true);
|
|
732
68
|
return;
|
|
733
69
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
console.log('[mcp] HTTP-only 模式:仅启动 HTTP Server');
|
|
737
|
-
console.log('[mcp] 不写入 MCP 配置(远程部署场景)');
|
|
738
|
-
console.log('[mcp] 使用 --start 启动服务');
|
|
70
|
+
if (args.includes('--install')) {
|
|
71
|
+
await runRemoteInstallWizard();
|
|
739
72
|
process.exit(0);
|
|
740
73
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const mcpUrl = process.env.MCP_URL;
|
|
744
|
-
if (!mcpUrl) {
|
|
745
|
-
console.log('[mcp] ❌ Channel-only 模式需要指定远程 HTTP MCP 地址');
|
|
746
|
-
console.log('[mcp] 请设置环境变量 MCP_URL:');
|
|
747
|
-
console.log('[mcp] MCP_URL=http://远程IP:18963 npx @vrs-soft/wecom-aibot-mcp --channel-only');
|
|
748
|
-
process.exit(1);
|
|
749
|
-
}
|
|
750
|
-
console.log(`[mcp] Channel-only 模式:Channel MCP 已配置`);
|
|
751
|
-
console.log(`[mcp] 连接地址: ${mcpUrl}`);
|
|
752
|
-
console.log('[mcp] 请确保远程 HTTP Server 已启动');
|
|
753
|
-
console.log('[mcp] 启动 Channel: npx @vrs-soft/wecom-aibot-mcp --channel');
|
|
74
|
+
if (args.includes('--uninstall')) {
|
|
75
|
+
uninstall();
|
|
754
76
|
process.exit(0);
|
|
755
77
|
}
|
|
756
|
-
//
|
|
757
|
-
if (
|
|
758
|
-
|
|
759
|
-
|
|
78
|
+
// 无参数:检查是否已有配置,否则引导安装
|
|
79
|
+
if (!process.stdin.isTTY) {
|
|
80
|
+
// 非交互式(stdio MCP 模式):不应出现这种情况,但安全起见给出提示
|
|
81
|
+
logger.error('[mcp] 请通过 --channel 参数启动 Channel MCP 代理');
|
|
82
|
+
process.exit(1);
|
|
760
83
|
}
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
logger.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
|
|
766
|
-
logger.log(' ║ Claude Code 审批通道 ║');
|
|
767
|
-
logger.log(' ╚════════════════════════════════════════════════════════╝');
|
|
768
|
-
logger.log('');
|
|
769
|
-
// 加载统计并清理旧日志(保留 1 小时)
|
|
770
|
-
loadStats();
|
|
771
|
-
cleanupOldLogs(1 / 24);
|
|
772
|
-
// 获取或初始化配置
|
|
773
|
-
let config;
|
|
774
|
-
let ranWizard = false; // 是否运行了配置向导
|
|
775
|
-
let instanceName = 'wecom-aibot';
|
|
776
|
-
if (reconfig) {
|
|
777
|
-
console.log('[config] 重新配置模式\n');
|
|
778
|
-
const result = await runConfigWizard();
|
|
779
|
-
config = result.config;
|
|
780
|
-
instanceName = result.instanceName;
|
|
781
|
-
ranWizard = true;
|
|
84
|
+
// TTY 模式:直接进入安装向导
|
|
85
|
+
const { mode } = getInstalledMode();
|
|
86
|
+
if (mode) {
|
|
87
|
+
console.log(`\n已配置(模式: ${mode})。重新配置请运行 --install,卸载请运行 --uninstall\n`);
|
|
782
88
|
}
|
|
783
89
|
else {
|
|
784
|
-
|
|
785
|
-
const savedConfig = loadConfig();
|
|
786
|
-
if (savedConfig && savedConfig.botId && savedConfig.secret && savedConfig.targetUserId) {
|
|
787
|
-
config = savedConfig;
|
|
788
|
-
}
|
|
789
|
-
else if (isInteractive) {
|
|
790
|
-
// TTY 模式下没有配置:提示使用 --setup,不再隐式弹向导
|
|
791
|
-
console.log('[config] 未找到机器人配置。');
|
|
792
|
-
console.log('[config] 请运行: npx @vrs-soft/wecom-aibot-mcp --setup');
|
|
793
|
-
process.exit(1);
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
// 非 TTY 模式(MCP HTTP),必须有配置
|
|
797
|
-
logger.error('[config] 未找到配置,且当前为非交互模式。');
|
|
798
|
-
logger.error('[config] 请在终端运行: npx @vrs-soft/wecom-aibot-mcp --config');
|
|
799
|
-
process.exit(1);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
// 确保 hook 已安装(幂等,每次启动检查)
|
|
803
|
-
ensureHookInstalled();
|
|
804
|
-
// 配置向导模式:验证连接并识别用户 ID
|
|
805
|
-
if (isInteractive && (ranWizard || reconfig)) {
|
|
806
|
-
console.log('[mcp] 验证机器人连接...');
|
|
807
|
-
// 临时建立连接验证凭证
|
|
808
|
-
const tempClient = initClient(config.botId, config.secret, config.targetUserId || 'placeholder', 'temp-validation');
|
|
809
|
-
const connected = await waitForConnection(tempClient, 10000);
|
|
810
|
-
if (!connected) {
|
|
811
|
-
console.log('[mcp] ❌ 连接失败,请检查:');
|
|
812
|
-
console.log(' 1. Bot ID 和 Secret 是否正确');
|
|
813
|
-
console.log(' 2. 新建机器人需等待约 2 分钟同步');
|
|
814
|
-
console.log(' 3. 是否已完成授权(机器人详情 → 可使用权限 → 授权)');
|
|
815
|
-
console.log('\n修复后重新运行: npx @vrs-soft/wecom-aibot-mcp --config');
|
|
816
|
-
tempClient.disconnect();
|
|
817
|
-
process.exit(1);
|
|
818
|
-
}
|
|
819
|
-
// 连接成功
|
|
820
|
-
logger.log('\n[mcp] ✅ 机器人凭证验证成功!');
|
|
821
|
-
// 保存配置(使用原用户 ID 或等待识别)
|
|
822
|
-
if (!config.targetUserId || config.targetUserId === 'placeholder' || config.targetUserId === '') {
|
|
823
|
-
// 新机器人,需要识别用户 ID
|
|
824
|
-
const userId = await detectUserIdFromMessage(tempClient, 180);
|
|
825
|
-
if (!userId) {
|
|
826
|
-
logger.log('\n[mcp] 未能在规定时间内识别用户 ID');
|
|
827
|
-
console.log('[mcp] 请重新运行配置:npx @vrs-soft/wecom-aibot-mcp --config');
|
|
828
|
-
tempClient.disconnect();
|
|
829
|
-
process.exit(1);
|
|
830
|
-
}
|
|
831
|
-
config.targetUserId = userId;
|
|
832
|
-
}
|
|
833
|
-
// 保存最终配置
|
|
834
|
-
saveConfig(config, instanceName);
|
|
835
|
-
logger.log('\n[mcp] ✅ 配置完成!');
|
|
836
|
-
logger.log(`[mcp] 用户 ID: ${config.targetUserId}`);
|
|
837
|
-
// 配置完成后断开连接
|
|
838
|
-
tempClient.disconnect();
|
|
839
|
-
// 首次安装后自动后台启动服务
|
|
840
|
-
logger.log('\n[mcp] 正在后台启动 MCP Server...');
|
|
841
|
-
startMcpServerBackground();
|
|
842
|
-
console.log('[mcp] 请重启 Claude Code 以加载 MCP 服务\n');
|
|
843
|
-
process.exit(0);
|
|
90
|
+
await runRemoteInstallWizard();
|
|
844
91
|
}
|
|
845
|
-
// 已有配置,显示状态并提示启动命令
|
|
846
|
-
showStatus();
|
|
847
|
-
logger.log('\n[mcp] 使用 --start 启动服务,--stop 停止服务');
|
|
848
|
-
console.log('[mcp] 命令: npx @vrs-soft/wecom-aibot-mcp --start\n');
|
|
849
92
|
}
|
|
850
93
|
main().catch((err) => {
|
|
851
|
-
logger.error('[mcp]
|
|
94
|
+
logger.error('[mcp] Fatal error:', err);
|
|
852
95
|
process.exit(1);
|
|
853
96
|
});
|