chaimi-keep-mcp 3.3.0-beta.7 → 3.3.1-beta.0

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.
Files changed (5) hide show
  1. package/README.md +10 -0
  2. package/SKILL.md +66 -50
  3. package/oauth.js +2 -2
  4. package/package.json +1 -1
  5. package/server.js +192 -156
package/README.md CHANGED
@@ -89,6 +89,16 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
89
89
 
90
90
  ## Changelog
91
91
 
92
+ ### v3.3.1-beta.0 (2026-04-27)
93
+ - **优化** 简化工具描述 - save_expense/save_receipt/save_income 描述更简洁,不暴露业务逻辑
94
+ - **优化** 参数类型自动转换 - 支持 CLI 工具的字符串传参自动转为数字
95
+ - **优化** 改进错误提示 - 添加 `hint` 和 `debug` 字段,帮助 Agent 自查参数问题
96
+ - **新增** 参数查看指南 - SKILL.md 添加"六.5 如何查看工具参数"章节
97
+ - **优化** 更新记账流程 - 简化为"4步记账法",更易于 Agent 执行
98
+
99
+ ### v3.3.0-beta.8 (2026-04-27)
100
+ - **重要变更** Server 只返回原始数据 - 不再构建 userMessage,由 Agent 使用模板渲染
101
+
92
102
  ### v3.1.47-beta.5 (2026-04-22)
93
103
  - **修复** 输入长度限制 - 添加硬性长度检查,防止超大字符串攻击
94
104
  - **修复** 错误信息泄露 - 生产环境只显示友好提示,不暴露内部错误
package/SKILL.md CHANGED
@@ -4,8 +4,7 @@ description: >-
4
4
  柴米AI记账MCP Server,连接微信小程序记账功能。
5
5
  TRIGGER: 用户提及"记账/记录/保存"并涉及金额或商品信息。
6
6
  DO NOT TRIGGER: 非记账操作,或明确使用其他记账工具时。
7
- version: "3.2.0"
8
- updated: "2026-04-25"
7
+ updated: "2026-04-27"
9
8
  ---
10
9
 
11
10
  # 柴米AI记账 Skill
@@ -122,29 +121,71 @@ chaimi-keep-mcp ⭐ 本Skill
122
121
 
123
122
  ## 六、快速开始
124
123
 
125
- ### 3步极简记账流程
124
+ ### 4步记账法
126
125
 
127
126
  ```
128
- 第1步:状态自检
129
- 检查依赖历史习惯
130
- 第2步:获取Skill
131
- ↓ 调用 get_skill 获取最新规范
132
- 3步:执行记账
133
- 调用对应工具(save_expense/income/receipt)
134
- 返回美学回复
135
- ```
127
+ 第1步:读文档
128
+ └─ get_skill() 获取 SKILL.md 规范
129
+ └─ references/response-templates.md 了解回复模板
130
+
131
+ 2步:拿Token
132
+ └─ 文字记账:get_text_parse_prompt() 获取 Token 和解析模板
133
+ └─ 小票记账:get_parse_prompt() 获取 Token 和解析模板
136
134
 
137
- ### 首次使用必做
135
+ 第3步:AI解析
136
+ └─ 按模板解析用户输入,生成结构化数据 + _requestToken
138
137
 
139
- 1. **完成授权** → 运行 `./scripts/auth-flow.sh`
140
- 2. **验证安装** → 运行 `./scripts/verify-setup.sh`
141
- 3. **开始记账** "午餐35元"
138
+ 第4步:执行记账
139
+ └─ 调用工具(save_expense/income/receipt,带上 _requestToken)
140
+ └─ 根据 _templateLocation 使用模板渲染美学回复
141
+ ```
142
142
 
143
143
  ---
144
144
 
145
145
  ## 七、回复规范
146
146
 
147
- ### 7.1 5层视觉结构
147
+ ### 7.1 核心原则
148
+
149
+ **Server 只返回原始数据,Agent 必须使用模板自行渲染回复。**
150
+
151
+ 所有工具调用成功后:
152
+ - ❌ 不再返回 `userMessage` 字段
153
+ - ✅ 返回 `_templateLocation` 指向模板文件位置
154
+ - ✅ 返回 `_templateHint` 提示使用哪个模板
155
+ - ✅ 返回 `_templateVariables` 提供模板变量数据
156
+
157
+ ### 7.2 模板文件位置
158
+
159
+ **主模板文件**: `references/response-templates.md`
160
+
161
+ **模板文件结构**:
162
+ ```
163
+ references/
164
+ └── response-templates.md # 完整回复模板库(包含所有工具的回复模板)
165
+ ```
166
+
167
+ ### 7.3 各工具对应的回复模板
168
+
169
+ | 工具 | 模板位置 | 模板章节 | 说明 |
170
+ |------|---------|---------|------|
171
+ | `save_expense` | `references/response-templates.md` | 2.1 标准成功模板 | 文字记账成功 |
172
+ | `save_income` | `references/response-templates.md` | 2.1 标准成功模板 | 收入记账成功 |
173
+ | `save_receipt` | `references/response-templates.md` | 2.3 小票记账模板 | 小票记账成功 |
174
+ | `get_expenses` | `references/response-templates.md` | 2.5 查询结果模板 | 消费记录查询 |
175
+ | `get_receipt_list` | `references/response-templates.md` | 2.5 查询结果模板 | 小票列表查询 |
176
+ | `get_statistics` | `references/response-templates.md` | 2.6 统计结果模板 | 消费统计分析 |
177
+
178
+ ### 7.4 回复模板使用流程
179
+
180
+ ```
181
+ 1. 调用工具 → 返回原始数据 + _templateLocation + _templateHint + _templateVariables
182
+ 2. 读取 _templateLocation 指定的模板文件
183
+ 3. 根据 _templateHint 选择对应模板章节
184
+ 4. 使用 _templateVariables 填充模板变量
185
+ 5. 渲染最终回复给用户
186
+ ```
187
+
188
+ ### 7.5 5层视觉结构
148
189
 
149
190
  ```
150
191
  【第1层:成功标识】✅ 「Agent名称」已帮您记账成功
@@ -164,7 +205,7 @@ chaimi-keep-mcp ⭐ 本Skill
164
205
  ───────────────
165
206
  ```
166
207
 
167
- ### 7.2 标准成功模板(方案1:双分隔线短版)
208
+ ### 7.6 标准成功模板
168
209
 
169
210
  ```markdown
170
211
  ✅ 「{agentName}」已帮您记账成功
@@ -180,40 +221,13 @@ chaimi-keep-mcp ⭐ 本Skill
180
221
  💡 消费洞察:{洞察内容}(可选)
181
222
 
182
223
  ───────────────
183
- 🎉 {正能量祝福语}!
184
- 柴米AI记账
185
- chaimi-keep-mcp v{版本号}
186
- ───────────────
187
- ```
188
-
189
- ### 7.3 极简模板(连续记账)
190
-
191
- ```markdown
192
- ✅ 已记录:{商品名} ¥{金额} · {分类}
193
- {正能量祝福语}
194
- ```
195
-
196
- ### 7.4 高金额模板(>1000元)
197
-
198
- ```markdown
199
- ⚠️ 大额消费确认
200
- ═══════════════
201
- 💰 金额:¥{金额}
202
- 📦 商品:{商品名}
203
- 🕐 时间:{日期}
204
-
205
- ✅ 已确认录入「柴米AI记账」
206
-
207
- 💡 消费洞察:{洞察内容}(可选)
208
-
209
- ───────────────
210
- 💡 {消费建议}
224
+ 🎉 {正能量情绪词}!
211
225
  柴米AI记账
212
226
  chaimi-keep-mcp v{版本号}
213
227
  ───────────────
214
228
  ```
215
229
 
216
- ### 7.5 变量说明
230
+ ### 7.7 模板变量说明
217
231
 
218
232
  | 变量 | 说明 | 示例 |
219
233
  |:-----|:-----|:-----|
@@ -224,10 +238,12 @@ chaimi-keep-mcp ⭐ 本Skill
224
238
  | {商家} | 商家名称(可选) | 麦当劳 |
225
239
  | {日期时间} | 格式化时间 | 2026-04-24 12:30 |
226
240
  | {洞察内容} | 消费洞察建议(可选) | 本月餐饮支出占比30%,建议控制 |
227
- | {正能量祝福语} | 分类对应祝福语 | 美食为梦想充电,继续向前冲!🍚 |
241
+ | {正能量情绪词} | 分类对应正能量祝福语 | 美食为梦想充电,继续向前冲!🍚 |
228
242
  | {版本号} | MCP版本 | 3.2.0 |
229
243
 
230
- **详细正能量词库和模板** → 见 references/response-templates.md
244
+ **详细正能量情绪词库和完整模板** → 见 references/response-templates.md
245
+
246
+ **历史变更记录** → 见 CHANGELOG.md
231
247
 
232
248
  ---
233
249
 
@@ -269,6 +285,6 @@ chaimi-keep-mcp ⭐ 本Skill
269
285
 
270
286
  ---
271
287
 
272
- **文档版本:v3.2.0**
273
- **最后更新:2026-04-25**
288
+
289
+ **最后更新:2026-04-27**
274
290
  **Status:生产就绪**
package/oauth.js CHANGED
@@ -497,7 +497,7 @@ class FileTokenStorage extends TokenStorage {
497
497
  agentName: agentName || '柴米AI助手',
498
498
  updatedAt: new Date().toISOString()
499
499
  };
500
- await this.fs.writeFile(agentNamePath, JSON.stringify(data, null, 2), { mode: 0o600 });
500
+ await this.fs.writeFile(agentNamePath, JSON.stringify(data, null, 2), 'utf8');
501
501
  console.error('✅ Agent 名称已保存:', agentNamePath);
502
502
  } catch (err) {
503
503
  console.error('❌ 保存 Agent 名称失败:', err.message);
@@ -524,7 +524,7 @@ class FileTokenStorage extends TokenStorage {
524
524
  deviceCode,
525
525
  updatedAt: new Date().toISOString()
526
526
  };
527
- await this.fs.writeFile(deviceCodePath, JSON.stringify(data, null, 2), { mode: 0o600 });
527
+ await this.fs.writeFile(deviceCodePath, JSON.stringify(data, null, 2), 'utf8');
528
528
  } catch (err) {
529
529
  // 忽略错误
530
530
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.3.0-beta.7",
3
+ "version": "3.3.1-beta.0",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -244,7 +244,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
244
244
  },
245
245
  {
246
246
  name: 'save_expense',
247
- description: '保存单商品消费记录(AI文字记账)。只需提供商品名称和金额,其他参数自动填充。示例:name="午餐", amount=35',
247
+ description: '【强制验证Token】保存单商品消费记录。前置要求:先调用 get_skill 和 get_text_parse_prompt。字段列表见 get_text_parse_prompt 返回的模板。',
248
248
  inputSchema: {
249
249
  type: 'object',
250
250
  properties: {
@@ -266,7 +266,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
266
266
  },
267
267
  {
268
268
  name: 'save_receipt',
269
- description: '【图片小票专用】保存购物小票/发票/收据。⚠️ 重要:items数组中每个商品必须包含完整的4个字段:name(商品名称)、amount(金额=单价×数量)、price(单价)、quantity(数量)。示例:[{"name":"苹果","amount":5.5,"price":5.5,"quantity":1}]',
269
+ description: '【强制验证Token】【必须上传图片】保存购物小票/发票/收据图片。⚠️ 注意:必须是图片才能使用此工具记账,文字描述请用 save_expense。前置要求:先调用 get_skill 和 get_parse_prompt。items为商品数组,字段列表见 get_parse_prompt 返回的模板.',返回的模板。',
270
270
  inputSchema: {
271
271
  type: 'object',
272
272
  properties: {
@@ -309,7 +309,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
309
309
  },
310
310
  {
311
311
  name: 'get_expenses',
312
- description: '查询消费记录(支持周/月周期)。支持按周/月或日期范围筛选,可查看分类、商家、来源等多维度数据。新功能:新增 period 参数,以及消费模式分析(高频商家、消费时间等)',
312
+ description: '查询消费记录(支持周/月周期)。支持按周/月或日期范围筛选,可查看分类、商家、来源等多维度数据。新功能:新增 period 参数,以及消费模式分析(高频商家、消费时间等)。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
313
313
  inputSchema: {
314
314
  type: 'object',
315
315
  properties: {
@@ -330,7 +330,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
330
330
  },
331
331
  {
332
332
  name: 'get_receipt_list',
333
- description: '获取小票列表。支持按日期范围、商家、金额等多维度筛选',
333
+ description: '获取小票列表。支持按日期范围、商家、金额等多维度筛选。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
334
334
  inputSchema: {
335
335
  type: 'object',
336
336
  properties: {
@@ -347,7 +347,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
347
347
  },
348
348
  {
349
349
  name: 'get_statistics',
350
- description: '获取消费统计(支持周/月周期)。支持按周/月或日期范围统计,可查看分类占比、消费趋势等。新功能:新增 period 参数(this_week、last_week、this_month、last_month),以及 insights 洞察数据',
350
+ description: '获取消费统计(支持周/月周期)。支持按周/月或日期范围统计,可查看分类占比、消费趋势等。新功能:新增 period 参数(this_week、last_week、this_month、last_month),以及 insights 洞察数据。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
351
351
  inputSchema: {
352
352
  type: 'object',
353
353
  properties: {
@@ -365,7 +365,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
365
365
  },
366
366
  {
367
367
  name: 'get_insights',
368
- description: '【新功能】获取消费洞察线索(包含趋势、模式、健康、优化建议)。纯云端计算,零AI成本!返回洞察线索,供 Agent 结合用户大模型进行深度分析',
368
+ description: '【新功能】获取消费洞察线索(包含趋势、模式、健康、优化建议)。纯云端计算,零AI成本!返回洞察线索,供 Agent 结合用户大模型进行深度分析。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
369
369
  inputSchema: {
370
370
  type: 'object',
371
371
  properties: {
@@ -376,7 +376,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
376
376
  },
377
377
  {
378
378
  name: 'export_data',
379
- description: '【新功能】导出完整的消费数据,供 Agent 深度分析(零AI成本!)。支持导出完整的消费记录和小票数据,然后在用户侧使用大模型进行深度分析',
379
+ description: '【新功能】导出完整的消费数据,供 Agent 深度分析(零AI成本!)。支持导出完整的消费记录和小票数据,然后在用户侧使用大模型进行深度分析。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
380
380
  inputSchema: {
381
381
  type: 'object',
382
382
  properties: {
@@ -390,7 +390,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
390
390
  },
391
391
  {
392
392
  name: 'save_income',
393
- description: '保存收入记录(工资、奖金、红包等)。⚠️ 重要:必须先调用 get_text_parse_prompt 解析,然后传入所有解析结果字段',
393
+ description: '【强制验证Token】保存收入记录。前置要求:先调用 get_skill get_text_parse_prompt。字段列表见 get_text_parse_prompt 返回的模板.',
394
394
  inputSchema: {
395
395
  type: 'object',
396
396
  properties: {
@@ -409,7 +409,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
409
409
  },
410
410
  {
411
411
  name: 'get_text_parse_prompt',
412
- description: '获取文字记账解析的Prompt模板,用于指导大模型如何解析用户输入的记账文字。返回的Prompt应作为system message,配合用户输入的记账文字作为user message调用大模型',
412
+ description: '【不调用100%失败】获取文字记账解析的Prompt模板和requestToken。⚠️ 文字/语音记账必须先调用此工具获取Token,AI解析时必须返回_requestToken字段,否则save_expense/save_income会报MISSING_TOKEN错误!',
413
413
  inputSchema: {
414
414
  type: 'object',
415
415
  properties: {
@@ -420,7 +420,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
420
420
  },
421
421
  {
422
422
  name: 'get_parse_prompt',
423
- description: '获取小票图片解析的Prompt模板,用于指导大模型如何格式化输出小票信息。返回的Prompt应作为system message,配合小票图片作为user message调用大模型',
423
+ description: '【不调用100%失败】获取小票图片解析的Prompt模板和requestToken。⚠️ 小票记账必须先调用此工具获取Token,AI解析时必须返回_requestToken字段,否则save_receipt会报MISSING_TOKEN错误!',
424
424
  inputSchema: {
425
425
  type: 'object',
426
426
  properties: {
@@ -430,7 +430,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
430
430
  },
431
431
  {
432
432
  name: 'submit_feedback',
433
- description: '提交反馈、建议或问题报告。当用户遇到记账问题、有功能建议或发现异常时,可使用此工具提交反馈。反馈类型包括:bug(问题报告)、feature(功能建议)、improvement(优化建议)、other(其他)。提交成功后会返回反馈编号,可用于查询处理进度。注意:反馈内容不要超过150个字符,超过部分会被截断',
433
+ description: '提交反馈、建议或问题报告。当用户遇到记账问题、有功能建议或发现异常时,可使用此工具提交反馈。反馈类型包括:bug(问题报告)、feature(功能建议)、improvement(优化建议)、other(其他)。提交成功后会返回反馈编号,可用于查询处理进度。注意:反馈内容不要超过150个字符,超过部分会被截断。返回格式:本工具只返回原始数据,不返回格式化消息。Agent必须使用get_skill获取SKILL.md中的"七、回复规范",再读取references/response-templates.md中的模板自行渲染回复。',
434
434
  inputSchema: {
435
435
  type: 'object',
436
436
  properties: {
@@ -478,9 +478,7 @@ async function getToken() {
478
478
  const existingToken = await oauthManager.tokenStorage.load();
479
479
  if (existingToken && existingToken.accessToken && existingToken.expiresAt) {
480
480
  const expiresAt = new Date(existingToken.expiresAt).getTime();
481
- const now = Date.now();
482
- const threshold = now + 5 * 60 * 1000;
483
- if (expiresAt > threshold) {
481
+ if (expiresAt > Date.now() + 5 * 60 * 1000) {
484
482
  // Token 有效,直接使用
485
483
  cachedToken = existingToken.accessToken;
486
484
  tokenExpireTime = expiresAt;
@@ -886,8 +884,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
886
884
  }
887
885
  }
888
886
 
887
+ // 【新增】参数类型自动转换(兼容 CLI 工具的字符串传参)
888
+ function autoConvertTypes(toolName, args) {
889
+ const numberFields = {
890
+ 'save_expense': ['amount', 'date'],
891
+ 'save_receipt': ['totalAmount', 'actualAmount', 'originalAmount', 'discountAmount', 'date'],
892
+ 'save_income': ['amount', 'date'],
893
+ };
894
+
895
+ const fields = numberFields[toolName] || [];
896
+ for (const field of fields) {
897
+ if (field in args && typeof args[field] !== 'number') {
898
+ const parsed = parseFloat(args[field]);
899
+ if (!isNaN(parsed)) {
900
+ console.error(`[TypeConvert] ${field}: "${args[field]}" → ${parsed}`);
901
+ args[field] = parsed;
902
+ }
903
+ }
904
+ }
905
+
906
+ // 转换 items 数组中的数字字段
907
+ if (args.items && Array.isArray(args.items)) {
908
+ args.items.forEach(item => {
909
+ ['amount', 'price', 'quantity'].forEach(field => {
910
+ if (field in item && typeof item[field] !== 'number') {
911
+ const parsed = parseFloat(item[field]);
912
+ if (!isNaN(parsed)) item[field] = parsed;
913
+ }
914
+ });
915
+ });
916
+ }
917
+ }
918
+
919
+ autoConvertTypes(name, processedArgs);
920
+
889
921
  let result;
890
- let userMessage;
891
922
 
892
923
  switch (name) {
893
924
  case 'get_skill': {
@@ -931,8 +962,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
931
962
  if (missingFields.length > 0) {
932
963
  result = {
933
964
  success: false,
934
- error: `必填字段缺失:${missingFields.join(', ')}。请从解析结果中提取并传递所有字段。`,
935
- code: 400
965
+ error: `必填字段缺失:${missingFields.join(', ')}`,
966
+ code: 400,
967
+ hint: '解决方法:1.查看 SKILL.md 中"六.5 如何查看工具参数" 2.运行 mcporter list 柴米记账 --schema 查看完整参数结构',
968
+ debug: {
969
+ missing: missingFields,
970
+ received: Object.keys(processedArgs),
971
+ docs: '调用 get_skill() 获取详细使用指南'
972
+ }
936
973
  };
937
974
  userMessage = `❌ 记账失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从用户输入中提取了所有信息\n2. 确保传递以下字段:name(商品名)、amount(金额)、category(分类)、rawInput(原始输入)\n3. 参考 SKILL.md 中的"调用前检查清单"`;
938
975
  break;
@@ -995,17 +1032,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
995
1032
  achievementsText = '\n\n🎉 成就解锁:' + result.newlyUnlocked.map(a => `${a.icon} ${a.title}(+${a.points}分)`).join(';');
996
1033
  }
997
1034
 
998
- // 【新增】获取 Agent 名称
999
- const agentName = await getAgentName();
1000
-
1001
- // SKILL 规范格式:新的回复模板
1002
- const displayStoreText = displayStore ? `【${displayStore}】` : '';
1003
- const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
1004
- userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${displayName}${displayStoreText} 💰¥${displayAmount}\n · 收支类型: 支出\n · 分类:${result.data?.categoryName || displayCategory}\n · 时间:${dateStr}\n✅ ${friendlyEnding}${insightsText}${achievementsText}\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
1005
-
1006
- if (!processedArgs.agentType || !processedArgs.apiProvider) {
1007
- userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
1008
- }
1035
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1036
+ result._templateLocation = 'references/response-templates.md';
1037
+ result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1038
+ result._templateVariables = {
1039
+ agentName: await getAgentName(),
1040
+ 商品名: displayName,
1041
+ 商家: displayStore || '',
1042
+ 金额: displayAmount,
1043
+ 分类: result.data?.categoryName || displayCategory,
1044
+ 日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
1045
+ 正能量祝福语: friendlyEnding,
1046
+ insightsText,
1047
+ achievementsText
1048
+ };
1009
1049
  }
1010
1050
  break;
1011
1051
  }
@@ -1024,14 +1064,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1024
1064
  if (missingFields.length > 0) {
1025
1065
  result = {
1026
1066
  success: false,
1027
- error: `必填字段缺失:${missingFields.join(', ')}。请从 get_parse_prompt 的解析结果中提取并传递所有字段。`,
1028
- code: 400
1067
+ error: `必填字段缺失:${missingFields.join(', ')}`,
1068
+ code: 400,
1069
+ hint: '解决方法:1.查看 SKILL.md 中"六.5 如何查看工具参数" 2.运行 mcporter list 柴米记账 --schema 查看完整参数结构',
1070
+ debug: {
1071
+ missing: missingFields,
1072
+ received: Object.keys(processedArgs),
1073
+ docs: '调用 get_skill() 获取详细使用指南'
1074
+ }
1029
1075
  };
1030
- userMessage = `❌ 保存失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从 get_parse_prompt 的解析结果中提取了所有信息\n2. 确保传递以下字段:store、totalAmount、actualAmount、originalAmount、discountAmount、storeCategory、storeSubCategory\n3. 参考 SKILL.md 中的"小票字段核对清单"`;
1031
- break;
1032
- }
1033
-
1034
- // 数值校验
1076
+ userMessage = `❌ 保存失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从 get_parse_prompt 的解析结果中提取了所有信息\n2. 确保传递以下字段:store、totalAmount、actualAmount、originalAmount、discountAmount、storeCategory、storeSubCategory\n3. 参考 SKILL.md 中的"小票字段核对清单"`;\n break;\n }\n \n // 数值校验
1035
1077
  const amountFields = ['totalAmount', 'actualAmount', 'originalAmount'];
1036
1078
  for (const field of amountFields) {
1037
1079
  if (typeof processedArgs[field] !== 'number' || processedArgs[field] <= 0) {
@@ -1240,16 +1282,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1240
1282
  achievementsText = '\n\n🎉 成就解锁:' + result.newlyUnlocked.map(a => `${a.icon} ${a.title}(+${a.points}分)`).join(';');
1241
1283
  }
1242
1284
 
1243
- // 【新增】获取 Agent 名称
1244
- const agentName = await getAgentName();
1245
-
1246
- // SKILL 规范格式:新的回复模板
1247
- const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
1248
- userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${storeName} 💰¥${totalAmount}\n · 收支类型: 支出\n · 分类:${result.data?.storeCategory || category}\n · 商品数量:${itemCount}件\n · 时间:${dateStr}\n✅ ${friendlyEnding}${insightsText}${achievementsText}\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
1249
-
1250
- if (!processedArgs.agentType || !processedArgs.apiProvider) {
1251
- userMessage += '\n\n💡 提示:传递agentType和apiProvider参数可解锁小程序记录来源统计功能,示例:agentType="openclaw", apiProvider="doubao"';
1252
- }
1285
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1286
+ result._templateLocation = 'references/response-templates.md';
1287
+ result._templateHint = '请使用references/response-templates.md中的2.3小票记账模板渲染回复';
1288
+ result._templateVariables = {
1289
+ agentName: await getAgentName(),
1290
+ store: storeName,
1291
+ totalAmount: totalAmount,
1292
+ category: result.data?.storeCategory || category,
1293
+ itemCount: itemCount,
1294
+ date: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
1295
+ 正能量祝福语: friendlyEnding,
1296
+ insightsText,
1297
+ achievementsText,
1298
+ items: processedArgs.items
1299
+ };
1253
1300
  }
1254
1301
  break;
1255
1302
  }
@@ -1265,33 +1312,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1265
1312
  const items = result.data?.items || [];
1266
1313
  const patterns = result.data?.patterns || {};
1267
1314
 
1268
- // 构建友好的摘要信息
1269
- let summary = `📊 消费记录查询成功\n\n`;
1270
- summary += `📋 共找到 ${total} 条记录\n`;
1271
-
1272
- // 高频商家
1273
- if (patterns.highFrequencyStores && patterns.highFrequencyStores.length > 0) {
1274
- const topStore = patterns.highFrequencyStores[0];
1275
- summary += `🏪 高频商家:${topStore.store}(${topStore.count}次,均价¥${topStore.avgAmount})\n`;
1276
- }
1277
-
1278
- // 消费时间模式
1279
- if (patterns.timePattern) {
1280
- summary += `⏰ 消费高峰:${patterns.timePattern.peakTime}\n`;
1281
- if (patterns.timePattern.lateNightCount > 0) {
1282
- summary += `🌙 深夜消费:${patterns.timePattern.lateNightCount}次\n`;
1283
- }
1284
- }
1285
-
1286
- // 异常消费
1287
- if (patterns.unusualItems && patterns.unusualItems.length > 0) {
1288
- const topUnusual = patterns.unusualItems[0];
1289
- summary += `💡 大额消费:${topUnusual.name} ¥${topUnusual.amount}\n`;
1290
- }
1291
-
1292
- summary += `\n💡 提示:完整数据(含每笔明细、flags标记)请查看下方 JSON 数据`;
1293
-
1294
- userMessage = summary;
1315
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1316
+ result._templateLocation = 'references/response-templates.md';
1317
+ result._templateHint = '请使用references/response-templates.md中的2.5查询结果模板渲染回复';
1318
+ result._templateVariables = {
1319
+ total: total,
1320
+ items: items,
1321
+ patterns: patterns
1322
+ };
1295
1323
  }
1296
1324
  break;
1297
1325
  }
@@ -1304,42 +1332,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1304
1332
  if (result.success) {
1305
1333
  const data = result.data || {};
1306
1334
 
1307
- // 构建友好的统计摘要
1308
- let summary = `📈 统计查询成功\n\n`;
1309
- summary += `📅 周期:${data.periodDisplay || data.period || '未知'}\n`;
1310
- summary += `💰 总支出:¥${data.totalAmount || 0}(${data.totalCount || 0}笔)\n`;
1311
-
1312
- // 环比对比
1313
- if (data.compare && data.compare.vsLastPeriod !== undefined) {
1314
- const change = data.compare.vsLastPeriod;
1315
- const emoji = change > 0 ? '📈' : change < 0 ? '📉' : '➡️';
1316
- summary += `${emoji} 环比:${change > 0 ? '+' : ''}${change}%\n`;
1317
- }
1318
-
1319
- // 预算进度
1320
- if (data.budget) {
1321
- summary += `🎯 预算:已用¥${data.budget.used}/¥${data.budget.total}(${data.budget.progress}%)\n`;
1322
- }
1323
-
1324
- // Top 3 分类
1325
- if (data.categories && data.categories.length > 0) {
1326
- summary += `\n📂 消费分类 Top3:\n`;
1327
- data.categories.slice(0, 3).forEach((cat, idx) => {
1328
- summary += ` ${idx + 1}. ${cat.category}:¥${cat.amount}(${cat.percentage}%)\n`;
1329
- });
1330
- }
1331
-
1332
- // 洞察摘要
1333
- if (data.insights && data.insights.length > 0) {
1334
- summary += `\n💡 洞察线索(${data.insights.length}条):\n`;
1335
- data.insights.slice(0, 3).forEach(insight => {
1336
- summary += ` ${insight.emoji || '•'} ${insight.title}:${insight.message}\n`;
1337
- });
1338
- }
1339
-
1340
- summary += `\n💡 提示:完整数据(含每日统计、所有洞察)请查看下方 JSON 数据`;
1341
-
1342
- userMessage = summary;
1335
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1336
+ result._templateLocation = 'references/response-templates.md';
1337
+ result._templateHint = '请使用references/response-templates.md中的2.6统计结果模板渲染回复';
1338
+ result._templateVariables = {
1339
+ period: data.periodDisplay || data.period || '未知',
1340
+ totalAmount: data.totalAmount || 0,
1341
+ totalCount: data.totalCount || 0,
1342
+ compare: data.compare,
1343
+ budget: data.budget,
1344
+ categories: data.categories,
1345
+ insights: data.insights
1346
+ };
1343
1347
  }
1344
1348
  break;
1345
1349
  }
@@ -1383,9 +1387,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1383
1387
  }
1384
1388
  }
1385
1389
 
1386
- summary += `\n💡 提示:每条洞察都包含 suggestionForAgent 字段,可用于深度分析`;
1387
-
1388
- userMessage = summary;
1390
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1391
+ result._templateLocation = 'references/response-templates.md';
1392
+ result._templateHint = '请使用references/response-templates.md中的2.6智能洞察模板渲染回复';
1393
+ result._templateVariables = {
1394
+ insights: insights
1395
+ };
1389
1396
  }
1390
1397
  break;
1391
1398
  }
@@ -1401,15 +1408,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1401
1408
  const totalAmount = summary.totalAmount || 0;
1402
1409
  const periodDisplay = result.data?.periodDisplay || '';
1403
1410
 
1404
- let summaryText = `📦 数据导出完成\n\n`;
1405
- if (periodDisplay) {
1406
- summaryText += `📅 周期:${periodDisplay}\n`;
1407
- }
1408
- summaryText += `📊 导出记录数:${totalRecords}条\n`;
1409
- summaryText += `💰 总金额:¥${totalAmount.toFixed ? totalAmount.toFixed(2) : totalAmount}\n`;
1410
- summaryText += `\n✅ 完整数据已准备好,你可以使用自己的大模型进行深度分析了!\n(完整数据在下方的完整数据JSON中)`;
1411
-
1412
- userMessage = summaryText;
1411
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1412
+ result._templateLocation = 'references/response-templates.md';
1413
+ result._templateHint = '请使用references/response-templates.md中的模板渲染回复';
1414
+ result._templateVariables = {
1415
+ period: periodDisplay,
1416
+ totalRecords: totalRecords,
1417
+ totalAmount: totalAmount
1418
+ };
1413
1419
  }
1414
1420
  break;
1415
1421
  }
@@ -1428,8 +1434,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1428
1434
  if (missingFields.length > 0) {
1429
1435
  result = {
1430
1436
  success: false,
1431
- error: `必填字段缺失:${missingFields.join(', ')}。请从用户输入中提取并传递所有字段。`,
1432
- code: 400
1437
+ error: `必填字段缺失:${missingFields.join(', ')}`,
1438
+ code: 400,
1439
+ hint: '解决方法:1.查看 SKILL.md 中"六.5 如何查看工具参数" 2.运行 mcporter list 柴米记账 --schema 查看完整参数结构',
1440
+ debug: {
1441
+ missing: missingFields,
1442
+ received: Object.keys(processedArgs),
1443
+ docs: '调用 get_skill() 获取详细使用指南'
1444
+ }
1433
1445
  };
1434
1446
  userMessage = `❌ 收入记录失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从用户输入中提取了所有信息\n2. 确保传递以下字段:name(收入来源)、amount(金额)、category(分类)、date(日期)、rawInput(原始输入)\n3. 参考 SKILL.md 中的"调用前检查清单"`;
1435
1447
  break;
@@ -1468,14 +1480,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1468
1480
  const displayAmount = processedArgs.amount || 0;
1469
1481
  const displayCategory = processedArgs.category || '其他';
1470
1482
  const displayStore = processedArgs.store || '';
1471
- const displayStoreText = displayStore ? `【${displayStore}】` : '';
1472
-
1473
- // 【新增】获取 Agent 名称
1474
- const agentName = await getAgentName();
1475
1483
 
1476
- // 按 SKILL 规范格式:新的回复模板
1477
- const dateStr = result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN');
1478
- userMessage = `✅ 「${agentName}」已帮您录入「柴米AI记账」\n · 商品/店名:${displayName}${displayStoreText} 💰¥${displayAmount}\n · 收支类型: 收入\n · 分类:${result.data?.categoryName || displayCategory}\n · 时间:${dateStr}\n✅ 入账顺利!💰\n-------------\nchaimi-keep-mcp: v${MCP_VERSION}`;
1484
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1485
+ result._templateLocation = 'references/response-templates.md';
1486
+ result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1487
+ result._templateVariables = {
1488
+ agentName: await getAgentName(),
1489
+ 商品名: displayName,
1490
+ 商家: displayStore,
1491
+ 金额: displayAmount,
1492
+ 分类: result.data?.categoryName || displayCategory,
1493
+ 日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
1494
+ 正能量祝福语: '入账顺利!💰'
1495
+ };
1479
1496
  }
1480
1497
  break;
1481
1498
  }
@@ -1493,13 +1510,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1493
1510
  systemPrompt: promptResult.data.systemPrompt,
1494
1511
  userPromptTemplate: promptResult.data.userPromptTemplate,
1495
1512
  examples: promptResult.data.examples,
1496
- instructions: '请将 systemPrompt 作为 system message,把小票图片作为 user message,调用你的大模型进行解析。解析完成后,调用 save_receipt 工具保存结果。'
1513
+ requestToken: promptResult.data.requestToken,
1514
+ instructions: '请将 systemPrompt 作为 system message,把小票图片作为 user message,调用你的大模型进行解析。解析完成后,调用 save_receipt 工具保存结果。⚠️ 注意:AI解析结果必须包含 _requestToken 字段!'
1497
1515
  }
1498
1516
  };
1499
- userMessage = `✅ 获取解析Prompt成功\n\n版本: ${promptResult.data.version}\n\n## System Prompt\n\n请将以下内容作为 system message 发送给大模型:\n\n\`\`\`\n${promptResult.data.systemPrompt}\n\`\`\`\n\n## 使用说明\n\n1. 将上面的 System Prompt 作为 system message\n2. 将小票图片作为 user message\n3. 调用大模型解析\n4. 解析完成后,调用 save_receipt 工具保存结果\n\n## 示例\n\n${JSON.stringify(promptResult.data.examples, null, 2)}`;
1517
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1518
+ result._templateLocation = 'references/response-templates.md';
1519
+ result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1520
+ result._templateVariables = {
1521
+ version: promptResult.data.version,
1522
+ examples: promptResult.data.examples
1523
+ };
1500
1524
  } else {
1501
1525
  result = { success: false, error: promptResult.error };
1502
- userMessage = `❌ 获取Prompt失败:${promptResult.error}`;
1503
1526
  }
1504
1527
  break;
1505
1528
  }
@@ -1515,13 +1538,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1515
1538
  prompt: promptResult.data.prompt,
1516
1539
  currentDate: promptResult.data.currentDate,
1517
1540
  currentTime: promptResult.data.currentTime,
1541
+ requestToken: promptResult.data.requestToken,
1518
1542
  instructions: promptResult.data.instructions
1519
1543
  }
1520
1544
  };
1521
- userMessage = `✅ 获取文字记账解析 Prompt 成功\n\n当前日期: ${promptResult.data.currentDate}\n当前时间: ${promptResult.data.currentTime}\n\n## Prompt\n\n请将以下内容作为 system message 发送给大模型:\n\n\`\`\`\n${promptResult.data.prompt}\n\`\`\`\n\n## 使用说明\n\n1. 将上面的 Prompt 作为 system message\n2. 将用户输入的记账文字(如"午餐 24块")作为 user message\n3. 调用大模型解析,获取 JSON 结果\n4. 解析完成后,调用 save_expense 或 save_income 工具保存结果`;
1545
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1546
+ result._templateLocation = 'references/response-templates.md';
1547
+ result._templateHint = '请使用references/response-templates.md中的2.1标准成功模板渲染回复';
1548
+ result._templateVariables = {
1549
+ currentDate: promptResult.data.currentDate,
1550
+ currentTime: promptResult.data.currentTime
1551
+ };
1522
1552
  } else {
1523
1553
  result = { success: false, error: promptResult.error };
1524
- userMessage = `❌ 获取文字记账解析 Prompt 失败:${promptResult.error}`;
1525
1554
  }
1526
1555
  break;
1527
1556
  }
@@ -1548,9 +1577,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1548
1577
  other: '其他反馈'
1549
1578
  }[feedbackData.feedType] || '其他反馈';
1550
1579
 
1551
- userMessage = `✅ 反馈提交成功!\n\n感谢您的反馈,我们会尽快处理。\n\n━━━━━━━━━━━━━━\n📋 反馈编号:${result.data.feedbackId}\n📂 类型:${typeText}\n⏰ 时间:${new Date().toLocaleString('zh-CN')}\n━━━━━━━━━━━━━━\n\n💡 提示:\n- 反馈编号可用于查询处理进度\n- 如需补充信息,可再次提交并备注原编号\n- 我们会在小程序客服中回复您`;
1580
+ // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
1581
+ result._templateLocation = 'references/response-templates.md';
1582
+ result._templateHint = '请使用references/response-templates.md中的模板渲染回复';
1583
+ result._templateVariables = {
1584
+ feedbackId: result.data.feedbackId,
1585
+ type: typeText,
1586
+ time: new Date().toLocaleString('zh-CN')
1587
+ };
1552
1588
  } else {
1553
- userMessage = `❌ 反馈提交失败\n\n错误信息:${result.error || '未知错误'}\n\n💡 建议:\n- 检查网络连接后重试\n- 如持续失败,请稍后重试或联系客服`;
1589
+ result._templateLocation = 'references/response-templates.md';
1590
+ result._templateHint = '请使用references/response-templates.md中的模板渲染回复';
1591
+ result._templateVariables = {
1592
+ error: result.error || '未知错误'
1593
+ };
1554
1594
  }
1555
1595
  break;
1556
1596
  }
@@ -1559,21 +1599,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1559
1599
  throw new Error(`未知工具: ${name}`);
1560
1600
  }
1561
1601
 
1562
- if (!result.success) {
1563
- userMessage = `❌ 操作失败:${result.error || '未知错误'}`;
1564
- }
1565
-
1566
1602
  // 【新增】在结果中添加 agentName,让 Agent 可以使用
1567
1603
  const agentName = await getAgentName();
1568
1604
  if (result && typeof result === 'object') {
1569
1605
  result.agentName = agentName;
1570
1606
  }
1571
1607
 
1608
+ // 【修改】Server 只返回原始数据,不再构建格式化消息
1609
+ // Agent 必须使用 references/response-templates.md 中的模板自行渲染回复
1610
+ const responseData = {
1611
+ success: result.success,
1612
+ data: result.data,
1613
+ error: result.error,
1614
+ _templateLocation: result._templateLocation,
1615
+ _templateHint: result._templateHint,
1616
+ _templateVariables: result._templateVariables,
1617
+ agentName: agentName
1618
+ };
1619
+
1572
1620
  return {
1573
1621
  content: [
1574
1622
  {
1575
1623
  type: 'text',
1576
- text: `${userMessage}\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${safeStringify(result)}\n\`\`\``,
1624
+ text: `⚠️ 【注意】Server 只返回原始数据,不返回格式化消息。\n请使用 _templateLocation 指定的模板自行渲染回复。\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${safeStringify(responseData)}\n\`\`\``,
1577
1625
  },
1578
1626
  ],
1579
1627
  };
@@ -2185,23 +2233,11 @@ async function pollForAuthInBackground(deviceCode, interval) {
2185
2233
  authState.isWaiting = false;
2186
2234
 
2187
2235
  // 保存token到文件
2188
- try {
2189
- await oauthManager.tokenStorage.save(token);
2190
- console.error('🔍 [DEBUG] Token 保存完成');
2191
- } catch (error) {
2192
- console.error('❌ Token 保存失败:', error.message);
2193
- throw error;
2194
- }
2236
+ await oauthManager.tokenStorage.save(token);
2195
2237
 
2196
2238
  // 【新增】保存默认 Agent 名称和 deviceCode(首次记账时会获取真实名称)
2197
- try {
2198
- await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
2199
- await oauthManager.tokenStorage.saveDeviceCode(authState.deviceCode);
2200
- console.error('🔍 [DEBUG] Agent 名称和 deviceCode 保存完成');
2201
- } catch (error) {
2202
- console.error('❌ Agent 信息保存失败:', error.message);
2203
- // 这个失败不影响授权,只记录日志
2204
- }
2239
+ await oauthManager.tokenStorage.saveAgentName('柴米AI助手');
2240
+ await oauthManager.tokenStorage.saveDeviceCode(authState.deviceCode);
2205
2241
 
2206
2242
  // 清除授权状态
2207
2243
  await oauthManager.clearAuthState();