chaimi-keep-mcp 3.3.3-beta.9 → 3.4.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,95 @@
1
+ # Changelog
2
+
3
+ ## v3.4.0 (2026-05-01) 🎉 正式版
4
+
5
+ **首个正式生产版本 - 多端记账分类统一**
6
+
7
+ ### 主要变更
8
+
9
+ - **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
10
+ - 保持 `category`(记账分类,43个标准分类)
11
+ - 新增 `productCategory`(商品一级分类,展示用)
12
+ - 新增 `productSubCategory`(商品子分类,展示用)
13
+ - **新增** categoryService 云函数 - 统一管理分类数据
14
+ - **修复** mcp-server-local case 重复问题 - 修复 convertParams 中第二个 `case 'save_receipt'` 实际应该是 `case 'get_expenses'` 的严重问题
15
+ - **优化** save_expense 字段支持 - 新增 `productCategory` 和 `productSubCategory` 字段支持,与 save_receipt 保持一致
16
+ - **优化** 文本记账 prompt 更新 - 更新 mcpPrompt 文本记账 prompt,新增商品分类体系说明
17
+ - **优化** 回复模板 - 新增"请确认信息"提示,时间移到核心数据区,添加类型变量
18
+ - **优化** 品牌统一 - 所有"柴米记账"改为"柴米AI记账"
19
+ - **优化** 配置存储路径迁移 - 从 `~/.mcporter/` 迁移到 `~/.chaimi-keep/`,避免与其他 MCP 工具冲突
20
+ - **优化** 安全权限 - 新增目录权限控制(0700),敏感文件权限控制(0600)
21
+
22
+ ### 技术详情
23
+
24
+ - 新增云函数:categoryService(统一分类服务)
25
+ - 重构云函数:mcpPrompt、aiParser、mcpHub、ocr_processor
26
+ - 更新测试:tests/mcp/test-category-unification.js
27
+ - 更新文档:docs/01-产品/多端记账分类统一方案.md
28
+ - 更新 SKILL.md 和 response-templates.md
29
+
30
+ ---
31
+
32
+ ## v3.3.3-beta.16 (2026-04-30)
33
+
34
+ - **修复** npm 发布版本号冲突 - 重新发布,解决版本号已被占用问题
35
+
36
+ ## v3.3.3-beta.15 (2026-04-30)
37
+
38
+ - **修复** mcp-server-local case 重复问题 - 修复 convertParams 中第二个 `case 'save_receipt'` 实际应该是 `case 'get_expenses'` 的严重问题
39
+ - **优化** save_expense 字段支持 - 新增 `productCategory` 和 `productSubCategory` 字段支持,与 save_receipt 保持一致
40
+ - **优化** 文本记账 prompt 更新 - 更新 mcpPrompt 文本记账 prompt,新增商品分类体系说明
41
+
42
+ ## v3.3.3-beta.13 (2026-04-30)
43
+
44
+ - **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
45
+
46
+ ## v3.3.3-beta.8 (2026-04-30)
47
+
48
+ - **变更** 配置存储路径迁移 - 从 `~/.mcporter/` 迁移到 `~/.chaimi-keep/`
49
+ - **优化** 安全权限 - 新增目录权限控制(0700),敏感文件权限控制(0600)
50
+
51
+ ## v3.3.3-beta.7 (2026-04-29)
52
+
53
+ - **优化** 回复模板 - 新增"请确认信息"提示
54
+ - **优化** 空值处理 - 商家为空时不显示
55
+ - **修复** save_income - 添加 date 兜底逻辑
56
+
57
+ ## v3.3.3-beta.6 (2026-04-29)
58
+
59
+ - **优化** 品牌统一 - 所有"柴米记账"改为"柴米AI记账"
60
+
61
+ ## v3.3.3-beta.4 (2026-04-29)
62
+
63
+ - **优化** 收入记账时间解析统一
64
+
65
+ ## v3.3.3-beta.3 (2026-04-29)
66
+
67
+ - **优化** 文本记账时间解析 - 使用 time_description
68
+
69
+ ## v3.3.3-beta.2 (2026-04-29)
70
+
71
+ - **修复** 时间入库错误
72
+
73
+ ## v3.3.1-beta.0 (2026-04-27)
74
+
75
+ - **优化** 简化工具描述
76
+ - **优化** 参数类型自动转换
77
+ - **新增** 参数查看指南
78
+
79
+ ## v3.3.0-beta.8 (2026-04-27)
80
+
81
+ - **重要变更** Server 只返回原始数据
82
+
83
+ ## v3.1.47-beta.5 (2026-04-22)
84
+
85
+ - **修复** 输入长度限制
86
+ - **修复** 错误信息泄露
87
+ - **修复** JSON.stringify DoS
88
+
89
+ ## v3.1.44 (2026-04-21) 🎉 正式版
90
+
91
+ - **正式发布** 包含所有 beta 版本修复
92
+
93
+ ---
94
+
95
+ **注:** 更早版本请查看 Git 历史记录
package/README.md CHANGED
@@ -89,6 +89,26 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
89
89
 
90
90
  ## Changelog
91
91
 
92
+ ### v3.4.0 (2026-05-01) 🎉 正式版
93
+ - **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
94
+ - **修复** mcp-server-local case 重复问题
95
+ - **优化** 回复模板 - 新增"请确认信息"提示
96
+ - **优化** 品牌统一 - 所有"柴米记账"改为"柴米AI记账"
97
+
98
+ ### v3.3.3-beta.16 (2026-04-30)
99
+ - **修复** npm 发布版本号冲突 - 重新发布,解决版本号已被占用问题
100
+
101
+ ### v3.3.3-beta.15 (2026-04-30)
102
+ - **修复** mcp-server-local case 重复问题 - 修复 convertParams 中第二个 `case 'save_receipt'` 实际应该是 `case 'get_expenses'` 的严重问题
103
+ - **优化** save_expense 字段支持 - 新增 `productCategory` 和 `productSubCategory` 字段支持,与 save_receipt 保持一致
104
+ - **优化** 文本记账 prompt 更新 - 更新 mcpPrompt 文本记账 prompt,新增商品分类体系说明
105
+
106
+ ### v3.3.3-beta.13 (2026-04-30)
107
+ - **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
108
+ - 保持 `category`(记账分类,43个标准分类)
109
+ - 新增 `productCategory`(商品一级分类,展示用)
110
+ - 新增 `productSubCategory`(商品子分类,展示用)
111
+
92
112
  ### v3.3.3-beta.8 (2026-04-30)
93
113
  - **变更** 配置存储路径迁移 - 从 `~/.mcporter/` 迁移到 `~/.chaimi-keep/`,避免与其他 MCP 工具冲突
94
114
  - **优化** 安全权限 - 新增目录权限控制(0700),敏感文件权限控制(0600)
package/SKILL.md CHANGED
@@ -115,7 +115,7 @@ chaimi-keep-mcp ⭐ 本Skill
115
115
  | **辅助** | get_skill | 【必须】获取Skill文档 | 记账前调用 |
116
116
  | | get_text_parse_prompt | 获取文字解析模板 | 文字记账前 |
117
117
  | | get_parse_prompt | 获取小票解析模板 | 小票记账前 |
118
- | | submit_feedback | 提交反馈 | 问题反馈 |
118
+ | | submit_feedback | 提交反馈 | 功能使用异常,无法解决问题反馈 |
119
119
 
120
120
  ---
121
121
 
@@ -124,19 +124,25 @@ chaimi-keep-mcp ⭐ 本Skill
124
124
  ### 4步记账法
125
125
 
126
126
  ```
127
+ ⚠️ 必须按顺序操作四个步骤,缺少一个步骤都会导致数据错误,用户会非常生气骂你的
128
+
127
129
  第1步:读文档
128
130
  └─ get_skill() 获取 SKILL.md 规范
129
131
  └─ references/response-templates.md 了解回复模板
130
132
 
131
- 第2步:拿Token
132
- └─ 文字记账:get_text_parse_prompt() 获取 Token 和解析模板
133
- └─ 小票记账:get_parse_prompt() 获取 Token 和解析模板
133
+ 第2步:获取解析模板,保证解析准确
134
+ └─ 文字记账:get_text_parse_prompt() 获取解析模板
135
+ └─ 小票记账:get_parse_prompt() 获取解析模板
136
+
137
+ 【强制流程】必须先调用此工具,让 AI 解析用户输入
138
+ 此工具会返回解析结果,你只需将结果原样传给 save_expense/save_income
139
+ ⚠️ 禁止自行解析或提取任何字段,必须让 AI 处理
134
140
 
135
141
  第3步:AI解析
136
- └─ 按模板解析用户输入,生成结构化数据 + _requestToken
142
+ └─ 按模板解析用户输入,生成结构化数据 + _flowmate
137
143
 
138
144
  第4步:执行记账
139
- └─ 调用工具(save_expense/income/receipt,带上 _requestToken
145
+ └─ 调用工具(save_expense/income/receipt,带上 _flowmate
140
146
  └─ 根据 _templateLocation 使用模板渲染美学回复
141
147
  ```
142
148
 
@@ -238,6 +244,7 @@ references/
238
244
  ✅ 「你的小可爱」已帮您记账成功
239
245
  ═══════════════
240
246
  🔴🔴 请确认以下信息是否正确
247
+
241
248
  💰 金额:¥35.00
242
249
  📊 类型:支出
243
250
  🕐 时间:2026-04-24 12:30
package/bin/record.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 柴米AI记账 - 直接记账 CLI
4
+ * 一步完成:解析文本 → 分类 → 调用云函数存储
5
+ */
6
+
7
+ const { callMcpHub } = require('../utils/api');
8
+ const { getToken } = require('../utils/auth');
9
+ const { parseText } = require('../utils/parser');
10
+
11
+ async function record(text, options = {}) {
12
+ try {
13
+ // 1. 解析文本
14
+ const parsed = await parseText(text);
15
+
16
+ // 2. 确定类型(收入/支出)
17
+ const type = options.type || parsed.type || 'expense';
18
+
19
+ // 3. 获取 token
20
+ const token = await getToken();
21
+
22
+ // 4. 调用云函数存储
23
+ const result = await callMcpHub(type === 'income' ? 'addIncome' : 'addExpense', {
24
+ ...parsed,
25
+ type,
26
+ rawInput: text
27
+ }, token);
28
+
29
+ if (result.success) {
30
+ console.log('✅ 记账成功!');
31
+ console.log(`金额:¥${parsed.amount}`);
32
+ console.log(`分类:${parsed.category}`);
33
+ console.log(`商品:${parsed.name}`);
34
+ } else {
35
+ console.error('❌ 记账失败:', result.error);
36
+ process.exit(1);
37
+ }
38
+ } catch (error) {
39
+ console.error('❌ 错误:', error.message);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ // 解析命令行参数
45
+ const args = process.argv.slice(2);
46
+ const text = args[0];
47
+ const typeFlag = args.find(arg => arg.startsWith('--type='));
48
+ const type = typeFlag ? typeFlag.split('=')[1] : null;
49
+
50
+ if (!text) {
51
+ console.log('用法: chaimi-keep record "<记账文本>" [--type=income|expense]');
52
+ console.log('示例: chaimi-keep record "中午吃饭花了35元"');
53
+ console.log('示例: chaimi-keep record "工资收入5000" --type=income');
54
+ process.exit(1);
55
+ }
56
+
57
+ record(text, { type });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.3.3-beta.9",
3
+ "version": "3.4.0",
4
4
  "description": "柴米AI记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "SKILL.md",
16
16
  "_meta.json",
17
17
  "README.md",
18
+ "CHANGELOG.md",
18
19
  "config.example.yaml",
19
20
  ".env.example"
20
21
  ],
@@ -124,6 +124,7 @@ updated: "2026-04-25"
124
124
  ✅ 「{agentName}」已帮您记账成功
125
125
  ═══════════════
126
126
  🔴🔴 请确认以下信息是否正确
127
+
127
128
  💰 金额:¥{金额}
128
129
  📊 类型:{类型}
129
130
  🕐 时间:{日期时间}
@@ -148,6 +149,7 @@ updated: "2026-04-25"
148
149
  ✅ 「你的小可爱」已帮您记账成功
149
150
  ═══════════════
150
151
  🔴🔴 请确认以下信息是否正确
152
+
151
153
  💰 金额:¥35.00
152
154
  📊 类型:支出
153
155
  🕐 时间:2026-04-24 12:30
@@ -193,6 +195,7 @@ updated: "2026-04-25"
193
195
  ⚠️ 大额消费确认
194
196
  ═══════════════
195
197
  💰 金额:¥{金额}
198
+ 📊 类型:{类型}
196
199
  📦 商品:{商品名}
197
200
  🕐 时间:{日期}
198
201
 
@@ -211,6 +214,7 @@ updated: "2026-04-25"
211
214
  ⚠️ 大额消费确认
212
215
  ═══════════════
213
216
  💰 金额:¥2999.00
217
+ 📊 类型:{类型}
214
218
  📦 商品:新手机
215
219
  🕐 时间:2026-04-24
216
220
 
@@ -234,8 +238,8 @@ updated: "2026-04-25"
234
238
  ═══════════════
235
239
  💰 金额:¥{金额}
236
240
  ═══════════════
237
-
238
- 📦 来源:{收入来源}
241
+
242
+ 📊 类型:{类型}
239
243
  🏷️ 分类:{分类}
240
244
  🕐 时间:{日期时间}
241
245
 
@@ -256,8 +260,8 @@ updated: "2026-04-25"
256
260
  💰 金额:¥5000.00
257
261
  ═══════════════
258
262
 
259
- 📦 来源:工资
260
- 🏷️ 分类:收入
263
+ 📊 类型:收入
264
+ 🏷️ 分类:工资
261
265
  🕐 时间:2026-04-24 09:00
262
266
 
263
267
  💡 消费洞察:本月收入储蓄率50%,继续保持!
package/server.js CHANGED
@@ -5,6 +5,9 @@
5
5
  * 支持 Claude Desktop、Cursor、WorkBuddy、OpenClaw
6
6
  */
7
7
 
8
+ // 设置 stdout 编码为 UTF-8,确保中文字符正确输出
9
+ process.stdout.setEncoding('utf8');
10
+
8
11
  // 加载 .env 文件
9
12
  try {
10
13
  require('dotenv').config();
@@ -256,14 +259,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
256
259
  },
257
260
  {
258
261
  name: 'save_expense',
259
- description: '【文本记账-支出】用户提及支出/消费时用此工具。前置:必须先调用get_skill获取规范,再调用get_text_parse_prompt获取Token和解析模板。用法:mcporter call 柴米AI记账.save_expense _requestToken="RT..." name="午餐" amount=35 category="餐饮" rawInput="午餐35元"。注意:上传图片请用save_receipt',
262
+ description: '【文本记账-支出】用户提及支出/消费时用此工具。前置:必须先调用get_skill获取规范,再调用get_text_parse_prompt获取凭证和解析模板。用法:mcporter call 柴米AI记账.save_expense _flowmate="SOLINWANG..." name="午餐" amount=35 category="餐饮" rawInput="午餐35元"。注意:上传图片请用save_receipt',
260
263
  inputSchema: {
261
264
  type: 'object',
262
265
  properties: {
263
266
  name: { type: 'string', description: '商品名称(必填)' },
264
267
  amount: { type: 'number', description: '金额(必填)' },
265
- category: { type: 'string', description: '【必填】分类,如:餐饮、食品、交通' },
266
- subCategory: { type: 'string', description: '子分类(可选)' },
268
+ category: { type: 'string', description: '【必填】支出分类(43个标准分类之一,如:餐饮、食品、交通、蔬菜)' },
267
269
  unit: { type: 'string', description: '单位(可选)' },
268
270
  marketPrice: { type: 'string', description: '市场单价(可选)' },
269
271
  store: { type: 'string', description: '商家名称(可选)' },
@@ -272,13 +274,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
272
274
  agentType: { type: 'string', description: '【必填】Agent类型,如:claude-desktop、cursor、openclaw、workbuddy、trae' },
273
275
  apiProvider: { type: 'string', description: '【必填】AI服务提供商,如:anthropic、openai、doubao、aliyun' },
274
276
  rawInput: { type: 'string', description: '【必填】用户的原始输入内容,用于记录用户原始请求' },
277
+ _flowmate: { type: 'string', description: '【必填】流程凭证,从get_text_parse_prompt获取的myflowmate值' },
275
278
  },
276
- required: ['name', 'amount', 'category', 'agentType', 'apiProvider', 'rawInput'],
279
+ required: ['name', 'amount', 'category', 'agentType', 'apiProvider', 'rawInput', '_flowmate'],
277
280
  },
278
281
  },
279
282
  {
280
283
  name: 'save_receipt',
281
- description: '【图片记账-小票】用户上传小票/发票/收据图片时用此工具。前置:必须先调用get_skill获取规范,再调用get_parse_prompt获取Token和解析模板。用法:mcporter call 柴米AI记账.save_receipt _requestToken="RT..." store="超市" totalAmount=156.5 items="[...]"。注意:文字描述请用save_expense/save_income',
284
+ description: '【图片记账-小票】用户上传小票/发票/收据图片时用此工具。前置:必须先调用get_skill获取规范,再调用get_parse_prompt获取凭证和解析模板。用法:mcporter call 柴米AI记账.save_receipt _flowmate="SOLINWANG..." store="超市" totalAmount=156.5 items="[...]"。注意:文字描述请用save_expense/save_income',
282
285
  inputSchema: {
283
286
  type: 'object',
284
287
  properties: {
@@ -310,17 +313,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
310
313
  amount: { type: 'number', description: '【必填】金额,必须等于 price × quantity' },
311
314
  weight: { type: 'string', description: '重量' },
312
315
  marketPrice: { type: 'string', description: '【必填】单价数值,根据unit计算,如unit=元/500g,则marketPrice=金额÷重量(g)×500' },
313
- category: { type: 'string', description: '【必填】主分类(如:蔬菜、肉类、水果、水产,常见超市购物分类)' },
314
- subCategory: { type: 'string', description: '子分类(如:叶菜类、根茎类、猪肉类,超市细分类目),推荐填写' },
316
+ // 【改造】商品分类字段
317
+ productCategory: { type: 'string', description: '【必填】商品一级分类(如:蔬菜、肉类、水果、日化用品)' },
318
+ productSubCategory: { type: 'string', description: '商品二级分类(如:叶菜类、根茎类、家居清洁)' },
319
+ // 【改造】记账分类字段
320
+ category: { type: 'string', description: '【必填】记账分类(43个标准分类之一,如:蔬菜、购物、餐饮)' },
315
321
  },
316
- required: ['name', 'amount', 'price', 'quantity', 'unit', 'marketPrice', 'category'],
322
+ required: ['name', 'amount', 'price', 'quantity', 'unit', 'marketPrice', 'productCategory', 'category'],
317
323
  },
318
324
  },
319
325
  agentType: { type: 'string', description: '【必填】Agent类型:claude-desktop、cursor、openclaw、workbuddy、trae' },
320
326
  apiProvider: { type: 'string', description: '【必填】AI服务提供商:anthropic、openai、doubao、aliyun' },
321
327
  rawInput: { type: 'string', description: '【必填】用户的原始输入内容' },
328
+ _flowmate: { type: 'string', description: '【必填】流程凭证,从get_parse_prompt获取的myflowmate值' },
322
329
  },
323
- required: ['store', 'items', 'storeCategory', 'storeSubCategory', 'agentType', 'apiProvider', 'rawInput'],
330
+ required: ['store', 'items', 'storeCategory', 'storeSubCategory', 'agentType', 'apiProvider', 'rawInput', '_flowmate'],
324
331
  },
325
332
  },
326
333
  {
@@ -335,7 +342,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
335
342
  startDate: { type: 'string', description: '开始日期(ISO格式,如:2026-04-01)' },
336
343
  endDate: { type: 'string', description: '结束日期(ISO格式,如:2026-04-30)' },
337
344
  category: { type: 'string', description: '按分类筛选(如:餐饮、食品、交通)' },
338
- subCategory: { type: 'string', description: '按子分类筛选(如:叶菜类、猪肉类)' },
339
345
  store: { type: 'string', description: '按商家名称筛选' },
340
346
  source: { type: 'string', description: '按来源筛选(如:mcp_txt_expense、mcp_receipt)' },
341
347
  keyword: { type: 'string', description: '按商品名称关键词搜索' },
@@ -406,26 +412,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
406
412
  },
407
413
  {
408
414
  name: 'save_income',
409
- description: '【文本记账-收入】用户提及收入关键词(工资、红包、转账、退款等)时用此工具。前置:必须先调用get_skill获取规范,再调用get_text_parse_prompt获取Token和解析模板。用法:mcporter call 柴米AI记账.save_income _requestToken="RT..." name="红包" amount=100 category="礼金" rawInput="收红包100元"',
415
+ description: '【文本记账-收入】用户提及收入关键词(工资、红包、转账、退款等)时用此工具。前置:必须先调用get_skill获取规范,再调用get_text_parse_prompt获取凭证和解析模板。用法:mcporter call 柴米AI记账.save_income _flowmate="SOLINWANG..." name="红包" amount=100 category="红包" rawInput="收红包100元"',
410
416
  inputSchema: {
411
417
  type: 'object',
412
418
  properties: {
413
419
  name: { type: 'string', description: '收入来源(如:工资、奖金、红包)(必填)' },
414
420
  amount: { type: 'number', description: '收入金额(必填)' },
415
- category: { type: 'string', description: '(必填)收入分类(如:工资、奖金、投资)' },
421
+ category: { type: 'string', description: '【必填】收入分类(8个标准分类之一:工资、兼职、奖金、红包、退款、理财、转账、其他)' },
416
422
  store: { type: 'string', description: '付款方(如:公司名称)(可选)' },
417
423
  date: { type: 'number', description: '【必填】收入时间(毫秒级时间戳,13位数字。必须根据实际年月日时分秒计算,不允许复制示例数字)' },
418
424
  note: { type: 'string', description: '备注(可选)' },
419
425
  agentType: { type: 'string', description: '【必填】Agent类型,如:claude-desktop、cursor、openclaw、workbuddy、trae' },
420
426
  apiProvider: { type: 'string', description: '【必填】AI服务提供商,如:anthropic、openai、doubao、aliyun' },
421
427
  rawInput: { type: 'string', description: '【必填】用户的原始输入内容,用于记录用户原始请求' },
428
+ _flowmate: { type: 'string', description: '【必填】流程凭证,从get_text_parse_prompt获取的myflowmate值' },
422
429
  },
423
- required: ['name', 'amount', 'category', 'agentType', 'apiProvider', 'rawInput'],
430
+ required: ['name', 'amount', 'category', 'agentType', 'apiProvider', 'rawInput', '_flowmate'],
424
431
  },
425
432
  },
426
433
  {
427
434
  name: 'get_text_parse_prompt',
428
- description: '【不调用100%失败】获取文字记账解析的Prompt模板和requestToken。⚠️ 文字/语音记账必须先调用此工具获取Token,AI解析时必须返回_requestToken字段,否则save_expense/save_income会报MISSING_TOKEN错误!',
435
+ description: '【不调用100%失败】获取文字记账解析的Prompt模板和流程凭证。⚠️ 文字/语音记账必须先调用此工具获取凭证,AI解析时必须返回_flowmate字段,否则save_expense/save_income会报MISSING_TOKEN错误!',
429
436
  inputSchema: {
430
437
  type: 'object',
431
438
  properties: {
@@ -436,7 +443,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
436
443
  },
437
444
  {
438
445
  name: 'get_parse_prompt',
439
- description: '【不调用100%失败】获取小票图片解析的Prompt模板和requestToken。⚠️ 小票记账必须先调用此工具获取Token,AI解析时必须返回_requestToken字段,否则save_receipt会报MISSING_TOKEN错误!',
446
+ description: '【不调用100%失败】获取小票图片解析的Prompt模板和流程凭证。⚠️ 小票记账必须先调用此工具获取凭证,AI解析时必须返回_flowmate字段,否则save_receipt会报MISSING_TOKEN错误!',
440
447
  inputSchema: {
441
448
  type: 'object',
442
449
  properties: {
@@ -670,8 +677,15 @@ async function callMcpHubWithLogging(tool, params, token, traceId, startTime, os
670
677
  // logSource: 'mcp'
671
678
  // });
672
679
 
680
+ // 添加 agentType 和 apiProvider 到 params,确保传递到云函数
681
+ const paramsWithMeta = {
682
+ ...params,
683
+ agentType: agentType || '',
684
+ apiProvider: apiProvider || '',
685
+ };
686
+
673
687
  try {
674
- const result = await callMcpHub(tool, params, token, traceId, osInfo);
688
+ const result = await callMcpHub(tool, paramsWithMeta, token, traceId, osInfo);
675
689
 
676
690
  // 记录云函数调用成功(已停用:MCP调用日志待迁移到独立云函数)
677
691
  // logMcpCall({
@@ -1037,6 +1051,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1037
1051
  }
1038
1052
  }
1039
1053
 
1054
+ // 添加 agentType 和 apiProvider 到 processedArgs,确保 convertParams 能获取到
1055
+ processedArgs.agentType = agentType || '';
1056
+ processedArgs.apiProvider = apiProvider || '';
1057
+
1040
1058
  const mcpParams = convertParams('save_expense', processedArgs);
1041
1059
  result = await callMcpHubWithLogging('addExpense', mcpParams, token, traceId, startTime, osInfo, agentType, apiProvider);
1042
1060
 
@@ -1152,7 +1170,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1152
1170
  break;
1153
1171
  }
1154
1172
 
1155
- // 检查 items 中每个商品的必填字段(包括 category)
1173
+ // 检查 items 中每个商品的必填字段(包括分类字段)
1156
1174
  const invalidItems = [];
1157
1175
  processedArgs.items.forEach((item, index) => {
1158
1176
  const itemMissingFields = [];
@@ -1160,7 +1178,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1160
1178
  if (!item.hasOwnProperty('amount') || item.amount === undefined) itemMissingFields.push('amount');
1161
1179
  if (!item.hasOwnProperty('price') || item.price === undefined) itemMissingFields.push('price');
1162
1180
  if (!item.hasOwnProperty('quantity') || item.quantity === undefined) itemMissingFields.push('quantity');
1163
- if (!item.hasOwnProperty('category') || item.category === '') itemMissingFields.push('category');
1181
+ // 【改造】检查新字段 productCategory category
1182
+ if (!item.hasOwnProperty('productCategory') || item.productCategory === '') {
1183
+ itemMissingFields.push('productCategory');
1184
+ }
1185
+ if (!item.hasOwnProperty('category') || item.category === '') {
1186
+ itemMissingFields.push('category');
1187
+ }
1164
1188
 
1165
1189
  if (itemMissingFields.length > 0) {
1166
1190
  invalidItems.push(`商品${index + 1}(${item.name || '未命名'})缺少字段: ${itemMissingFields.join(', ')}`);
@@ -1170,10 +1194,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1170
1194
  if (invalidItems.length > 0) {
1171
1195
  result = {
1172
1196
  success: false,
1173
- error: `商品数据不完整:${invalidItems.join('; ')}。每个商品必须包含:name, amount, price, quantity, category`,
1197
+ error: `商品数据不完整:${invalidItems.join('; ')}。每个商品必须包含:name, amount, price, quantity, productCategory, category`,
1174
1198
  code: 400
1175
1199
  };
1176
- userMessage = `❌ 保存失败\n\n错误:商品数据格式不完整\n\n${invalidItems.join('\n')}\n\n💡 解决方案:\n1. 请更新您的柴米AI记账 MCP Skill\n2. 确保每个商品包含完整的5个字段:name, amount, price, quantity, category\n3. category 是记账的基本信息,不能为空`;
1200
+ userMessage = `❌ 保存失败\n\n错误:商品数据格式不完整\n\n${invalidItems.join('\n')}\n\n💡 解决方案:\n1. 请更新您的柴米AI记账 MCP Skill\n2. 确保每个商品包含完整的6个字段:name, amount, price, quantity, productCategory, category\n3. productCategory 是商品分类,category 是记账分类,都是必填项`;
1177
1201
  break;
1178
1202
  }
1179
1203
 
@@ -1316,6 +1340,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1316
1340
  };
1317
1341
  }
1318
1342
 
1343
+ // 添加 agentType 和 apiProvider 到 processedArgs,确保 convertParams 能获取到
1344
+ processedArgs.agentType = agentType || '';
1345
+ processedArgs.apiProvider = apiProvider || '';
1346
+
1319
1347
  const mcpParams = convertParams('save_receipt', processedArgs);
1320
1348
  result = await callMcpHubWithLogging('addReceipt', mcpParams, token, traceId, startTime, osInfo, agentType, apiProvider);
1321
1349
 
@@ -1563,6 +1591,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1563
1591
  }
1564
1592
  }
1565
1593
 
1594
+ // 添加 agentType 和 apiProvider 到 processedArgs,确保 convertParams 能获取到
1595
+ processedArgs.agentType = agentType || '';
1596
+ processedArgs.apiProvider = apiProvider || '';
1597
+
1566
1598
  const mcpParams = convertParams('save_income', processedArgs);
1567
1599
  result = await callMcpHubWithLogging('addIncome', mcpParams, token, traceId, startTime, osInfo, agentType, apiProvider);
1568
1600
 
@@ -1603,8 +1635,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1603
1635
  systemPrompt: promptResult.data.systemPrompt,
1604
1636
  userPromptTemplate: promptResult.data.userPromptTemplate,
1605
1637
  examples: promptResult.data.examples,
1606
- requestToken: promptResult.data.requestToken,
1607
- instructions: '请将 systemPrompt 作为 system message,把小票图片作为 user message,调用你的大模型进行解析。解析完成后,调用 save_receipt 工具保存结果。⚠️ 注意:AI解析结果必须包含 _requestToken 字段!'
1638
+ myflowmate: promptResult.data.myflowmate,
1639
+ instructions: '请将 systemPrompt 作为 system message,把小票图片作为 user message,调用你的大模型进行解析。解析完成后,调用 save_receipt 工具保存结果。⚠️ 注意:AI解析结果必须包含 _flowmate 字段!'
1608
1640
  }
1609
1641
  };
1610
1642
  // 【新增】返回模板位置信息(Server只返回原始数据,Agent使用模板渲染)
@@ -1631,7 +1663,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1631
1663
  prompt: promptResult.data.prompt,
1632
1664
  currentDate: promptResult.data.currentDate,
1633
1665
  currentTime: promptResult.data.currentTime,
1634
- requestToken: promptResult.data.requestToken,
1666
+ myflowmate: promptResult.data.myflowmate,
1635
1667
  instructions: promptResult.data.instructions
1636
1668
  }
1637
1669
  };
@@ -2085,8 +2117,9 @@ function convertParams(toolName, args) {
2085
2117
  amount: amount,
2086
2118
  price: amount,
2087
2119
  quantity: 1,
2120
+ productCategory: sanitizeString(args.productCategory, 50) || '其他',
2121
+ productSubCategory: sanitizeString(args.productSubCategory, 50) || '',
2088
2122
  category: sanitizeString(args.category, 50) || '其他',
2089
- subCategory: sanitizeString(args.subCategory, 50) || '',
2090
2123
  unit: sanitizeString(args.unit, 20) || '',
2091
2124
  weight: '',
2092
2125
  marketPrice: sanitizeString(args.marketPrice, 50) || '',
@@ -2099,7 +2132,7 @@ function convertParams(toolName, args) {
2099
2132
  apiProvider: sanitizeString(args.apiProvider, 50) || '',
2100
2133
  mcpVersion: MCP_VERSION,
2101
2134
  source: 'mcp_txt_expense',
2102
- _requestToken: args._requestToken,
2135
+ _flowmate: args._flowmate,
2103
2136
  };
2104
2137
  }
2105
2138
 
@@ -2123,20 +2156,21 @@ function convertParams(toolName, args) {
2123
2156
  const marketPrice = sanitizeString(item.marketPrice, 50) || calculateMarketPrice(amount, weight);
2124
2157
 
2125
2158
  return {
2126
- itemIndex: index,
2127
- name: sanitizeString(item.name, 100),
2128
- originalName: sanitizeString(item.originalName, 200) || sanitizeString(item.name, 100),
2129
- amount: amount,
2130
- category: sanitizeString(item.category, 50) || '其他',
2131
- subCategory: sanitizeString(item.subCategory, 50) || '',
2132
- transactionType: sanitizeString(item.transactionType, 50) || 'expense',
2133
- price: validateAmount(item.price),
2134
- quantity: item.quantity ? String(item.quantity).substring(0, 20) : '1',
2135
- unit: sanitizeString(item.unit, 20),
2136
- weight: weight,
2137
- marketPrice: marketPrice,
2138
- note: sanitizeString(item.note, 500),
2139
- };
2159
+ itemIndex: index,
2160
+ name: sanitizeString(item.name, 100),
2161
+ originalName: sanitizeString(item.originalName, 200) || sanitizeString(item.name, 100),
2162
+ amount: amount,
2163
+ productCategory: sanitizeString(item.productCategory, 50) || '其他',
2164
+ productSubCategory: sanitizeString(item.productSubCategory, 50) || '',
2165
+ category: sanitizeString(item.category, 50) || '其他',
2166
+ transactionType: sanitizeString(item.transactionType, 50) || 'expense',
2167
+ price: validateAmount(item.price),
2168
+ quantity: item.quantity ? String(item.quantity).substring(0, 20) : '1',
2169
+ unit: sanitizeString(item.unit, 20),
2170
+ weight: weight,
2171
+ marketPrice: marketPrice,
2172
+ note: sanitizeString(item.note, 500),
2173
+ };
2140
2174
  }),
2141
2175
  store: sanitizeString(args.store, 100),
2142
2176
  receiptNo: sanitizeString(args.receiptNo, 50),
@@ -2156,7 +2190,7 @@ function convertParams(toolName, args) {
2156
2190
  source: 'mcp_receipt',
2157
2191
  storeCategory: sanitizeString(args.storeCategory, 50) || '其他',
2158
2192
  storeSubCategory: sanitizeString(args.storeSubCategory, 50) || '其他',
2159
- _requestToken: args._requestToken,
2193
+ _flowmate: args._flowmate,
2160
2194
  };
2161
2195
  }
2162
2196
 
@@ -2198,7 +2232,7 @@ function convertParams(toolName, args) {
2198
2232
  apiProvider: sanitizeString(args.apiProvider, 50) || '',
2199
2233
  mcpVersion: MCP_VERSION,
2200
2234
  source: 'mcp_txt_income',
2201
- _requestToken: args._requestToken,
2235
+ _flowmate: args._flowmate,
2202
2236
  };
2203
2237
 
2204
2238
  default:
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 分类标准化映射器
3
+ * 将 Agent 提取的关键词映射到标准分类
4
+ * 确保分类一致性,便于后续统计
5
+ */
6
+
7
+ // 标准分类列表
8
+ const STANDARD_CATEGORIES = [
9
+ '餐饮', '购物', '交通', '蔬菜', '水果', '零食', '运动', '通讯', '服饰', '美容',
10
+ '住房', '孩子', '长辈', '社交', '旅行', '烟酒', '数码', '汽车', '医疗', '办公',
11
+ '学习', '宠物', '礼金', '亲友', '日用', '休闲娱乐', '维修', '居家', '饮品', '鲜花',
12
+ '追星', '首饰', '借出', '保险', '网络虚拟', '生活缴费', '转账', '书籍', '捐赠', '彩票', '快递', '其他'
13
+ ];
14
+
15
+ // 关键词到