@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 +182 -32
- package/dist/config-wizard.js +49 -23
- package/dist/http-server.js +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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('
|
|
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);
|
|
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
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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);
|
package/dist/config-wizard.js
CHANGED
|
@@ -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
|
-
//
|
|
212
|
+
// 先删除所有文件,再删除目录(防止文件被重建)
|
|
201
213
|
const files = fs.readdirSync(CONFIG_DIR);
|
|
202
214
|
for (const file of files) {
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
209
|
-
|
|
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
|
|
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
|
|
544
|
+
catch {
|
|
521
545
|
// ignore
|
|
522
546
|
}
|
|
523
547
|
}
|
|
524
548
|
// 其他机器人配置
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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;
|
package/dist/http-server.js
CHANGED
|
@@ -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.
|
|
28
|
+
const VERSION = '1.0.9';
|
|
29
29
|
function createMcpServer() {
|
|
30
30
|
const server = new McpServer({
|
|
31
31
|
name: 'wecom-aibot-mcp',
|