@vrs-soft/wecom-aibot-mcp 1.0.3 → 1.0.5
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 +1 -1
- package/dist/bin.js +19 -4
- package/dist/config-wizard.d.ts +1 -1
- package/dist/config-wizard.js +99 -42
- package/package.json +1 -1
- package/skills/headless-mode/SKILL.md +17 -0
package/README.md
CHANGED
package/dist/bin.js
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* npx 运行入口
|
|
6
6
|
*/
|
|
7
7
|
import * as readline from 'readline';
|
|
8
|
-
import { runConfigWizard, loadConfig, deleteConfig, uninstall, ensureHookInstalled } from './config-wizard.js';
|
|
8
|
+
import { runConfigWizard, loadConfig, deleteConfig, uninstall, addMcpConfig, ensureHookInstalled } from './config-wizard.js';
|
|
9
9
|
import { initClient } from './client.js';
|
|
10
10
|
import { registerTools } from './tools/index.js';
|
|
11
11
|
import { startHttpServer } from './http-server.js';
|
|
12
12
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
-
const VERSION = '1.0.
|
|
14
|
+
const VERSION = '1.0.5';
|
|
15
15
|
// 等待连接验证(最多等待 10 秒)
|
|
16
16
|
async function waitForConnection(client, timeoutMs = 10000) {
|
|
17
17
|
return new Promise((resolve) => {
|
|
@@ -41,7 +41,8 @@ function showHelp() {
|
|
|
41
41
|
选项:
|
|
42
42
|
--help, -h 显示帮助信息
|
|
43
43
|
--version, -v 显示版本号
|
|
44
|
-
--config
|
|
44
|
+
--config 重新配置默认机器人(修改 Bot ID / Secret / 目标用户)
|
|
45
|
+
--add 添加新的机器人配置(多机器人场景)
|
|
45
46
|
--status 显示当前配置状态
|
|
46
47
|
--uninstall 卸载并删除所有配置(包括 MCP 配置、hook、skill)
|
|
47
48
|
|
|
@@ -133,6 +134,10 @@ async function main() {
|
|
|
133
134
|
uninstall();
|
|
134
135
|
process.exit(0);
|
|
135
136
|
}
|
|
137
|
+
if (args.includes('--add')) {
|
|
138
|
+
await addMcpConfig();
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
136
141
|
const reconfig = args.includes('--config');
|
|
137
142
|
const isInteractive = process.stdin.isTTY; // 是否为用户交互模式
|
|
138
143
|
console.log('');
|
|
@@ -233,10 +238,20 @@ async function main() {
|
|
|
233
238
|
const transport = new StdioServerTransport();
|
|
234
239
|
await server.connect(transport);
|
|
235
240
|
// 定期清理过期消息
|
|
236
|
-
setInterval(() => {
|
|
241
|
+
const cleanupInterval = setInterval(() => {
|
|
237
242
|
wecomClient.cleanupMessages();
|
|
238
243
|
}, 60000);
|
|
239
244
|
console.log('[mcp] MCP Server 已就绪');
|
|
245
|
+
// 退出处理:清理资源
|
|
246
|
+
const gracefulShutdown = () => {
|
|
247
|
+
console.log('[mcp] 正在关闭...');
|
|
248
|
+
clearInterval(cleanupInterval);
|
|
249
|
+
wecomClient.disconnect();
|
|
250
|
+
process.exit(0);
|
|
251
|
+
};
|
|
252
|
+
// 监听进程信号
|
|
253
|
+
process.on('SIGINT', gracefulShutdown);
|
|
254
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
240
255
|
}
|
|
241
256
|
main().catch((err) => {
|
|
242
257
|
console.error('[mcp] 启动失败:', err);
|
package/dist/config-wizard.d.ts
CHANGED
|
@@ -6,10 +6,10 @@ export interface WecomConfig {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function loadConfig(): WecomConfig | null;
|
|
8
8
|
export declare function deleteConfig(): void;
|
|
9
|
-
export declare function deleteMcpConfig(): void;
|
|
10
9
|
export declare function deleteHook(): void;
|
|
11
10
|
export declare function deleteSkills(): void;
|
|
12
11
|
export declare function uninstall(): void;
|
|
12
|
+
export declare function addMcpConfig(): Promise<void>;
|
|
13
13
|
export declare function ensureHookInstalled(): void;
|
|
14
14
|
export declare function saveConfig(config: WecomConfig): void;
|
|
15
15
|
/**
|
package/dist/config-wizard.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* 配置向导模块
|
|
3
3
|
*
|
|
4
4
|
* 首次运行时引导用户配置 Bot ID、Secret 和默认目标用户
|
|
5
|
+
* 配置直接存储在 ~/.claude.json 的 mcpServers.wecom-aibot.env 中
|
|
5
6
|
*/
|
|
6
7
|
import * as readline from 'readline';
|
|
7
8
|
import * as fs from 'fs';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import * as os from 'os';
|
|
10
11
|
const CONFIG_DIR = path.join(os.homedir(), '.wecom-aibot-mcp');
|
|
11
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
12
12
|
const CLAUDE_CONFIG_FILE = path.join(os.homedir(), '.claude.json');
|
|
13
13
|
const CLAUDE_SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.local.json');
|
|
14
14
|
const HOOK_SCRIPT_PATH = path.join(CONFIG_DIR, 'permission-hook.sh');
|
|
@@ -24,18 +24,30 @@ const MCP_TOOL_PERMISSIONS = [
|
|
|
24
24
|
'mcp__wecom-aibot__enter_headless_mode',
|
|
25
25
|
'mcp__wecom-aibot__exit_headless_mode',
|
|
26
26
|
];
|
|
27
|
-
//
|
|
27
|
+
// 确保配置目录存在(用于存储端口文件、hook脚本等运行时文件)
|
|
28
28
|
function ensureConfigDir() {
|
|
29
29
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
30
30
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
// 读取已保存的配置
|
|
33
|
+
// 从 ~/.claude.json 读取已保存的配置
|
|
34
34
|
export function loadConfig() {
|
|
35
35
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// 优先从 ~/.claude.json 读取
|
|
37
|
+
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
38
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
39
|
+
const claudeConfig = JSON.parse(content);
|
|
40
|
+
const mcpConfig = claudeConfig.mcpServers?.['wecom-aibot'];
|
|
41
|
+
if (mcpConfig?.env) {
|
|
42
|
+
const { WECOM_BOT_ID, WECOM_SECRET, WECOM_TARGET_USER } = mcpConfig.env;
|
|
43
|
+
if (WECOM_BOT_ID && WECOM_SECRET && WECOM_TARGET_USER) {
|
|
44
|
+
return {
|
|
45
|
+
botId: WECOM_BOT_ID,
|
|
46
|
+
secret: WECOM_SECRET,
|
|
47
|
+
targetUserId: WECOM_TARGET_USER,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
39
51
|
}
|
|
40
52
|
}
|
|
41
53
|
catch (err) {
|
|
@@ -43,36 +55,21 @@ export function loadConfig() {
|
|
|
43
55
|
}
|
|
44
56
|
return null;
|
|
45
57
|
}
|
|
46
|
-
//
|
|
58
|
+
// 删除配置(从 ~/.claude.json)
|
|
47
59
|
export function deleteConfig() {
|
|
48
|
-
try {
|
|
49
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
50
|
-
fs.unlinkSync(CONFIG_FILE);
|
|
51
|
-
console.log('[config] 已删除配置文件');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch (err) {
|
|
55
|
-
console.error('[config] 删除配置失败:', err);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// 删除 MCP Server 配置(从 ~/.claude.json)
|
|
59
|
-
export function deleteMcpConfig() {
|
|
60
60
|
try {
|
|
61
61
|
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
62
62
|
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
63
63
|
const claudeConfig = JSON.parse(content);
|
|
64
|
-
if (claudeConfig.mcpServers
|
|
64
|
+
if (claudeConfig.mcpServers?.['wecom-aibot']) {
|
|
65
65
|
delete claudeConfig.mcpServers['wecom-aibot'];
|
|
66
66
|
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(claudeConfig, null, 2));
|
|
67
|
-
console.log('[config] 已从 ~/.claude.json 删除 wecom-aibot
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
console.log('[config] ~/.claude.json 中未找到 wecom-aibot 配置');
|
|
67
|
+
console.log('[config] 已从 ~/.claude.json 删除 wecom-aibot 配置');
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
catch (err) {
|
|
75
|
-
console.error('[config]
|
|
72
|
+
console.error('[config] 删除配置失败:', err);
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
// 删除 PermissionRequest hook(从 ~/.claude/settings.local.json)
|
|
@@ -117,11 +114,10 @@ export function deleteSkills() {
|
|
|
117
114
|
// 完全卸载(删除所有相关配置)
|
|
118
115
|
export function uninstall() {
|
|
119
116
|
console.log('\n[config] 开始卸载 wecom-aibot-mcp...\n');
|
|
120
|
-
deleteConfig();
|
|
121
|
-
deleteMcpConfig();
|
|
117
|
+
deleteConfig(); // 删除 ~/.claude.json 中的配置
|
|
122
118
|
deleteHook();
|
|
123
119
|
deleteSkills();
|
|
124
|
-
//
|
|
120
|
+
// 删除运行时文件目录
|
|
125
121
|
if (fs.existsSync(CONFIG_DIR)) {
|
|
126
122
|
try {
|
|
127
123
|
// 删除所有 port-* 和 headless-* 文件
|
|
@@ -261,7 +257,7 @@ done
|
|
|
261
257
|
console.log(`[config] Hook 脚本已写入: ${HOOK_SCRIPT_PATH}`);
|
|
262
258
|
}
|
|
263
259
|
// 写入 MCP Server 配置到 ~/.claude.json
|
|
264
|
-
function writeMcpServerConfig(config) {
|
|
260
|
+
function writeMcpServerConfig(config, instanceName) {
|
|
265
261
|
try {
|
|
266
262
|
// 读取现有配置
|
|
267
263
|
let claudeConfig = {};
|
|
@@ -272,13 +268,13 @@ function writeMcpServerConfig(config) {
|
|
|
272
268
|
// 确保 mcpServers 存在
|
|
273
269
|
if (!claudeConfig.mcpServers)
|
|
274
270
|
claudeConfig.mcpServers = {};
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
const name = instanceName || 'wecom-aibot';
|
|
272
|
+
// 检查是否已存在同名配置
|
|
273
|
+
if (claudeConfig.mcpServers[name] && !instanceName) {
|
|
274
|
+
console.log(`[config] ~/.claude.json 中已存在 ${name} 配置,将更新`);
|
|
279
275
|
}
|
|
280
276
|
// 写入 MCP Server 配置
|
|
281
|
-
claudeConfig.mcpServers[
|
|
277
|
+
claudeConfig.mcpServers[name] = {
|
|
282
278
|
command: 'npx',
|
|
283
279
|
args: ['@vrs-soft/wecom-aibot-mcp'],
|
|
284
280
|
env: {
|
|
@@ -288,15 +284,16 @@ function writeMcpServerConfig(config) {
|
|
|
288
284
|
},
|
|
289
285
|
};
|
|
290
286
|
fs.writeFileSync(CLAUDE_CONFIG_FILE, JSON.stringify(claudeConfig, null, 2));
|
|
291
|
-
console.log(`[config] MCP Server 配置已写入 ~/.claude.json`);
|
|
287
|
+
console.log(`[config] MCP Server 配置已写入 ~/.claude.json (实例名: ${name})`);
|
|
292
288
|
return true;
|
|
293
289
|
}
|
|
294
290
|
catch (err) {
|
|
295
291
|
console.error('[config] 写入 ~/.claude.json 失败:', err);
|
|
296
292
|
console.log('[config] ⚠️ 请手动将以下配置添加到 ~/.claude.json:');
|
|
293
|
+
const name = instanceName || 'wecom-aibot';
|
|
297
294
|
console.log(JSON.stringify({
|
|
298
295
|
mcpServers: {
|
|
299
|
-
|
|
296
|
+
[name]: {
|
|
300
297
|
command: 'npx',
|
|
301
298
|
args: ['@vrs-soft/wecom-aibot-mcp'],
|
|
302
299
|
env: {
|
|
@@ -310,6 +307,68 @@ function writeMcpServerConfig(config) {
|
|
|
310
307
|
return false;
|
|
311
308
|
}
|
|
312
309
|
}
|
|
310
|
+
// 添加新的 MCP 配置(用于多 bot 场景)
|
|
311
|
+
export async function addMcpConfig() {
|
|
312
|
+
const rl = createRL();
|
|
313
|
+
try {
|
|
314
|
+
console.log('\n添加新的企业微信机器人配置\n');
|
|
315
|
+
// 获取实例名称
|
|
316
|
+
const instanceName = await question(rl, 'MCP 实例名称(如 wecom-aibot-zhangsan): ');
|
|
317
|
+
if (!instanceName) {
|
|
318
|
+
console.log('[config] 实例名称不能为空');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// 检查名称是否已存在
|
|
322
|
+
if (fs.existsSync(CLAUDE_CONFIG_FILE)) {
|
|
323
|
+
const content = fs.readFileSync(CLAUDE_CONFIG_FILE, 'utf-8');
|
|
324
|
+
const claudeConfig = JSON.parse(content);
|
|
325
|
+
if (claudeConfig.mcpServers?.[instanceName]) {
|
|
326
|
+
console.log(`[config] 实例名称 "${instanceName}" 已存在,请使用其他名称`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// 获取 Bot ID
|
|
331
|
+
let botId = await question(rl, 'Bot ID: ');
|
|
332
|
+
while (!botId) {
|
|
333
|
+
console.log('Bot ID 不能为空');
|
|
334
|
+
botId = await question(rl, 'Bot ID: ');
|
|
335
|
+
}
|
|
336
|
+
// 获取 Secret
|
|
337
|
+
let secret = await question(rl, 'Secret: ');
|
|
338
|
+
while (!secret) {
|
|
339
|
+
console.log('Secret 不能为空');
|
|
340
|
+
secret = await question(rl, 'Secret: ');
|
|
341
|
+
}
|
|
342
|
+
// 获取默认目标用户
|
|
343
|
+
console.log('\n请输入默认交互用户的 User ID(企业微信用户账号)\n');
|
|
344
|
+
let targetUserId = await question(rl, '默认目标用户 ID: ');
|
|
345
|
+
while (!targetUserId) {
|
|
346
|
+
console.log('用户 ID 不能为空');
|
|
347
|
+
targetUserId = await question(rl, '默认目标用户 ID: ');
|
|
348
|
+
}
|
|
349
|
+
const config = { botId, secret, targetUserId };
|
|
350
|
+
// 确认配置
|
|
351
|
+
console.log('\n─────────────────────────────────────');
|
|
352
|
+
console.log('配置确认:');
|
|
353
|
+
console.log(` 实例名称: ${instanceName}`);
|
|
354
|
+
console.log(` Bot ID: ${botId}`);
|
|
355
|
+
console.log(` Secret: ${secret.slice(0, 8)}...${secret.slice(-4)}`);
|
|
356
|
+
console.log(` 目标用户: ${targetUserId}`);
|
|
357
|
+
console.log('─────────────────────────────────────\n');
|
|
358
|
+
const confirm = await question(rl, '确认添加配置?(Y/n): ');
|
|
359
|
+
if (confirm.toLowerCase() === 'n') {
|
|
360
|
+
console.log('[config] 已取消');
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// 写入配置
|
|
364
|
+
writeMcpServerConfig(config, instanceName);
|
|
365
|
+
console.log(`\n[config] ✅ 已添加新机器人配置: ${instanceName}`);
|
|
366
|
+
console.log('[config] 请重启 Claude Code 以加载新的 MCP 服务\n');
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
rl.close();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
313
372
|
// 安装 skill 文件到 ~/.claude/skills/
|
|
314
373
|
function installSkills() {
|
|
315
374
|
try {
|
|
@@ -395,14 +454,12 @@ export function ensureHookInstalled() {
|
|
|
395
454
|
writeMcpPermissions();
|
|
396
455
|
installSkills();
|
|
397
456
|
}
|
|
398
|
-
//
|
|
457
|
+
// 保存配置(直接写入 ~/.claude.json)
|
|
399
458
|
export function saveConfig(config) {
|
|
400
|
-
ensureConfigDir();
|
|
401
|
-
|
|
402
|
-
console.log(`[config] 配置已保存到 ${CONFIG_FILE}`);
|
|
403
|
-
// 自动写入 MCP Server 配置到 ~/.claude.json
|
|
459
|
+
ensureConfigDir(); // 确保运行时文件目录存在
|
|
460
|
+
// 写入 MCP Server 配置到 ~/.claude.json
|
|
404
461
|
writeMcpServerConfig(config);
|
|
405
|
-
//
|
|
462
|
+
// 写入 MCP 工具权限和 Hook 到 ~/.claude/settings.local.json
|
|
406
463
|
writeMcpPermissions();
|
|
407
464
|
}
|
|
408
465
|
// 创建 readline 接口
|
package/package.json
CHANGED
|
@@ -7,6 +7,23 @@ description: 当用户说「现在开始通过微信联系」、「我要离开
|
|
|
7
7
|
|
|
8
8
|
**触发条件**:「现在开始通过微信联系」、「我要离开电脑前」、「切换到微信模式」
|
|
9
9
|
|
|
10
|
+
## 多机器人场景
|
|
11
|
+
|
|
12
|
+
如果用户配置了多个企业微信机器人(如 `wecom-aibot-zhangsan`、`wecom-aibot-lisi`),在进入微信模式前需要确认使用哪个:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
【需要确认】检测到您有多个企业微信机器人配置,请选择使用哪个:
|
|
16
|
+
1. wecom-aibot(默认)
|
|
17
|
+
2. wecom-aibot-zhangsan
|
|
18
|
+
3. wecom-aibot-lisi
|
|
19
|
+
|
|
20
|
+
请回复数字或名称选择。
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
选择后,后续的 `send_message`、`get_pending_messages` 等操作都使用对应的 MCP 实例。
|
|
24
|
+
|
|
25
|
+
> 如何添加多个机器人:运行 `npx @vrs-soft/wecom-aibot-mcp --add`
|
|
26
|
+
|
|
10
27
|
## 激活后立即执行(必须按顺序)
|
|
11
28
|
|
|
12
29
|
**Step 1**:进入 headless 模式(写入状态文件,让 hook 知道要发微信审批)
|