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 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
- await this.fs.writeFile(agentNamePath, JSON.stringify(data, null, 2), 'utf8');
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.0",
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
- 3. 小程序确认
36
+ 4. 用户在小程序输入验证码
35
37
  │────────────────────────>│
36
38
  │ │
37
- 4. 发放Token
39
+ 5. 小程序验证并发放Token
38
40
  │<────────────────────────│
39
41
  │ │
40
- 5. Token记账 │
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
- - 点击"我的" → "AI 助手绑定"
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. 进入"我的" → "AI 助手绑定"
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. 在小程序"我的"→"AI 助手绑定"中输入验证码
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
- 💰 金额:¥5000.00
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. 在小程序"我的"→"AI 助手绑定"中输入验证码
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分钟内在小程序"我的"→"AI 助手绑定"中输入
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 || 'chaimi-url-key-2024';
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 || 'chaimi-mcp-secret-2024';
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
- throw new Error(`NEED_AUTH:授权已过期,请重新授权`);
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
- if (localName === '柴米AI助手') {
2394
- // 获取保存的 deviceCode
2395
- const deviceCode = await oauthManager.tokenStorage.loadDeviceCode();
2396
- if (deviceCode) {
2397
- // 从云端获取
2398
- const cloudName = await fetchAgentNameFromCloud(deviceCode);
2399
- if (cloudName && cloudName !== '柴米AI助手') {
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
- // 【修复】不退出进程,让 Agent 可以在同一会话中继续执行记账操作
2526
- // 这样 Agent 调用 get_skill 授权后,可以立即调用 save_expense 记账
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
  * 移除敏感信息,限制长度