@vrs-soft/wecom-aibot-mcp 1.0.8 → 1.0.9

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.js CHANGED
@@ -13,15 +13,20 @@
13
13
  * - enter_headless_mode 时按需建立连接
14
14
  * - exit_headless_mode 时断开连接
15
15
  */
16
+ import { spawn } from 'child_process';
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import * as os from 'os';
16
20
  import { runConfigWizard, loadConfig, saveConfig, deleteConfig, deleteMcpConfigInteractive, uninstall, addMcpConfig, detectUserIdFromMessage, ensureHookInstalled, listAllRobots, } from './config-wizard.js';
17
21
  import { initClient } from './client.js';
18
22
  import { registerTools } from './tools/index.js';
19
- import { startHttpServer, HTTP_PORT } from './http-server.js';
23
+ import { startHttpServer, stopHttpServer, HTTP_PORT } from './http-server.js';
20
24
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
21
25
  import { clearAllProjectHooks, getAllHeadlessStates } from './headless-state.js';
22
26
  import { loadStats, cleanupOldLogs } from './connection-log.js';
23
27
  import { startKeepaliveMonitor, stopKeepaliveMonitor } from './keepalive-monitor.js';
24
- const VERSION = '1.0.7';
28
+ const VERSION = '1.0.9';
29
+ const PID_FILE = path.join(os.homedir(), '.wecom-aibot-mcp', 'server.pid');
25
30
  function showHelp() {
26
31
  console.log(`
27
32
  企业微信智能机器人 MCP 服务 v${VERSION}
@@ -35,12 +40,27 @@ function showHelp() {
35
40
  选项:
36
41
  --help, -h 显示帮助信息
37
42
  --version, -v 显示版本号
43
+ --start 启动 MCP Server(后台服务模式)
44
+ --stop 停止 MCP Server
45
+ --status 显示服务状态和机器人配置
38
46
  --config 重新配置默认机器人(修改 Bot ID / Secret / 目标用户)
39
47
  --add 添加新的机器人配置(多机器人场景)
40
- --list 列出所有已配置的机器人
48
+ --list 列出所有已配置的机器人及其占用状态
41
49
  --delete [名称] 删除指定的机器人配置(无参数则显示列表选择)
42
50
  --uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
43
51
 
52
+ 使用流程:
53
+ 1. 首次安装: npx @vrs-soft/wecom-aibot-mcp
54
+ (进入配置向导,完成后自动后台启动服务)
55
+
56
+ 2. 已有配置: npx @vrs-soft/wecom-aibot-mcp
57
+ (显示状态,提示使用 --start 启动)
58
+
59
+ 3. 启动服务: npx @vrs-soft/wecom-aibot-mcp --start
60
+ (后台启动 MCP HTTP Server)
61
+
62
+ 4. 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop
63
+
44
64
  MCP 配置(HTTP Transport):
45
65
 
46
66
  编辑 ~/.claude.json:
@@ -49,7 +69,7 @@ MCP 配置(HTTP Transport):
49
69
  "mcpServers": {
50
70
  "wecom-aibot": {
51
71
  "type": "http",
52
- "url": "http://127.0.0.1:${HTTP_PORT}/mcp"
72
+ "url": "http://127.0.0.1:18963/mcp"
53
73
  }
54
74
  }
55
75
  }
@@ -73,8 +93,11 @@ function showVersion() {
73
93
  function showStatus() {
74
94
  const allRobots = listAllRobots();
75
95
  const headlessStates = getAllHeadlessStates();
96
+ // 检查服务是否运行
97
+ const serverRunning = isServerRunning();
98
+ console.log(`\n服务状态: ${serverRunning ? '✅ 运行中' : '❌ 未启动'}\n`);
76
99
  if (allRobots.length === 0) {
77
- console.log('尚未配置,请运行 npx @vrs-soft/wecom-aibot-mcp 启动配置向导');
100
+ console.log('尚未配置机器人,请运行 npx @vrs-soft/wecom-aibot-mcp 启动配置向导');
78
101
  return;
79
102
  }
80
103
  // 构建机器人占用信息
@@ -101,6 +124,56 @@ function showStatus() {
101
124
  console.log('');
102
125
  }
103
126
  }
127
+ // 检查服务是否运行
128
+ function isServerRunning() {
129
+ if (!fs.existsSync(PID_FILE)) {
130
+ return false;
131
+ }
132
+ try {
133
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
134
+ // 检查进程是否存在
135
+ process.kill(pid, 0);
136
+ return true;
137
+ }
138
+ catch {
139
+ // 进程不存在,清理 PID 文件
140
+ fs.unlinkSync(PID_FILE);
141
+ return false;
142
+ }
143
+ }
144
+ // 停止服务
145
+ function stopServer() {
146
+ if (!fs.existsSync(PID_FILE)) {
147
+ console.log('[mcp] 服务未运行');
148
+ return false;
149
+ }
150
+ try {
151
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
152
+ process.kill(pid, 'SIGTERM');
153
+ // 等待进程退出
154
+ let attempts = 0;
155
+ while (attempts < 10) {
156
+ try {
157
+ process.kill(pid, 0);
158
+ // 进程还存在,等待
159
+ setTimeout(() => { }, 500);
160
+ attempts++;
161
+ }
162
+ catch {
163
+ // 进程已退出
164
+ break;
165
+ }
166
+ }
167
+ fs.unlinkSync(PID_FILE);
168
+ console.log('[mcp] 服务已停止');
169
+ return true;
170
+ }
171
+ catch (err) {
172
+ console.error('[mcp] 停止服务失败:', err);
173
+ fs.unlinkSync(PID_FILE);
174
+ return false;
175
+ }
176
+ }
104
177
  // 等待连接验证(用于配置向导验证凭证)
105
178
  async function waitForConnection(client, timeoutMs = 10000) {
106
179
  return new Promise((resolve) => {
@@ -117,6 +190,81 @@ async function waitForConnection(client, timeoutMs = 10000) {
117
190
  }, 500);
118
191
  });
119
192
  }
193
+ // 启动 MCP Server(前台运行,供 --start 使用)
194
+ async function startMcpServerForeground() {
195
+ const savedConfig = loadConfig();
196
+ if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
197
+ console.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
198
+ process.exit(1);
199
+ }
200
+ // 写入 PID 文件
201
+ fs.writeFileSync(PID_FILE, String(process.pid));
202
+ // 确保 hook 已安装
203
+ ensureHookInstalled();
204
+ // 清理残留的 headless 状态
205
+ clearAllProjectHooks();
206
+ // 加载统计并清理旧日志
207
+ loadStats();
208
+ cleanupOldLogs(1 / 24);
209
+ // 创建 MCP Server
210
+ const server = new McpServer({
211
+ name: 'wecom-aibot-mcp',
212
+ version: VERSION,
213
+ });
214
+ registerTools(server);
215
+ // 启动 HTTP 服务
216
+ console.log('');
217
+ console.log(' ╔════════════════════════════════════════════════════════╗');
218
+ console.log(` ║ 企业微信智能机器人 MCP 服务 v${VERSION} ║`);
219
+ console.log(' ║ Claude Code 审批通道 ║');
220
+ console.log(' ╚════════════════════════════════════════════════════════╝');
221
+ console.log('');
222
+ console.log(`[mcp] 启动 MCP HTTP Server (端口: ${HTTP_PORT})...`);
223
+ await startHttpServer(server);
224
+ startKeepaliveMonitor();
225
+ console.log(`[mcp] MCP Server 已就绪`);
226
+ console.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
227
+ console.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
228
+ console.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
229
+ console.log(`[mcp] PID: ${process.pid}`);
230
+ // 退出处理
231
+ const gracefulShutdown = () => {
232
+ console.log('[mcp] 正在关闭...');
233
+ stopKeepaliveMonitor();
234
+ stopHttpServer();
235
+ if (fs.existsSync(PID_FILE)) {
236
+ fs.unlinkSync(PID_FILE);
237
+ }
238
+ process.exit(0);
239
+ };
240
+ process.on('SIGINT', gracefulShutdown);
241
+ process.on('SIGTERM', gracefulShutdown);
242
+ }
243
+ // 后台启动 MCP Server(使用 spawn)
244
+ function startMcpServerBackground() {
245
+ // 检查配置是否存在
246
+ const savedConfig = loadConfig();
247
+ if (!savedConfig || !savedConfig.botId || !savedConfig.secret || !savedConfig.targetUserId) {
248
+ console.error('[mcp] 未找到配置,请先运行: npx @vrs-soft/wecom-aibot-mcp');
249
+ process.exit(1);
250
+ }
251
+ // 检查是否已运行
252
+ if (isServerRunning()) {
253
+ console.log('[mcp] 服务已在运行中');
254
+ return;
255
+ }
256
+ const nodePath = process.execPath;
257
+ const scriptPath = process.argv[1];
258
+ const child = spawn(nodePath, [scriptPath, '--start', '--foreground'], {
259
+ detached: true,
260
+ stdio: 'ignore',
261
+ });
262
+ child.unref();
263
+ console.log('[mcp] MCP Server 已在后台启动');
264
+ console.log(`[mcp] HTTP endpoint: http://127.0.0.1:18963/mcp`);
265
+ console.log('[mcp] 健康检查: curl http://127.0.0.1:18963/health');
266
+ console.log('[mcp] 停止服务: npx @vrs-soft/wecom-aibot-mcp --stop');
267
+ }
120
268
  async function main() {
121
269
  const args = process.argv.slice(2);
122
270
  // 解析命令行参数
@@ -132,7 +280,17 @@ async function main() {
132
280
  showStatus();
133
281
  process.exit(0);
134
282
  }
283
+ // --stop 命令:停止服务
284
+ if (args.includes('--stop')) {
285
+ stopServer();
286
+ process.exit(0);
287
+ }
288
+ // --uninstall 命令:先停止服务再卸载
135
289
  if (args.includes('--uninstall')) {
290
+ if (isServerRunning()) {
291
+ console.log('[mcp] 正在停止服务...');
292
+ stopServer();
293
+ }
136
294
  uninstall();
137
295
  process.exit(0);
138
296
  }
@@ -147,6 +305,16 @@ async function main() {
147
305
  await deleteMcpConfigInteractive(instanceName);
148
306
  process.exit(0);
149
307
  }
308
+ // --start --foreground:前台启动(内部调用)
309
+ if (args.includes('--start') && args.includes('--foreground')) {
310
+ await startMcpServerForeground();
311
+ return; // 保持运行,不 exit
312
+ }
313
+ // --start:后台启动
314
+ if (args.includes('--start')) {
315
+ startMcpServerBackground();
316
+ process.exit(0);
317
+ }
150
318
  const reconfig = args.includes('--config');
151
319
  const isInteractive = process.stdin.isTTY; // 是否为用户交互模式
152
320
  console.log('');
@@ -157,7 +325,7 @@ async function main() {
157
325
  console.log('');
158
326
  // 加载统计并清理旧日志(保留 1 小时)
159
327
  loadStats();
160
- cleanupOldLogs(1 / 24); // 保留 1 小时
328
+ cleanupOldLogs(1 / 24);
161
329
  // 获取或初始化配置
162
330
  let config;
163
331
  let ranWizard = false; // 是否运行了配置向导
@@ -229,36 +397,18 @@ async function main() {
229
397
  saveConfig(config, instanceName);
230
398
  console.log('\n[mcp] ✅ 配置完成!');
231
399
  console.log(`[mcp] 用户 ID: ${userId}`);
232
- console.log('[mcp] 请重启 Claude Code 以加载 MCP 服务\n');
233
400
  // 配置完成后断开连接
234
401
  tempClient.disconnect();
402
+ // 首次安装后自动后台启动服务
403
+ console.log('\n[mcp] 正在后台启动 MCP Server...');
404
+ startMcpServerBackground();
405
+ console.log('[mcp] 请重启 Claude Code 以加载 MCP 服务\n');
235
406
  process.exit(0);
236
407
  }
237
- // 创建 MCP Server(不建立 WebSocket 连接)
238
- const server = new McpServer({
239
- name: 'wecom-aibot-mcp',
240
- version: VERSION,
241
- });
242
- // 注册工具(不传入 client,由 ConnectionManager 管理)
243
- registerTools(server);
244
- // 启动 HTTP 服务
245
- console.log(`[mcp] 启动 MCP HTTP Server (端口: ${HTTP_PORT})...`);
246
- await startHttpServer(server);
247
- // 启动保活监控
248
- startKeepaliveMonitor();
249
- console.log(`[mcp] MCP Server 已就绪`);
250
- console.log(`[mcp] HTTP endpoint: http://127.0.0.1:${HTTP_PORT}/mcp`);
251
- console.log(`[mcp] 健康检查: http://127.0.0.1:${HTTP_PORT}/health`);
252
- console.log(`[mcp] 微信模式:enter_headless_mode 时建立连接`);
253
- // 退出处理
254
- const gracefulShutdown = () => {
255
- console.log('[mcp] 正在关闭...');
256
- stopKeepaliveMonitor();
257
- process.exit(0);
258
- };
259
- // 监听进程信号
260
- process.on('SIGINT', gracefulShutdown);
261
- process.on('SIGTERM', gracefulShutdown);
408
+ // 已有配置,显示状态并提示启动命令
409
+ showStatus();
410
+ console.log('\n[mcp] 使用 --start 启动服务,--stop 停止服务');
411
+ console.log('[mcp] 命令: npx @vrs-soft/wecom-aibot-mcp --start\n');
262
412
  }
263
413
  main().catch((err) => {
264
414
  console.error('[mcp] 启动失败:', err);
@@ -194,29 +194,53 @@ export function uninstall() {
194
194
  deleteConfig(); // 删除 ~/.claude.json 中的配置
195
195
  deleteHook();
196
196
  deleteSkills();
197
- // 删除运行时文件目录
197
+ // 删除全局 headless 状态索引文件(可能在同一目录)
198
+ const headlessIndexFile = path.join(CONFIG_DIR, 'headless-index.json');
199
+ if (fs.existsSync(headlessIndexFile)) {
200
+ try {
201
+ fs.unlinkSync(headlessIndexFile);
202
+ console.log('[config] 已删除 headless 状态索引');
203
+ }
204
+ catch (err) {
205
+ console.error('[config] 删除 headless 状态索引失败:', err);
206
+ }
207
+ }
208
+ // 删除整个配置目录(包括 config.json、robot-*.json、hook 脚本、日志等)
209
+ // 使用 recursive: true 和 force: true 确保完全删除
198
210
  if (fs.existsSync(CONFIG_DIR)) {
199
211
  try {
200
- // 删除所有 port-* 和 headless-* 文件
212
+ // 先删除所有文件,再删除目录(防止文件被重建)
201
213
  const files = fs.readdirSync(CONFIG_DIR);
202
214
  for (const file of files) {
203
- if (file.startsWith('port-') || file.startsWith('headless-')) {
204
- fs.unlinkSync(path.join(CONFIG_DIR, file));
215
+ const filePath = path.join(CONFIG_DIR, file);
216
+ try {
217
+ if (fs.statSync(filePath).isDirectory()) {
218
+ fs.rmSync(filePath, { recursive: true, force: true });
219
+ }
220
+ else {
221
+ fs.unlinkSync(filePath);
222
+ }
223
+ }
224
+ catch {
225
+ // 忽略单个文件删除失败
205
226
  }
206
227
  }
207
- // 如果目录为空,删除目录
208
- const remainingFiles = fs.readdirSync(CONFIG_DIR);
209
- if (remainingFiles.length === 0) {
210
- fs.rmSync(CONFIG_DIR);
228
+ // 最后尝试删除目录本身
229
+ try {
230
+ fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
211
231
  console.log('[config] 已删除配置目录');
212
232
  }
233
+ catch {
234
+ // 目录可能被其他进程占用,下次启动时会清理
235
+ console.log('[config] 配置目录已清空(部分文件可能被占用)');
236
+ }
213
237
  }
214
238
  catch (err) {
215
239
  console.error('[config] 删除配置目录失败:', err);
216
240
  }
217
241
  }
218
242
  console.log('\n[config] 卸载完成');
219
- console.log('[config] 如需重新安装,请运行: npx @vrs-soft/wecom-aibot-mcp --config\n');
243
+ console.log('[config] 如需重新安装,请运行: npx @vrs-soft/wecom-aibot-mcp\n');
220
244
  }
221
245
  // 生成并写入 hook 脚本(HTTP Transport 版本)
222
246
  function writeHookScript() {
@@ -517,24 +541,26 @@ export function listAllRobots() {
517
541
  isDefault: true,
518
542
  });
519
543
  }
520
- catch (e) {
544
+ catch {
521
545
  // ignore
522
546
  }
523
547
  }
524
548
  // 其他机器人配置
525
- const files = fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'));
526
- for (const file of files) {
527
- try {
528
- const config = JSON.parse(fs.readFileSync(path.join(CONFIG_DIR, file), 'utf-8'));
529
- robots.push({
530
- name: config.nameTag || file,
531
- botId: config.botId,
532
- targetUserId: config.targetUserId,
533
- isDefault: false,
534
- });
535
- }
536
- catch (e) {
537
- // ignore
549
+ if (fs.existsSync(CONFIG_DIR)) {
550
+ const files = fs.readdirSync(CONFIG_DIR).filter(f => f.startsWith('robot-') && f.endsWith('.json'));
551
+ for (const file of files) {
552
+ try {
553
+ const config = JSON.parse(fs.readFileSync(path.join(CONFIG_DIR, file), 'utf-8'));
554
+ robots.push({
555
+ name: config.nameTag || file,
556
+ botId: config.botId,
557
+ targetUserId: config.targetUserId,
558
+ isDefault: false,
559
+ });
560
+ }
561
+ catch {
562
+ // ignore
563
+ }
538
564
  }
539
565
  }
540
566
  return robots;
@@ -25,7 +25,7 @@ let startTime = 0;
25
25
  // 使用 Map 存储多个待处理审批
26
26
  const pendingApprovals = new Map();
27
27
  const APPROVAL_TIMEOUT_MS = parseInt(process.env.APPROVAL_TIMEOUT_MS || '600000', 10); // 默认 10 分钟,可通过环境变量配置
28
- const VERSION = '1.0.7';
28
+ const VERSION = '1.0.9';
29
29
  function createMcpServer() {
30
30
  const server = new McpServer({
31
31
  name: 'wecom-aibot-mcp',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrs-soft/wecom-aibot-mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "企业微信智能机器人 MCP 服务 - Claude Code 审批通道",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",