chaimi-keep-mcp 3.5.0-beta.0 → 3.5.0-beta.10
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/CHANGELOG.md +58 -0
- package/README.md +29 -0
- package/SKILL.md +16 -5
- package/bin/restart-daemon.js +201 -0
- package/oauth.js +5 -1
- package/package.json +3 -2
- package/references/authentication.md +15 -11
- package/references/response-templates.md +14 -8
- package/references/troubleshooting.md +40 -2
- package/server.js +162 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.5.0-beta.10 (2026-05-06)
|
|
4
|
+
|
|
5
|
+
- **修复** 旧版本进程残留问题 - 解决 npm 更新后 Agent 仍调用旧版本 MCP Server 的问题
|
|
6
|
+
- 新增 `killOtherMcpProcesses()` 函数 - 启动时自动杀掉旧进程
|
|
7
|
+
- 在 `main()` 函数中添加授权状态检查 - 已授权时杀掉旧进程,正在授权中不杀
|
|
8
|
+
- 恢复 `process.exit(0)` - 授权成功后自动退出,下次调用使用新版本
|
|
9
|
+
- 支持 macOS/Linux/Windows 三个平台的进程检测和关闭
|
|
10
|
+
- 失败时静默处理,不影响启动
|
|
11
|
+
|
|
12
|
+
## v3.5.0-beta.8 (2026-05-05)
|
|
13
|
+
|
|
14
|
+
- **新增** 自动重启 mcporter daemon - 安装新版本后自动重启
|
|
15
|
+
- 新增 bin/restart-daemon.js 脚本
|
|
16
|
+
- 检测 mcporter 是否安装
|
|
17
|
+
- 检测 daemon 是否在运行
|
|
18
|
+
- 自动执行 mcporter stop + mcporter start
|
|
19
|
+
- 失败时静默处理,不阻止安装流程
|
|
20
|
+
- 支持 DEBUG=true 查看详细日志
|
|
21
|
+
- **优化** postinstall 脚本 - 先同步 Skill 后重启 daemon
|
|
22
|
+
- 执行顺序: bin/sync-skill.js → bin/restart-daemon.js
|
|
23
|
+
|
|
24
|
+
## v3.5.0-beta.7 (2026-05-05)
|
|
25
|
+
|
|
26
|
+
- **优化** 回复模板 - 调整信息布局优化
|
|
27
|
+
- 将「请确认以下信息是否正确」→「AI生成信息,请确认是否正确」
|
|
28
|
+
- 将分类(🏷️ 分类)移到分隔线上面的核心数据区域
|
|
29
|
+
- 收入记账模板同步更新保持一致性
|
|
30
|
+
- **优化** 字段验证脚本增强 - 新增 mcpRecord() 函数,支持 MCP 链路字段验证
|
|
31
|
+
- 新增 MCP 通用必填字段验证(agentType、apiProvider、mcpVersion、osType、osVersion)
|
|
32
|
+
- 三条链路(save_expense、save_income、save_receipt)验证增强
|
|
33
|
+
- 更新 docs/04-数据/数据上报逻辑校验机制/链路3-MCP Server.md
|
|
34
|
+
|
|
35
|
+
## v3.5.0-beta.6 (2026-05-02)
|
|
36
|
+
|
|
37
|
+
- **新增** Agent名称编辑功能 - 小程序端可以编辑已授权Agent的名称
|
|
38
|
+
- 在Agent授权页面添加「编辑」按钮
|
|
39
|
+
- 复用现有取名弹窗,支持编辑模式
|
|
40
|
+
- 调用mcpOAuth云函数的updateAgentName工具更新名称
|
|
41
|
+
- 编辑完成后刷新设备列表
|
|
42
|
+
- **优化** Agent名称同步机制 - MCP Server每次调用getAgentName时从云端获取最新名称
|
|
43
|
+
- 修改getAgentName函数,每次调用时检查云端名称
|
|
44
|
+
- 如果云端名称与本地缓存不同,自动更新本地缓存
|
|
45
|
+
- 确保用户修改的名称能及时同步到MCP Server
|
|
46
|
+
|
|
47
|
+
## v3.5.0-beta.5 (2026-05-02)
|
|
48
|
+
|
|
49
|
+
- **修复** 重新授权流程 - 修复 Token 过期/取消授权后无法弹出新验证码的问题
|
|
50
|
+
- Bug 1: `getToken()` 函数中添加 `await startAuthFlow()`
|
|
51
|
+
- Bug 2: `callMcpHubWithLogging()` 中检测业务 401 并触发重新授权
|
|
52
|
+
- 新增: `clearCachedToken()` 工具函数
|
|
53
|
+
- **优化** 文档 - 在 SKILL.md, authentication.md, troubleshooting.md 中添加授权流程关键说明
|
|
54
|
+
|
|
55
|
+
## v3.5.0-beta.4 (2026-05-02)
|
|
56
|
+
- **优化** 回复模板 - 调整确认提示对齐方式
|
|
57
|
+
- SKILL.md: `🔴🔴` → `🔴`,添加空格对齐
|
|
58
|
+
- response-templates.md: `🔴🔴` → `🔴`,添加空格对齐
|
|
59
|
+
- "请"字与下方的"¥"符号左对齐,更美观
|
|
60
|
+
|
|
3
61
|
## v3.4.0 (2026-05-01) 🎉 正式版
|
|
4
62
|
|
|
5
63
|
**首个正式生产版本 - 多端记账分类统一**
|
package/README.md
CHANGED
|
@@ -89,6 +89,35 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
|
|
|
89
89
|
|
|
90
90
|
## Changelog
|
|
91
91
|
|
|
92
|
+
### v3.5.0-beta.8 (2026-05-05)
|
|
93
|
+
- **新增** 自动重启 mcporter daemon - 安装新版本后自动重启
|
|
94
|
+
- 新增 bin/restart-daemon.js 脚本
|
|
95
|
+
- 检测 mcporter 是否安装
|
|
96
|
+
- 检测 daemon 是否在运行
|
|
97
|
+
- 自动执行 mcporter stop + mcporter start
|
|
98
|
+
- 失败时静默处理,不阻止安装流程
|
|
99
|
+
- 支持 DEBUG=true 查看详细日志
|
|
100
|
+
- **优化** postinstall 脚本 - 先同步 Skill 后重启 daemon
|
|
101
|
+
- 执行顺序: bin/sync-skill.js → bin/restart-daemon.js
|
|
102
|
+
|
|
103
|
+
### v3.5.0-beta.7 (2026-05-05)
|
|
104
|
+
- **优化** 回复模板 - 调整信息布局优化
|
|
105
|
+
- 将「请确认以下信息是否正确」→「AI生成信息,请确认是否正确」
|
|
106
|
+
- 将分类(🏷️ 分类)移到分隔线上面的核心数据区域
|
|
107
|
+
- 收入记账模板同步更新保持一致性
|
|
108
|
+
- **优化** 字段验证脚本增强 - 新增 mcpRecord() 函数,支持 MCP 链路验证链路字段验证
|
|
109
|
+
|
|
110
|
+
### v3.5.0-beta.6 (2026-05-02)
|
|
111
|
+
- **新增** Agent名称编辑功能 - 在小程序中可以编辑已授权Agent的名称,MCP Server自动同步更新
|
|
112
|
+
- **优化** Agent名称同步机制 - getAgentName函数每次调用时从云端获取最新名称并同步到本地缓存
|
|
113
|
+
|
|
114
|
+
### v3.5.0-beta.5 (2026-05-02)
|
|
115
|
+
- **修复** 重新授权流程 - 修复 Token 过期/取消授权后无法弹出新验证码的问题
|
|
116
|
+
- **优化** 文档 - 在 SKILL.md, authentication.md, troubleshooting.md 中添加授权流程关键说明
|
|
117
|
+
|
|
118
|
+
### v3.5.0-beta.4 (2026-05-02)
|
|
119
|
+
- **优化** 回复模板 - 调整确认提示对齐方式
|
|
120
|
+
|
|
92
121
|
### v3.4.0 (2026-05-01) 🎉 正式版
|
|
93
122
|
- **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
|
|
94
123
|
- **修复** mcp-server-local case 重复问题
|
package/SKILL.md
CHANGED
|
@@ -211,10 +211,10 @@ references/
|
|
|
211
211
|
【第2层:分隔强调】═══════════════
|
|
212
212
|
【第3层:核心数据】💰 金额:¥xx.xx
|
|
213
213
|
📊 类型:支出/收入
|
|
214
|
+
🏷️ 分类:xxx
|
|
214
215
|
═══════════════
|
|
215
216
|
【第4层:详细信息】
|
|
216
217
|
📦 商品:xxx
|
|
217
|
-
🏷️ 分类:xxx
|
|
218
218
|
🏪 商家:xxx
|
|
219
219
|
🕐 时间:xxxx-xx-xx xx:xx
|
|
220
220
|
💡 消费洞察:xxx(可选)
|
|
@@ -233,9 +233,9 @@ references/
|
|
|
233
233
|
> **模板要点:**
|
|
234
234
|
> - 第1层:成功标识 `✅ 「{agentName}」已帮您记账成功`
|
|
235
235
|
> - 第2层:分隔线 `═══════════════`
|
|
236
|
-
> - 第3
|
|
236
|
+
> - 第3层:核心数据(🔴确认提示、金额、类型、分类、时间)
|
|
237
237
|
> - 第4层:分隔线
|
|
238
|
-
> - 第5
|
|
238
|
+
> - 第5层:详细信息(商品、商家)
|
|
239
239
|
> - 第6层:消费洞察(可选)
|
|
240
240
|
> - 第7层:底部祝福 + 品牌信息
|
|
241
241
|
|
|
@@ -243,15 +243,15 @@ references/
|
|
|
243
243
|
```markdown
|
|
244
244
|
✅ 「你的小可爱」已帮您记账成功
|
|
245
245
|
═══════════════
|
|
246
|
-
|
|
246
|
+
🔴 AI生成信息,请确认是否正确
|
|
247
247
|
|
|
248
248
|
💰 金额:¥35.00
|
|
249
249
|
📊 类型:支出
|
|
250
|
+
🏷️ 分类:餐饮
|
|
250
251
|
🕐 时间:2026-04-24 12:30
|
|
251
252
|
═══════════════
|
|
252
253
|
|
|
253
254
|
📦 商品:午餐
|
|
254
|
-
🏷️ 分类:餐饮
|
|
255
255
|
🏪 商家:麦当劳
|
|
256
256
|
|
|
257
257
|
💡 消费洞察:本月餐饮支出占比30%,建议控制
|
|
@@ -296,6 +296,17 @@ references/
|
|
|
296
296
|
|
|
297
297
|
## 九、错误速查
|
|
298
298
|
|
|
299
|
+
### ⚠️ 授权流程关键说明(必读!)
|
|
300
|
+
|
|
301
|
+
**重要提示:验证码只能在 Agent/MCP 端(终端)生成!小程序里无法生成验证码,只能输入验证码!**
|
|
302
|
+
|
|
303
|
+
| 位置 | 作用 |
|
|
304
|
+
|:-----|:------|
|
|
305
|
+
| Agent/MCP 端(终端) | 生成验证码 |
|
|
306
|
+
| 小程序 | 只负责输入验证码 |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
299
310
|
### Top 3 常见错误
|
|
300
311
|
|
|
301
312
|
| 错误 | 现象 | 解决方案 | 参考文档 |
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 自动重启 mcporter daemon 脚本
|
|
4
|
+
*
|
|
5
|
+
* 功能:安装新版本时自动重启 mcporter daemon,确保加载最新版本
|
|
6
|
+
* 使用场景:npm install 或 npm update 后自动调用
|
|
7
|
+
* 安全策略:只尝试重启,不强制退出,失败时静默处理
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { spawn, exec } = require('child_process');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const IS_DEBUG = process.env.DEBUG === 'true';
|
|
16
|
+
const LOG_PREFIX = '[mcporter-restart]';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 日志函数
|
|
20
|
+
*/
|
|
21
|
+
function log(message, level = 'info') {
|
|
22
|
+
const timestamp = new Date().toISOString();
|
|
23
|
+
if (level === 'error') {
|
|
24
|
+
console.error(`${LOG_PREFIX} ${timestamp} ❌ ${message}`);
|
|
25
|
+
} else if (level === 'warn') {
|
|
26
|
+
console.warn(`${LOG_PREFIX} ${timestamp} ⚠️ ${message}`);
|
|
27
|
+
} else if (IS_DEBUG) {
|
|
28
|
+
console.log(`${LOG_PREFIX} ${timestamp} ${message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 检查 mcporter 是否已安装
|
|
34
|
+
*/
|
|
35
|
+
function checkMcporterInstalled() {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
exec('which mcporter', (error, stdout) => {
|
|
38
|
+
if (error) {
|
|
39
|
+
resolve(false);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
resolve(stdout.trim().length > 0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 检查 mcporter daemon 是否在运行
|
|
49
|
+
*/
|
|
50
|
+
function checkDaemonRunning() {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
let cmd;
|
|
53
|
+
if (os.platform() === 'win32') {
|
|
54
|
+
cmd = 'tasklist /FI "IMAGENAME eq node.exe" /V';
|
|
55
|
+
} else {
|
|
56
|
+
cmd = 'ps aux | grep -E "mcporter.*daemon|daemon.*mcporter" | grep -v grep';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exec(cmd, (error, stdout) => {
|
|
60
|
+
if (error) {
|
|
61
|
+
// 没找到进程
|
|
62
|
+
resolve(false);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
resolve(stdout.trim().length > 0);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 尝试停止 mcporter daemon
|
|
72
|
+
*/
|
|
73
|
+
function stopDaemon() {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
log('尝试停止 mcporter daemon...', 'info');
|
|
76
|
+
exec('mcporter stop', (error, stdout, stderr) => {
|
|
77
|
+
if (error) {
|
|
78
|
+
log(`停止失败: ${error.message}`, 'warn');
|
|
79
|
+
if (IS_DEBUG && stderr) {
|
|
80
|
+
log(`stderr: ${stderr}`, 'warn');
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
log(`mcporter stop 命令执行成功`, 'info');
|
|
84
|
+
if (IS_DEBUG && stdout) {
|
|
85
|
+
log(`stdout: ${stdout}`, 'info');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 尝试启动 mcporter daemon
|
|
95
|
+
*/
|
|
96
|
+
function startDaemon() {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
log('尝试启动 mcporter daemon...', 'info');
|
|
99
|
+
exec('mcporter start', (error, stdout, stderr) => {
|
|
100
|
+
if (error) {
|
|
101
|
+
log(`启动失败: ${error.message}`, 'warn');
|
|
102
|
+
if (IS_DEBUG && stderr) {
|
|
103
|
+
log(`stderr: ${stderr}`, 'warn');
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
log(`mcporter start 命令执行成功`, 'info');
|
|
107
|
+
if (IS_DEBUG && stdout) {
|
|
108
|
+
log(`stdout: ${stdout}`, 'info');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 等待一段时间
|
|
118
|
+
*/
|
|
119
|
+
function wait(ms) {
|
|
120
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 获取当前版本
|
|
125
|
+
*/
|
|
126
|
+
function getCurrentVersion() {
|
|
127
|
+
try {
|
|
128
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
129
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
130
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
131
|
+
return pkg.version;
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// 忽略
|
|
135
|
+
}
|
|
136
|
+
return 'unknown';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 主函数
|
|
141
|
+
*/
|
|
142
|
+
async function main() {
|
|
143
|
+
console.log('\n🔄 检查 mcporter daemon...\n');
|
|
144
|
+
|
|
145
|
+
const version = getCurrentVersion();
|
|
146
|
+
log(`当前版本: ${version}`, 'info');
|
|
147
|
+
|
|
148
|
+
// 检查 mcporter 是否安装
|
|
149
|
+
const isInstalled = await checkMcporterInstalled();
|
|
150
|
+
if (!isInstalled) {
|
|
151
|
+
log('mcporter 未安装,跳过重启', 'info');
|
|
152
|
+
console.log('✅ mcporter 未安装,无需重启\n');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
log('mcporter 已安装', 'info');
|
|
157
|
+
|
|
158
|
+
// 检查 daemon 是否在运行
|
|
159
|
+
const isRunning = await checkDaemonRunning();
|
|
160
|
+
if (!isRunning) {
|
|
161
|
+
log('mcporter daemon 未在运行,无需重启', 'info');
|
|
162
|
+
console.log('✅ mcporter daemon 未在运行\n');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
log('mcporter daemon 在运行中,准备重启...', 'info');
|
|
167
|
+
|
|
168
|
+
// 停止 daemon
|
|
169
|
+
await stopDaemon();
|
|
170
|
+
|
|
171
|
+
// 等待一小会儿
|
|
172
|
+
await wait(500);
|
|
173
|
+
|
|
174
|
+
// 启动 daemon
|
|
175
|
+
await startDaemon();
|
|
176
|
+
|
|
177
|
+
// 等待并检查是否成功启动
|
|
178
|
+
await wait(1000);
|
|
179
|
+
|
|
180
|
+
const isRunningAfter = await checkDaemonRunning();
|
|
181
|
+
if (isRunningAfter) {
|
|
182
|
+
console.log('✅ mcporter daemon 重启成功!\n');
|
|
183
|
+
} else {
|
|
184
|
+
console.log('⚠️ mcporter daemon 可能需要手动启动\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 给用户提示
|
|
188
|
+
console.log('💡 如果新版本功能未生效,请尝试:');
|
|
189
|
+
console.log(' 1. 重启您的 AI Agent(OpenClaw/WorkBuddy 等)');
|
|
190
|
+
console.log(' 2. 或运行: mcporter restart\n');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 执行
|
|
194
|
+
main().catch(error => {
|
|
195
|
+
log(`发生未预期错误: ${error.message}`, 'error');
|
|
196
|
+
if (IS_DEBUG) {
|
|
197
|
+
console.error(error.stack);
|
|
198
|
+
}
|
|
199
|
+
// 不阻止安装流程继续
|
|
200
|
+
process.exit(0);
|
|
201
|
+
});
|
package/oauth.js
CHANGED
|
@@ -505,7 +505,11 @@ class FileTokenStorage extends TokenStorage {
|
|
|
505
505
|
agentName: agentName || '柴米AI助手',
|
|
506
506
|
updatedAt: new Date().toISOString()
|
|
507
507
|
};
|
|
508
|
-
|
|
508
|
+
// 使用 JSON.stringify 并转义处理,确保中文正常显示(不转义为 \uXXXX)
|
|
509
|
+
const jsonStr = JSON.stringify(data, null, 2)
|
|
510
|
+
.replace(/\\u[\dA-F]{4}/gi, (match) =>
|
|
511
|
+
String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)));
|
|
512
|
+
await this.fs.writeFile(agentNamePath, jsonStr, 'utf8');
|
|
509
513
|
console.error('✅ Agent 名称已保存:', agentNamePath);
|
|
510
514
|
} catch (err) {
|
|
511
515
|
console.error('❌ 保存 Agent 名称失败:', err.message);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaimi-keep-mcp",
|
|
3
|
-
"version": "3.5.0-beta.
|
|
3
|
+
"version": "3.5.0-beta.10",
|
|
4
4
|
"description": "柴米AI记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"start": "node server.js",
|
|
25
25
|
"dev": "node server.js",
|
|
26
|
-
"postinstall": "node bin/sync-skill.js",
|
|
26
|
+
"postinstall": "node bin/sync-skill.js && node bin/restart-daemon.js",
|
|
27
|
+
"postversion": "node -e \"console.log(require('./package.json').version)\" > VERSION && git add VERSION",
|
|
27
28
|
"test": "jest",
|
|
28
29
|
"test:static": "jest test/static-analysis.test.js",
|
|
29
30
|
"prepublishOnly": "npm run test:static"
|
|
@@ -25,22 +25,26 @@ updated: "2026-04-30"
|
|
|
25
25
|
```
|
|
26
26
|
用户(小程序端) Agent(MCP端)
|
|
27
27
|
│ │
|
|
28
|
-
│ 1.
|
|
28
|
+
│ 1. 请求记账 │
|
|
29
29
|
│────────────────────────>│
|
|
30
30
|
│ │
|
|
31
|
-
│ 2.
|
|
31
|
+
│ 2. 检测未授权 │
|
|
32
|
+
│ │
|
|
33
|
+
│ 3. Agent 端(终端)生成验证码 │
|
|
32
34
|
│<────────────────────────│
|
|
33
35
|
│ │
|
|
34
|
-
│
|
|
36
|
+
│ 4. 用户在小程序输入验证码 │
|
|
35
37
|
│────────────────────────>│
|
|
36
38
|
│ │
|
|
37
|
-
│
|
|
39
|
+
│ 5. 小程序验证并发放Token │
|
|
38
40
|
│<────────────────────────│
|
|
39
41
|
│ │
|
|
40
|
-
│
|
|
42
|
+
│ 6. Token记账 │
|
|
41
43
|
│────────────────────────>│
|
|
42
44
|
```
|
|
43
45
|
|
|
46
|
+
> ⚠️ **重要提示:验证码只能在 Agent/MCP 端(终端)生成!小程序里无法生成验证码,只能输入验证码!**
|
|
47
|
+
|
|
44
48
|
### 1.2 Token有效期
|
|
45
49
|
|
|
46
50
|
| Token类型 | 有效期 | 说明 |
|
|
@@ -65,7 +69,7 @@ updated: "2026-04-30"
|
|
|
65
69
|
chaimi-keep-mcp
|
|
66
70
|
```
|
|
67
71
|
|
|
68
|
-
2.
|
|
72
|
+
2. **查看验证码(注意:验证码只在 Agent/终端 显示)**
|
|
69
73
|
终端会显示:
|
|
70
74
|
```
|
|
71
75
|
🔐 柴米AI记账授权
|
|
@@ -74,10 +78,10 @@ updated: "2026-04-30"
|
|
|
74
78
|
⏰ 有效期:5分钟
|
|
75
79
|
```
|
|
76
80
|
|
|
77
|
-
3.
|
|
81
|
+
3. **小程序确认(注意:小程序只负责输入验证码,不会生成验证码)**
|
|
78
82
|
- 打开"柴米AI记账"小程序
|
|
79
|
-
- 点击"我的" → "
|
|
80
|
-
-
|
|
83
|
+
- 点击"我的" → "Agent 授权"
|
|
84
|
+
- 输入刚才在 Agent/终端 看到的验证码
|
|
81
85
|
- 点击确认
|
|
82
86
|
|
|
83
87
|
4. **完成授权**
|
|
@@ -99,7 +103,7 @@ mcporter auth 柴米AI记账 --reset
|
|
|
99
103
|
**然后:**
|
|
100
104
|
1. 查看终端显示的验证码
|
|
101
105
|
2. 打开"柴米AI记账"小程序
|
|
102
|
-
3. 进入"我的" → "
|
|
106
|
+
3. 进入"我的" → "Agent 授权"
|
|
103
107
|
4. 输入验证码完成授权
|
|
104
108
|
|
|
105
109
|
### 2.3 原生 MCP 客户端授权失败(Claude/Cursor)
|
|
@@ -127,7 +131,7 @@ mcporter auth 柴米AI记账 --reset
|
|
|
127
131
|
mcporter auth 柴米AI记账
|
|
128
132
|
```
|
|
129
133
|
2. 查看终端显示的验证码
|
|
130
|
-
3. 在小程序"我的"→"
|
|
134
|
+
3. 在小程序"我的"→"Agent 授权"中输入验证码
|
|
131
135
|
4. 返回 Claude/Cursor 重新尝试记账
|
|
132
136
|
|
|
133
137
|
**解决方案(方式2 - 删除 Token 文件):**
|
|
@@ -123,15 +123,15 @@ updated: "2026-04-25"
|
|
|
123
123
|
```markdown
|
|
124
124
|
✅ 「{agentName}」已帮您记账成功
|
|
125
125
|
═══════════════
|
|
126
|
-
|
|
126
|
+
🔴 AI生成信息,请确认是否正确
|
|
127
127
|
|
|
128
128
|
💰 金额:¥{金额}
|
|
129
129
|
📊 类型:{类型}
|
|
130
|
+
🏷️ 分类:{分类}
|
|
130
131
|
🕐 时间:{日期时间}
|
|
131
132
|
═══════════════
|
|
132
133
|
|
|
133
134
|
📦 商品:{商品名}
|
|
134
|
-
🏷️ 分类:{分类}
|
|
135
135
|
🏪 商家:{商家}
|
|
136
136
|
💡 消费洞察:{洞察内容}
|
|
137
137
|
⏰ 时间说明:{timeNote}
|
|
@@ -148,15 +148,15 @@ updated: "2026-04-25"
|
|
|
148
148
|
```markdown
|
|
149
149
|
✅ 「你的小可爱」已帮您记账成功
|
|
150
150
|
═══════════════
|
|
151
|
-
|
|
151
|
+
🔴 AI生成信息,请确认是否正确
|
|
152
152
|
|
|
153
153
|
💰 金额:¥35.00
|
|
154
154
|
📊 类型:支出
|
|
155
|
+
🏷️ 分类:餐饮
|
|
155
156
|
🕐 时间:2026-04-24 12:30
|
|
156
157
|
═══════════════
|
|
157
158
|
|
|
158
159
|
📦 商品:午餐
|
|
159
|
-
🏷️ 分类:餐饮
|
|
160
160
|
🏪 商家:麦当劳
|
|
161
161
|
💡 消费洞察:本月餐饮支出占比30%,建议控制
|
|
162
162
|
|
|
@@ -236,12 +236,15 @@ updated: "2026-04-25"
|
|
|
236
236
|
```markdown
|
|
237
237
|
✅ 「{agentName}」已帮您记录收入
|
|
238
238
|
═══════════════
|
|
239
|
+
🔴 AI生成信息,请确认是否正确
|
|
240
|
+
|
|
239
241
|
💰 金额:¥{金额}
|
|
240
|
-
═══════════════
|
|
241
|
-
|
|
242
242
|
📊 类型:{类型}
|
|
243
243
|
🏷️ 分类:{分类}
|
|
244
244
|
🕐 时间:{日期时间}
|
|
245
|
+
═══════════════
|
|
246
|
+
|
|
247
|
+
🏪 付款方:{商家}
|
|
245
248
|
|
|
246
249
|
💡 消费洞察:{洞察内容}(可选)
|
|
247
250
|
|
|
@@ -257,12 +260,15 @@ updated: "2026-04-25"
|
|
|
257
260
|
```markdown
|
|
258
261
|
✅ 「你的小可爱」已帮您记录收入
|
|
259
262
|
═══════════════
|
|
260
|
-
|
|
261
|
-
═══════════════
|
|
263
|
+
🔴 AI生成信息,请确认是否正确
|
|
262
264
|
|
|
265
|
+
💰 金额:¥5000.00
|
|
263
266
|
📊 类型:收入
|
|
264
267
|
🏷️ 分类:工资
|
|
265
268
|
🕐 时间:2026-04-24 09:00
|
|
269
|
+
═══════════════
|
|
270
|
+
|
|
271
|
+
🏪 付款方:公司
|
|
266
272
|
|
|
267
273
|
💡 消费洞察:本月收入储蓄率50%,继续保持!
|
|
268
274
|
|
|
@@ -24,6 +24,17 @@ updated: "2026-04-25"
|
|
|
24
24
|
|
|
25
25
|
## 一、授权问题
|
|
26
26
|
|
|
27
|
+
### ⚠️ 授权流程关键说明(必读!)
|
|
28
|
+
|
|
29
|
+
**重要提示:验证码只能在 Agent/MCP 端(终端)生成!小程序里无法生成验证码,只能输入验证码!**
|
|
30
|
+
|
|
31
|
+
| 位置 | 作用 |
|
|
32
|
+
|:-----|:------|
|
|
33
|
+
| Agent/MCP 端(终端) | 生成验证码 |
|
|
34
|
+
| 小程序 | 只负责输入验证码 |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
27
38
|
### 1.1 未找到有效授权
|
|
28
39
|
|
|
29
40
|
**现象:**
|
|
@@ -40,7 +51,7 @@ updated: "2026-04-25"
|
|
|
40
51
|
**解决方案:**
|
|
41
52
|
1. 执行 `mcporter auth 柴米AI记账`
|
|
42
53
|
2. 查看终端显示的验证码
|
|
43
|
-
3. 在小程序"我的"→"
|
|
54
|
+
3. 在小程序"我的"→"Agent 授权"中输入验证码
|
|
44
55
|
4. 重新尝试记账
|
|
45
56
|
|
|
46
57
|
### 1.2 授权过期
|
|
@@ -77,9 +88,36 @@ mcporter auth 柴米AI记账 --reset
|
|
|
77
88
|
|
|
78
89
|
**解决方案:**
|
|
79
90
|
1. 重新执行 `mcporter auth 柴米AI记账` 生成新验证码
|
|
80
|
-
2. 在5分钟内在小程序"我的"→"
|
|
91
|
+
2. 在5分钟内在小程序"我的"→"Agent 授权"中输入
|
|
81
92
|
3. 注意区分大小写
|
|
82
93
|
|
|
94
|
+
### 1.4 签名验证失败(版本升级问题)
|
|
95
|
+
|
|
96
|
+
**现象:**
|
|
97
|
+
```
|
|
98
|
+
错误:签名验证失败
|
|
99
|
+
或
|
|
100
|
+
Unsupported state or unable to authenticate data
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**原因:**
|
|
104
|
+
- 2026年5月1日安全加固升级,旧 Token 使用的密钥已变更
|
|
105
|
+
- MCP Server 版本从 ≤3.4.0 升级到 ≥3.5.0 时需要重新授权
|
|
106
|
+
|
|
107
|
+
**解决方案:**
|
|
108
|
+
1. **手动清理旧配置(推荐):**
|
|
109
|
+
```bash
|
|
110
|
+
# 清理旧的 token 配置
|
|
111
|
+
rm -rf ~/.chaimi-keep
|
|
112
|
+
# 重新运行 MCP Server
|
|
113
|
+
chaimi-keep-mcp
|
|
114
|
+
```
|
|
115
|
+
2. 查看终端显示的新验证码
|
|
116
|
+
3. 在小程序"我的"→"Agent 授权"中输入新验证码
|
|
117
|
+
4. 重新尝试记账
|
|
118
|
+
|
|
119
|
+
**重要提示:** 此问题仅在安全加固版本升级时出现一次,后续不会再遇到。
|
|
120
|
+
|
|
83
121
|
> **注意:** 使用 Claude/Cursor 等原生 MCP 客户端的用户,如遇授权问题,请参考 [authentication.md - 2.3 原生 MCP 客户端授权失败](authentication.md#23-原生-mcp-客户端授权失败claudecursor)
|
|
84
122
|
|
|
85
123
|
---
|
package/server.js
CHANGED
|
@@ -25,6 +25,9 @@ const path = require('path');
|
|
|
25
25
|
const os = require('os');
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const crypto = require('crypto');
|
|
28
|
+
const { exec } = require('child_process');
|
|
29
|
+
const util = require('util');
|
|
30
|
+
const execPromise = util.promisify(exec);
|
|
28
31
|
|
|
29
32
|
// 读取版本文件获取版本号
|
|
30
33
|
function getVersion() {
|
|
@@ -152,9 +155,9 @@ const ENV = process.env.NODE_ENV || 'production';
|
|
|
152
155
|
// 加密的云函数 URL(开发和生产环境分开)
|
|
153
156
|
const ENCRYPTED_URLS = {
|
|
154
157
|
development: {
|
|
155
|
-
hub: '',
|
|
156
|
-
oauth: '',
|
|
157
|
-
prompt: ''
|
|
158
|
+
hub: 'enc:v2:d397243fce051c611175302dea8a7a54:8f0bc1dc6966e9149e8d3ecae2467830:cf61c7a159154d15aa1502deec2519931090ff791be67e89fd4e3b20322b2bea8071be02d768ec10abdc50b2d3e76df6dc44a6d46d61c2743acdef428205cc0e3534c93d94857cc91985db5fd69745053df05a87',
|
|
159
|
+
oauth: 'enc:v2:bff470f73af1723d6e320d39df72b342:eefb409dff5a4713931caa3886f9159c:18d0a711f31a5ba7e068e43d8bcce6b8fc56513d44ff3d90ab006063461b44025387e52cb2ce5ffe471a8a8f4b993879dcaa485dd1e845c647be134595467c4a00efc736d15abfcaa8d8cf460365fbf597e0',
|
|
160
|
+
prompt: 'enc:v2:a058c504374087a36417280f0bc4288d:ed5fc65a697c1254e8aeb9faa7119ec8:b8831fd319c35bc01554546827ce31bd04d2039fb87f0b6a19b35850ec228b6988b2103b1cc45a7bd21b117b299c5095a44e1ee4a4c2adb04e24a401e1271aff912503e2abb9cec03764b774df53e60b0686d3'
|
|
158
161
|
},
|
|
159
162
|
production: {
|
|
160
163
|
hub: 'enc:v2:500682dfbd51aff69852abe39430da35:3d57b5ac7f798c6770209159b6dfd9fb:7b9733f27208809b761090f7628f3799aac12c957cdd45b3d512eb4f1f1c18f626afd0b73b12982500122b11e373e0a2a71f14cb6c5966cf98af9ae4b9e79fb575d1e2f42ec403690d5c7dcc519e9eaec21865eb',
|
|
@@ -195,14 +198,14 @@ let tokenEncryptionKey;
|
|
|
195
198
|
// 初始化配置
|
|
196
199
|
async function initConfig() {
|
|
197
200
|
// 获取 URL 加密密钥
|
|
198
|
-
const urlEncryptKey = process.env.URL_ENCRYPT_KEY || '
|
|
201
|
+
const urlEncryptKey = process.env.URL_ENCRYPT_KEY || 'd2e4168144a50c6d8d0c4692cd73331eb2bc1db0cd727afaf29ff9645d480e59';
|
|
199
202
|
|
|
200
203
|
// 获取 Token 加密密钥
|
|
201
204
|
tokenEncryptionKey = getOrCreateSecretKey();
|
|
202
205
|
|
|
203
206
|
// 获取 API 密钥
|
|
204
207
|
if (!CHAIMI_API_SECRET) {
|
|
205
|
-
CHAIMI_API_SECRET = process.env.CHAIMI_API_SECRET || '
|
|
208
|
+
CHAIMI_API_SECRET = process.env.CHAIMI_API_SECRET || '040f513aa8f0688b4fa151865ee7515dac707f9649f48ec13c953a5e7dca0cad';
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
// 配置 URL
|
|
@@ -237,6 +240,11 @@ function setCachedToken(token) {
|
|
|
237
240
|
cachedEncryptedToken = encrypt(token, tokenEncryptionKey);
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
// 清除缓存的 Token
|
|
244
|
+
function clearCachedToken() {
|
|
245
|
+
cachedEncryptedToken = null;
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
// OAuth 管理器实例
|
|
241
249
|
let oauthManager = null;
|
|
242
250
|
|
|
@@ -652,7 +660,8 @@ async function getToken() {
|
|
|
652
660
|
// Token 获取失败,可能需要重新授权
|
|
653
661
|
authState.isAuthorized = false;
|
|
654
662
|
authState.isWaiting = false;
|
|
655
|
-
|
|
663
|
+
await startAuthFlow();
|
|
664
|
+
throw new Error(`NEED_AUTH:${authState.userCode || '等待生成验证码'}`);
|
|
656
665
|
}
|
|
657
666
|
}
|
|
658
667
|
|
|
@@ -757,6 +766,20 @@ async function callMcpHubWithLogging(tool, params, token, traceId, startTime, os
|
|
|
757
766
|
try {
|
|
758
767
|
const result = await callMcpHub(tool, paramsWithMeta, token, traceId, osInfo);
|
|
759
768
|
|
|
769
|
+
// ✅ 检查业务返回的 401 错误
|
|
770
|
+
if (!result.success && result.code === 401) {
|
|
771
|
+
console.error('检测到业务 401,触发重新授权流程');
|
|
772
|
+
|
|
773
|
+
// 清除缓存的 token
|
|
774
|
+
clearCachedToken();
|
|
775
|
+
await oauthManager.clearAuthState();
|
|
776
|
+
|
|
777
|
+
// 启动新授权流程
|
|
778
|
+
await startAuthFlow();
|
|
779
|
+
|
|
780
|
+
throw new Error(`NEED_AUTH:${authState.userCode || '等待生成验证码'}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
760
783
|
// 记录云函数调用成功(已停用:MCP调用日志待迁移到独立云函数)
|
|
761
784
|
// logMcpCall({
|
|
762
785
|
// traceId,
|
|
@@ -1560,6 +1583,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1560
1583
|
}
|
|
1561
1584
|
|
|
1562
1585
|
case 'export_data': {
|
|
1586
|
+
// 【临时措施】强制使用 CSV 格式,JSON 格式暂时停用
|
|
1587
|
+
if (processedArgs.format === 'json' || !processedArgs.format) {
|
|
1588
|
+
processedArgs.format = 'csv';
|
|
1589
|
+
}
|
|
1563
1590
|
const toolName = toolMapping[name];
|
|
1564
1591
|
const mcpParams = convertParams(name, processedArgs);
|
|
1565
1592
|
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo, agentType, apiProvider);
|
|
@@ -1852,6 +1879,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1852
1879
|
📱 验证码:**${userCode}**
|
|
1853
1880
|
━━━━━━━━━━━━━━
|
|
1854
1881
|
|
|
1882
|
+
⚠️ 重要说明:
|
|
1883
|
+
• 上面的验证码是我为您生成的!(只有在 Agent/MCP 端才能看到)
|
|
1884
|
+
• 小程序里无法生成验证码,只负责输入验证码!
|
|
1885
|
+
|
|
1855
1886
|
请在"柴米AI记账"小程序中完成授权:
|
|
1856
1887
|
|
|
1857
1888
|
1️⃣ 打开微信小程序"柴米AI记账"
|
|
@@ -2389,17 +2420,14 @@ async function getAgentName() {
|
|
|
2389
2420
|
if (oauthManager && oauthManager.tokenStorage) {
|
|
2390
2421
|
const localName = await oauthManager.tokenStorage.loadAgentName();
|
|
2391
2422
|
|
|
2392
|
-
//
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
const
|
|
2396
|
-
if (
|
|
2397
|
-
//
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
await oauthManager.tokenStorage.saveAgentName(cloudName);
|
|
2401
|
-
return cloudName;
|
|
2402
|
-
}
|
|
2423
|
+
// 尝试从云端获取,看看是否有更新
|
|
2424
|
+
const deviceCode = await oauthManager.tokenStorage.loadDeviceCode();
|
|
2425
|
+
if (deviceCode) {
|
|
2426
|
+
const cloudName = await fetchAgentNameFromCloud(deviceCode);
|
|
2427
|
+
if (cloudName && cloudName !== localName) {
|
|
2428
|
+
// 云端有更新,同步到本地
|
|
2429
|
+
await oauthManager.tokenStorage.saveAgentName(cloudName);
|
|
2430
|
+
return cloudName;
|
|
2403
2431
|
}
|
|
2404
2432
|
}
|
|
2405
2433
|
|
|
@@ -2436,6 +2464,46 @@ async function main() {
|
|
|
2436
2464
|
await initConfig();
|
|
2437
2465
|
await initOAuthManager();
|
|
2438
2466
|
|
|
2467
|
+
// 检查授权状态,决定是否杀掉旧进程
|
|
2468
|
+
try {
|
|
2469
|
+
// 先检查有没有 token(已授权)
|
|
2470
|
+
let hasValidToken = false;
|
|
2471
|
+
try {
|
|
2472
|
+
const existingToken = await oauthManager.tokenStorage.load();
|
|
2473
|
+
if (existingToken && existingToken.accessToken && existingToken.expiresAt) {
|
|
2474
|
+
const expiresAt = new Date(existingToken.expiresAt).getTime();
|
|
2475
|
+
if (expiresAt > Date.now() + 5 * 60 * 1000) {
|
|
2476
|
+
hasValidToken = true;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
} catch (e) {
|
|
2480
|
+
// 忽略
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
// 再检查有没有正在授权中(有验证码)
|
|
2484
|
+
let isAuthorizing = false;
|
|
2485
|
+
try {
|
|
2486
|
+
const savedAuthState = await oauthManager.loadAuthState();
|
|
2487
|
+
if (savedAuthState && savedAuthState.deviceCode && savedAuthState.userCode) {
|
|
2488
|
+
if (!oauthManager.isAuthStateExpired(savedAuthState)) {
|
|
2489
|
+
isAuthorizing = true;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
} catch (e) {
|
|
2493
|
+
// 忽略
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// 如果已授权且没在授权中 → 杀掉旧进程
|
|
2497
|
+
if (hasValidToken && !isAuthorizing) {
|
|
2498
|
+
await killOtherMcpProcesses();
|
|
2499
|
+
} else if (isAuthorizing) {
|
|
2500
|
+
console.error('🔐 正在授权中,不关闭旧进程');
|
|
2501
|
+
}
|
|
2502
|
+
} catch (e) {
|
|
2503
|
+
// 失败时不影响启动
|
|
2504
|
+
console.error('⚠️ 检查授权状态失败:', e.message);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2439
2507
|
const transport = new StdioServerTransport();
|
|
2440
2508
|
await server.connect(transport);
|
|
2441
2509
|
}
|
|
@@ -2522,10 +2590,8 @@ async function pollForAuthInBackground(deviceCode, interval) {
|
|
|
2522
2590
|
|
|
2523
2591
|
console.error('✅ 授权成功!可以继续使用记账功能');
|
|
2524
2592
|
|
|
2525
|
-
//
|
|
2526
|
-
|
|
2527
|
-
// process.exit(0); // 注释掉,避免授权成功后进程退出
|
|
2528
|
-
return; // 直接返回,停止轮询
|
|
2593
|
+
// 授权成功后退出进程,下次调用时使用新版本
|
|
2594
|
+
process.exit(0);
|
|
2529
2595
|
}
|
|
2530
2596
|
} catch (err) {
|
|
2531
2597
|
if (err.message.includes('expired') || err.message.includes('invalid')) {
|
|
@@ -2550,6 +2616,81 @@ function delay(ms) {
|
|
|
2550
2616
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
2551
2617
|
}
|
|
2552
2618
|
|
|
2619
|
+
// 杀掉所有其他 chaimi-keep-mcp 进程
|
|
2620
|
+
async function killOtherMcpProcesses() {
|
|
2621
|
+
try {
|
|
2622
|
+
const currentPid = process.pid;
|
|
2623
|
+
let pidsToKill = [];
|
|
2624
|
+
|
|
2625
|
+
if (os.platform() === 'win32') {
|
|
2626
|
+
// Windows: 使用 wmic 查找 node 进程
|
|
2627
|
+
try {
|
|
2628
|
+
const { stdout } = await execPromise('wmic process where "name=\'node.exe\'" get processid,commandline');
|
|
2629
|
+
const lines = stdout.split('\n').filter(line => line.trim() !== '');
|
|
2630
|
+
for (const line of lines) {
|
|
2631
|
+
if (line.includes('chaimi-keep-mcp') && !line.includes('wmic')) {
|
|
2632
|
+
const match = line.match(/(\d+)\s*$/);
|
|
2633
|
+
if (match && parseInt(match[1]) !== currentPid) {
|
|
2634
|
+
pidsToKill.push(match[1]);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
} catch (e) {
|
|
2639
|
+
// 失败时尝试 tasklist
|
|
2640
|
+
try {
|
|
2641
|
+
const { stdout } = await execPromise('tasklist /fi "imagename eq node.exe" /fo csv /v');
|
|
2642
|
+
const lines = stdout.split('\n').filter(line => line.trim() !== '');
|
|
2643
|
+
for (const line of lines) {
|
|
2644
|
+
if (line.includes('chaimi-keep-mcp')) {
|
|
2645
|
+
const match = line.match(/(\d+)/);
|
|
2646
|
+
if (match && parseInt(match[1]) !== currentPid) {
|
|
2647
|
+
pidsToKill.push(match[1]);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
} catch (e2) {
|
|
2652
|
+
// 忽略
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
} else {
|
|
2656
|
+
// macOS/Linux: 使用 ps 查找 node 进程
|
|
2657
|
+
try {
|
|
2658
|
+
const { stdout } = await execPromise('ps aux | grep node');
|
|
2659
|
+
const lines = stdout.split('\n');
|
|
2660
|
+
for (const line of lines) {
|
|
2661
|
+
if (line.includes('chaimi-keep-mcp') && !line.includes('grep')) {
|
|
2662
|
+
const match = line.match(/\s+(\d+)\s+/);
|
|
2663
|
+
if (match && parseInt(match[1]) !== currentPid) {
|
|
2664
|
+
pidsToKill.push(match[1]);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
} catch (e) {
|
|
2669
|
+
// 忽略
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
if (pidsToKill.length > 0) {
|
|
2674
|
+
console.error(`🔄 发现 ${pidsToKill.length} 个旧 MCP 进程正在运行,正在关闭...`);
|
|
2675
|
+
for (const pid of pidsToKill) {
|
|
2676
|
+
try {
|
|
2677
|
+
if (os.platform() === 'win32') {
|
|
2678
|
+
await execPromise(`taskkill /F /PID ${pid}`);
|
|
2679
|
+
} else {
|
|
2680
|
+
await execPromise(`kill -9 ${pid}`);
|
|
2681
|
+
}
|
|
2682
|
+
console.error(` ✅ 已关闭进程 ${pid}`);
|
|
2683
|
+
} catch (e) {
|
|
2684
|
+
console.error(` ⚠️ 关闭进程 ${pid} 失败:`, e.message);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
} catch (e) {
|
|
2689
|
+
// 失败时不影响启动
|
|
2690
|
+
console.error('⚠️ 关闭旧进程失败:', e.message);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2553
2694
|
/**
|
|
2554
2695
|
* 脱敏处理日志参数
|
|
2555
2696
|
* 移除敏感信息,限制长度
|