@vrs-soft/wecom-aibot-mcp 2.4.12 → 2.4.16

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 CHANGED
@@ -1,149 +1,201 @@
1
- # @vrs-soft/wecom-aibot-mcp
1
+ # wecom-aibot-mcp
2
2
 
3
- 中文 | [English](README_EN.md)
3
+ English | [中文文档](README_ZH.md)
4
4
 
5
- 企业微信智能机器人 MCP 服务 - Claude Code 通过微信远程审批和交互。
5
+ Enterprise WeChat AI Bot MCP Service - Remote Approval Channel for Claude Code
6
6
 
7
- **核心功能**:
8
- - 远程审批敏感操作(Bash/Write/Edit),微信卡片一键通过/拒绝
9
- - 离开电脑后通过微信下达任务,实时接收进度通知
10
- - 支持群聊 @机器人,多机器人、多用户并发
11
- - 代理企业微信文档 MCP,支持文档和智能表格操作
7
+ > Handle Claude Code approval requests via WeChat, even when away from your computer.
12
8
 
13
- ---
9
+ ## Features
14
10
 
15
- ## 效果预览
11
+ - 🔐 **Remote Approval**: Approve/deny sensitive operations (Bash/Write/Edit) via WeChat cards
12
+ - 🔍 **Full Command View**: Approval cards include a detail link — open in browser to view the complete command
13
+ - 💬 **Bidirectional Communication**: Real-time task progress notifications
14
+ - 📱 **Headless Mode**: Switch to WeChat interaction when leaving terminal
15
+ - 🤖 **Multi-bot Support**: Multiple bots for team and group chat scenarios
16
+ - 🌐 **Remote Deployment**: MCP server can be deployed on a remote host with Bearer Token auth
17
+ - 🔄 **Auto-reconnect**: Channel mode automatically reconnects after network interruption or server restart
16
18
 
17
- <img src="docs/approval-card.png" width="320" alt="微信审批卡片" />
19
+ ## Architecture
18
20
 
19
- 每次 Claude 执行敏感操作(Bash 命令、编辑文件等)时,企业微信会推送审批卡片,点击**允许**或**拒绝**即可实时控制执行权限。超时未响应时,根据配置自动代批(允许项目内操作,拒绝删除命令)。
21
+ ```
22
+ ┌─────────────────┐ MCP (stdio) ┌──────────────────────┐
23
+ │ Claude Code │ ──────────────────▶ │ Channel MCP Proxy │
24
+ │ (MCP Client) │ ◀────────────────── │ (local, SSE client) │
25
+ └─────────────────┘ └──────────────────────┘
26
+ │ SSE
27
+
28
+ ┌─────────────────────┐
29
+ │ wecom-aibot-mcp │
30
+ │ HTTP MCP Server │
31
+ │ (local or remote) │
32
+ └─────────────────────┘
33
+
34
+ WebSocket Connection
35
+
36
+ ┌─────────────────────┐
37
+ │ Enterprise WeChat │
38
+ └─────────────────────┘
39
+
40
+
41
+ ┌─────────────────────┐
42
+ │ User WeChat Client │
43
+ │ (Mobile/Desktop) │
44
+ └─────────────────────┘
45
+ ```
20
46
 
21
- ---
47
+ ## Installation
22
48
 
23
- ## 使用场景
49
+ ### Prerequisites
24
50
 
25
- ### 场景一:离开电脑,微信远程监督
51
+ - **Node.js >= 18**
52
+ - Enterprise WeChat account (with bot creation permission)
53
+ - Claude Code
26
54
 
27
- 出门开会或离开座位时,告诉 Claude「现在开始通过微信联系」,进入微信模式后:
55
+ ### Step 1: Create Enterprise WeChat Bot
28
56
 
29
- - Claude 执行每一步操作前发送审批请求到微信
30
- - 你在手机上点击**允许 / 拒绝**,Claude 实时响应
31
- - 设置超时自动审批(`autoApproveTimeout`),无人值守时自动处理项目内操作
57
+ 1. Login to WeChat Work admin portal: https://work.weixin.qq.com
58
+ 2. Go to "Management Tools" → "Smart Bot"
59
+ 3. Click "Create Bot" → "Manual Creation"
60
+ 4. Fill in bot name (e.g., "Claude Approval Assistant")
61
+ 5. In "API Configuration":
62
+ - Select "Use Long Connection"
63
+ - Click "Get Secret"
64
+ 6. Record **Bot ID** and **Secret**
32
65
 
33
- ### 场景二:微信直接下达任务
66
+ ### Step 2: Run Configuration Wizard
34
67
 
35
- 不在电脑前,直接在企业微信给机器人发消息:
68
+ ```bash
69
+ npx @vrs-soft/wecom-aibot-mcp --setup
70
+ ```
36
71
 
37
- - 「帮我跑一下单元测试,把结果发给我」
38
- - 「把 src/index.ts 里的 TODO 都处理掉」
39
- - 「最近有什么错误日志?」
72
+ Choose the appropriate role flag:
40
73
 
41
- Claude 执行完成后自动回复进度和结果到微信。
74
+ | Command | Role | Description |
75
+ |---------|------|-------------|
76
+ | `--setup` | Interactive | Guides through local or remote setup |
77
+ | `--setup --server` | Server-side | Configure bot + Token, no local MCP config |
78
+ | `--setup --channel` | Channel client | Connect to remote server, write Channel MCP config |
79
+ | `--setup --server --channel` | Full local | HTTP + Channel full install |
42
80
 
43
- ### 场景三:团队共享机器人,群聊协作
81
+ **Start server after setup**:
44
82
 
45
- 在企业微信群中 @机器人,多个成员可以同时:
83
+ ```bash
84
+ npx @vrs-soft/wecom-aibot-mcp --http-only --start
85
+ ```
46
86
 
47
- - 查询项目状态
48
- - 触发 CI 任务
49
- - 审批自己负责的操作(审批请求精确路由到对应 Claude 窗口)
87
+ ## Commands
50
88
 
51
- ---
89
+ | Command | Description |
90
+ |---------|-------------|
91
+ | `--start / --stop` | Start/stop background service |
92
+ | `--status` | View service status and bot list |
93
+ | `--config` | Modify default bot configuration |
94
+ | `--add / --delete` | Add/delete bot |
95
+ | `--set-token [token]` | Set Auth Token (for remote deployment) |
96
+ | `--set-token --clear` | Clear Auth Token |
97
+ | `--debug` | Start in foreground with debug output |
98
+ | `--http-only` | Start HTTP MCP Server only (server-side use) |
99
+ | `--channel-only` | Configure Channel MCP only (requires `MCP_URL`) |
100
+ | `--clean-cache` | Clear CC registry cache |
101
+ | `--upgrade` | Force upgrade global configs |
102
+ | `--uninstall` | Complete uninstall |
52
103
 
53
- ## 前置条件
104
+ ## Run Modes
54
105
 
55
- 企业微信管理后台创建智能机器人,连接方式选「使用长连接」,记录 **Bot ID** **Secret** 以及 **DocURL**(文档url)。
106
+ | | Channel Mode | HTTP Mode |
107
+ |-|-------------|-----------|
108
+ | Message delivery | SSE push (instant) | `/loop` heartbeat polling |
109
+ | Latency | Immediate | ≤1 minute |
110
+ | Claude account | claude.ai direct only | Any (including API relay) |
111
+ | Reconnect | Auto (including server restart) | Auto via heartbeat |
56
112
 
57
- ---
113
+ To enter WeChat mode, tell Claude: **"Now contact me via WeChat"** — this triggers the `headless-mode` skill automatically.
58
114
 
59
- ## 安装
115
+ **Claude startup command for Channel mode**:
60
116
 
61
117
  ```bash
62
- npx @vrs-soft/wecom-aibot-mcp --setup
118
+ claude --dangerously-load-development-channels server:wecom-aibot-channel
63
119
  ```
64
120
 
65
- 根据部署角色选择参数:
66
-
67
- | 命令 | 角色 | 说明 |
68
- |------|------|------|
69
- | `--setup` | 交互式 | 询问本地 / 远程,自动引导 |
70
- | `--setup --server` | 服务器端 | 配置机器人 + Token,不写本机 MCP 配置 |
71
- | `--setup --channel` | Channel 客户端 | 连接远程 Server,写入 Channel MCP |
72
- | `--setup --server --channel` | 本地完整 | HTTP + Channel 全安装 |
121
+ ## Usage Example
73
122
 
74
- **Server 端安装后启动**:
123
+ ### Headless Mode (Remote Approval)
75
124
 
76
- ```bash
77
- npx @vrs-soft/wecom-aibot-mcp --http-only --start
78
125
  ```
126
+ You: Now contact me via WeChat
79
127
 
80
- **后台启动 / 停止(本地或 Server 端)**:
81
-
82
- ```bash
83
- npx @vrs-soft/wecom-aibot-mcp --start # 后台启动
84
- npx @vrs-soft/wecom-aibot-mcp --stop # 停止
85
- ```
128
+ Claude: Entered WeChat mode. All interactions will go through Enterprise WeChat.
86
129
 
87
- ---
130
+ [You leave the computer. Claude needs to run a command.]
88
131
 
89
- ## 运行模式对比
132
+ WeChat receives approval card:
133
+ ┌──────────────────────────────┐
134
+ │ 【Pending Approval】Bash │
135
+ │ Command: npm run build... │
136
+ │ 📋 TaskID: approval_xxx │
137
+ │ [Allow Once] [Default] [Deny]│
138
+ │ Details: View full command │
139
+ └──────────────────────────────┘
90
140
 
91
- | | Channel 模式 | HTTP 模式 |
92
- |-|-------------|----------|
93
- | 消息接收 | SSE 自动推送唤醒 | `/loop` 心跳轮询 |
94
- | 响应延迟 | 即时 | ≤1 分钟 |
95
- | 账号要求 | claude.ai 直连 | 任意(含 API 中转)|
141
+ [Tap "Allow Once" on phone, or open "View full command" to see complete output]
96
142
 
97
- 使用微信模式时告诉 Claude「**现在开始通过微信联系**」,会自动触发 `headless-mode` skill。
143
+ Claude continues execution and sends the result to WeChat.
98
144
 
99
- **Channel 模式下 Claude 的启动命令**:
145
+ You: I'm back
100
146
 
101
- ```bash
102
- claude --dangerously-load-development-channels server:wecom-aibot-channel
147
+ Claude: Exited WeChat mode.
103
148
  ```
104
149
 
105
- ---
150
+ ### Timeout Auto-Approval
106
151
 
107
- ## 常用命令
152
+ Configure in the bot config file or via `wecom-aibot.json`:
108
153
 
109
- | 命令 | 说明 |
110
- |------|------|
111
- | `--start / --stop` | 启动/停止后台服务 |
112
- | `--status` | 查看服务状态和机器人列表 |
113
- | `--config` | 修改默认机器人配置 |
114
- | `--add / --delete` | 添加/删除机器人 |
115
- | `--set-token [token]` | 设置 Auth Token(远程部署用) |
116
- | `--set-token --clear` | 清除 Auth Token |
117
- | `--debug` | 前台启动,输出调试日志 |
118
- | `--http-only` | 仅启动 HTTP MCP Server(服务器端用) |
119
- | `--channel-only` | 仅配置 Channel MCP(需 `MCP_URL` 环境变量) |
120
- | `--clean-cache` | 清空 CC 注册表缓存 |
121
- | `--upgrade` | 强制升级全局配置 |
122
- | `--uninstall` | 完全卸载 |
123
-
124
- 超时自动审批(默认 10 分钟):在机器人配置中设置 `"autoApproveTimeout": 600`。
154
+ ```json
155
+ {
156
+ "autoApproveTimeout": 600
157
+ }
158
+ ```
125
159
 
126
- ---
160
+ - `autoApproveTimeout`: Timeout in seconds (default 600s = 10 minutes)
161
+ - After timeout: operations **within** the project directory are auto-allowed; operations outside or delete commands are auto-denied
127
162
 
128
- ## 故障排查
163
+ ## Troubleshooting
129
164
 
130
165
  ```bash
131
- # 检查服务是否运行
166
+ # Check if service is running
132
167
  curl http://127.0.0.1:18963/health
133
168
 
134
- # Channel 不可用("Channels are not currently available"
135
- # → 使用 API Key 或中转服务,改用 HTTP 模式
169
+ # Channel unavailable ("Channels are not currently available")
170
+ # → Using API key or relay service? Switch to HTTP mode instead.
171
+
172
+ # Channel fails to reconnect after server restart
173
+ # → Auto-reconnect triggers within 5 seconds; no manual action needed.
174
+ # Requires v2.4.13 or later.
136
175
 
137
- # 端口占用
176
+ # Approval detail page shows "Unauthorized"
177
+ # → Upgrade to v2.4.14 or later; the /approval/ path is now auth-exempt.
178
+
179
+ # Port conflict
138
180
  lsof -i :18963 | grep LISTEN
139
181
  kill <PID>
140
182
 
141
- # 清理断线残留的 ccId 注册
183
+ # Clean up stale ccId registrations after disconnect
142
184
  npx @vrs-soft/wecom-aibot-mcp --clean-cache
143
185
  ```
144
186
 
145
- ---
187
+ ## MCP Tools
188
+
189
+ | Tool | Description | Key Parameters |
190
+ |------|-------------|----------------|
191
+ | `send_message` | Send message to WeChat | `content`, `cc_id`, `target_user` |
192
+ | `get_pending_messages` | Get pending messages (long poll) | `cc_id`, `timeout_ms` |
193
+ | `enter_headless_mode` | Enter WeChat mode | `cc_id`, `robot_id`, `mode` |
194
+ | `exit_headless_mode` | Exit WeChat mode | `cc_id` |
195
+ | `check_connection` | Check WebSocket connection status | - |
196
+ | `list_robots` | List all configured bots | - |
197
+ | `get_connection_stats` | Get connection stats and logs | `recent_logs` |
146
198
 
147
199
  ## License
148
200
 
149
- MIT · [企业微信机器人文档](https://developer.work.weixin.qq.com/document/path/101039) · [Channels 文档](https://code.claude.com/docs/en/channels-reference)
201
+ MIT · [Enterprise WeChat Bot Docs](https://developer.work.weixin.qq.com/document/path/101039) · [Channels Reference](https://code.claude.com/docs/en/channels-reference)
@@ -205,6 +205,23 @@ function connectSSE(ccId) {
205
205
  if (!res.ok) {
206
206
  logChannel('SSE connect failed', { status: res.status });
207
207
  sseConnected = false;
208
+ // server 重启后 ccId 注册丢失(404),需重新注册再重连
209
+ if (!sseAbortController?.signal.aborted) {
210
+ const delay = res.status === 404 ? 5000 : 3000;
211
+ logChannel(`SSE 连接失败(${res.status}),${delay / 1000} 秒后重连`, { ccId });
212
+ setTimeout(async () => {
213
+ httpSessionId = null; // 重置 session,防止使用 server 重启前的旧 session
214
+ if (ccId) {
215
+ // 重新调 enter_headless_mode 恢复 server 端 ccId 注册
216
+ await forwardToHttpMcp('enter_headless_mode', {
217
+ cc_id: ccId,
218
+ mode: 'channel',
219
+ project_dir: process.cwd(),
220
+ }).catch((e) => logChannel('重注册 ccId 失败', { error: String(e) }));
221
+ }
222
+ connectSSE(ccId);
223
+ }, delay);
224
+ }
208
225
  return;
209
226
  }
210
227
  logChannel('SSE connected, waiting for messages', { status: res.status });
@@ -230,7 +247,7 @@ function connectSSE(ccId) {
230
247
  // 非主动断开时自动重连
231
248
  if (!sseAbortController?.signal.aborted) {
232
249
  logChannel('SSE 断线,3 秒后重连', { ccId });
233
- setTimeout(() => connectSSE(ccId), 3000);
250
+ setTimeout(() => { httpSessionId = null; connectSSE(ccId); }, 3000);
234
251
  }
235
252
  break;
236
253
  }
@@ -305,7 +322,7 @@ function connectSSE(ccId) {
305
322
  // 非主动断开时自动重连
306
323
  if (!sseAbortController?.signal.aborted) {
307
324
  logChannel('SSE 出错,3 秒后重连', { ccId });
308
- setTimeout(() => connectSSE(ccId), 3000);
325
+ setTimeout(() => { httpSessionId = null; connectSSE(ccId); }, 3000);
309
326
  }
310
327
  });
311
328
  }
package/dist/client.js CHANGED
@@ -252,8 +252,9 @@ class WecomClient extends EventEmitter {
252
252
  : eventKey === 'allow-always' ? '✅ 已允许(永久)'
253
253
  : '❌ 已拒绝';
254
254
  const toolInfo = approval.toolName ? `: ${approval.toolName}` : '';
255
- const descInfo = approval.description ? `\n\n> ${approval.description}` : '';
256
- const content = `**审批结果**${toolInfo}\n\n${resultText}${descInfo}`;
255
+ const desc = approval.description || '';
256
+ const descSnippet = desc ? `\n执行命令: ${desc.slice(0, 100)}${desc.length > 100 ? '…' : ''}` : '';
257
+ const content = `**审批结果**${toolInfo}\n\n${resultText}${descSnippet}`;
257
258
  this.sendText(content).catch(err => {
258
259
  logger.error('wecom', `发送审批确认失败: ${err}`);
259
260
  });
@@ -461,8 +462,9 @@ class WecomClient extends EventEmitter {
461
462
  const resultText = result === 'deny' ? '❌ 已拒绝' : '✅ 已允许';
462
463
  const reasonText = reason ? `\n\n原因:${reason}` : '';
463
464
  const toolInfo = approval.toolName ? `: ${approval.toolName}` : '';
464
- const descInfo = approval.description ? `\n\n> ${approval.description}` : '';
465
- this.sendText(`**审批结果(超时自动决策)**${toolInfo}\n\n${resultText}${reasonText}${descInfo}`).catch(err => {
465
+ const desc = approval.description || '';
466
+ const descBlock = desc ? `\n\n**执行命令**\n\`\`\`\n${desc}\n\`\`\`` : '';
467
+ this.sendText(`**审批结果(超时自动决策)**${toolInfo}\n\n${resultText}${reasonText}${descBlock}`).catch(err => {
466
468
  logger.error('[wecom] 发送审批确认失败:', err);
467
469
  });
468
470
  logger.log(`[wecom] 超时自动决策已设置: ${taskId} → ${result}`);
@@ -204,9 +204,9 @@ function initMcpServer() {
204
204
  subscribeWecomMessage((msg) => {
205
205
  handleWecomMessage(msg);
206
206
  });
207
- // 定时清理过期审批条目(每 5 分钟清理超过 15 分钟的条目)
207
+ // 定时清理过期审批条目(每 5 分钟清理超过 30 分钟的条目)
208
208
  setInterval(() => {
209
- const cutoff = Date.now() - 15 * 60 * 1000;
209
+ const cutoff = Date.now() - 30 * 60 * 1000;
210
210
  let cleaned = 0;
211
211
  for (const [id, entry] of pendingApprovals) {
212
212
  if (entry.createdAt < cutoff) {
@@ -506,9 +506,9 @@ export async function startHttpServer(_server, port = HTTP_PORT, httpsConfig) {
506
506
  return;
507
507
  }
508
508
  const url = req.url || '/';
509
- // Auth token 校验(排除 /health
509
+ // Auth token 校验(排除 /health 和 /approval/ 详情页,后者由浏览器直接访问)
510
510
  const authToken = getAuthToken();
511
- if (authToken && url !== '/health') {
511
+ if (authToken && url !== '/health' && !url.startsWith('/approval/')) {
512
512
  const authHeader = req.headers['authorization'];
513
513
  if (authHeader !== `Bearer ${authToken}`) {
514
514
  res.writeHead(401, { 'Content-Type': 'application/json' });
@@ -996,7 +996,7 @@ function handleApprovalDetail(_req, res, url) {
996
996
  </div>
997
997
  <h3>完整参数</h3>
998
998
  <pre>${escapeHtml(inputPretty)}</pre>
999
- <footer>此页面随审批记录自动过期清理 · 请回到企业微信卡片点击审批按钮</footer>
999
+ <footer>审批记录保留 30 分钟后自动清理 · 请回到企业微信卡片点击审批按钮</footer>
1000
1000
  </body>
1001
1001
  </html>`;
1002
1002
  respondHtml(200, html);
@@ -1056,7 +1056,11 @@ async function handleApprovalTimeout(req, res, url) {
1056
1056
  const success = client.setApprovalResult(taskId, result, reason);
1057
1057
  if (success) {
1058
1058
  entry.status = result;
1059
- pendingApprovals.delete(taskId); // 处理完立即删除
1059
+ // 保留 30 分钟供事后查看详情,不立即删除
1060
+ setTimeout(() => {
1061
+ pendingApprovals.delete(taskId);
1062
+ logger.log(`[http] 超时审批条目已清理: taskId=${taskId}`);
1063
+ }, 30 * 60 * 1000);
1060
1064
  res.writeHead(200, { 'Content-Type': 'application/json' });
1061
1065
  res.end(JSON.stringify({ success: true, taskId, result }));
1062
1066
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrs-soft/wecom-aibot-mcp",
3
- "version": "2.4.12",
3
+ "version": "2.4.16",
4
4
  "description": "企业微信智能机器人 MCP 服务 - Claude Code 审批通道",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",