chaimi-keep-mcp 3.3.3-beta.13 → 3.3.3-beta.15

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 (3) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/server.js +37 -28
package/README.md CHANGED
@@ -89,6 +89,12 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
89
89
 
90
90
  ## Changelog
91
91
 
92
+ ### v3.3.3-beta.13 (2026-04-30)
93
+ - **变更** 多端记账分类统一 - 支持商品分类和记账分类分离
94
+ - 保持 `category`(记账分类,43个标准分类)
95
+ - 新增 `productCategory`(商品一级分类,展示用)
96
+ - 新增 `productSubCategory`(商品子分类,展示用)
97
+
92
98
  ### v3.3.3-beta.8 (2026-04-30)
93
99
  - **变更** 配置存储路径迁移 - 从 `~/.mcporter/` 迁移到 `~/.chaimi-keep/`,避免与其他 MCP 工具冲突
94
100
  - **优化** 安全权限 - 新增目录权限控制(0700),敏感文件权限控制(0600)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.3.3-beta.13",
3
+ "version": "3.3.3-beta.15",
4
4
  "description": "柴米AI记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -265,8 +265,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
265
265
  properties: {
266
266
  name: { type: 'string', description: '商品名称(必填)' },
267
267
  amount: { type: 'number', description: '金额(必填)' },
268
- category: { type: 'string', description: '【必填】分类,如:餐饮、食品、交通' },
269
- subCategory: { type: 'string', description: '子分类(可选)' },
268
+ category: { type: 'string', description: '【必填】支出分类(43个标准分类之一,如:餐饮、食品、交通、蔬菜)' },
270
269
  unit: { type: 'string', description: '单位(可选)' },
271
270
  marketPrice: { type: 'string', description: '市场单价(可选)' },
272
271
  store: { type: 'string', description: '商家名称(可选)' },
@@ -314,10 +313,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
314
313
  amount: { type: 'number', description: '【必填】金额,必须等于 price × quantity' },
315
314
  weight: { type: 'string', description: '重量' },
316
315
  marketPrice: { type: 'string', description: '【必填】单价数值,根据unit计算,如unit=元/500g,则marketPrice=金额÷重量(g)×500' },
317
- category: { type: 'string', description: '【必填】主分类(如:蔬菜、肉类、水果、水产,常见超市购物分类)' },
318
- subCategory: { type: 'string', description: '子分类(如:叶菜类、根茎类、猪肉类,超市细分类目),推荐填写' },
316
+ // 【改造】商品分类字段
317
+ productCategory: { type: 'string', description: '【必填】商品一级分类(如:蔬菜、肉类、水果、日化用品)' },
318
+ productSubCategory: { type: 'string', description: '商品二级分类(如:叶菜类、根茎类、家居清洁)' },
319
+ // 【改造】记账分类字段
320
+ category: { type: 'string', description: '【必填】记账分类(43个标准分类之一,如:蔬菜、购物、餐饮)' },
319
321
  },
320
- required: ['name', 'amount', 'price', 'quantity', 'unit', 'marketPrice', 'category'],
322
+ required: ['name', 'amount', 'price', 'quantity', 'unit', 'marketPrice', 'productCategory', 'category'],
321
323
  },
322
324
  },
323
325
  agentType: { type: 'string', description: '【必填】Agent类型:claude-desktop、cursor、openclaw、workbuddy、trae' },
@@ -340,7 +342,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
340
342
  startDate: { type: 'string', description: '开始日期(ISO格式,如:2026-04-01)' },
341
343
  endDate: { type: 'string', description: '结束日期(ISO格式,如:2026-04-30)' },
342
344
  category: { type: 'string', description: '按分类筛选(如:餐饮、食品、交通)' },
343
- subCategory: { type: 'string', description: '按子分类筛选(如:叶菜类、猪肉类)' },
344
345
  store: { type: 'string', description: '按商家名称筛选' },
345
346
  source: { type: 'string', description: '按来源筛选(如:mcp_txt_expense、mcp_receipt)' },
346
347
  keyword: { type: 'string', description: '按商品名称关键词搜索' },
@@ -411,13 +412,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
411
412
  },
412
413
  {
413
414
  name: 'save_income',
414
- description: '【文本记账-收入】用户提及收入关键词(工资、红包、转账、退款等)时用此工具。前置:必须先调用get_skill获取规范,再调用get_text_parse_prompt获取凭证和解析模板。用法:mcporter call 柴米AI记账.save_income _flowmate="SOLINWANG..." 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元"',
415
416
  inputSchema: {
416
417
  type: 'object',
417
418
  properties: {
418
419
  name: { type: 'string', description: '收入来源(如:工资、奖金、红包)(必填)' },
419
420
  amount: { type: 'number', description: '收入金额(必填)' },
420
- category: { type: 'string', description: '(必填)收入分类(如:工资、奖金、投资)' },
421
+ category: { type: 'string', description: '【必填】收入分类(8个标准分类之一:工资、兼职、奖金、红包、退款、理财、转账、其他)' },
421
422
  store: { type: 'string', description: '付款方(如:公司名称)(可选)' },
422
423
  date: { type: 'number', description: '【必填】收入时间(毫秒级时间戳,13位数字。必须根据实际年月日时分秒计算,不允许复制示例数字)' },
423
424
  note: { type: 'string', description: '备注(可选)' },
@@ -1169,7 +1170,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1169
1170
  break;
1170
1171
  }
1171
1172
 
1172
- // 检查 items 中每个商品的必填字段(包括 category)
1173
+ // 检查 items 中每个商品的必填字段(包括分类字段)
1173
1174
  const invalidItems = [];
1174
1175
  processedArgs.items.forEach((item, index) => {
1175
1176
  const itemMissingFields = [];
@@ -1177,7 +1178,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1177
1178
  if (!item.hasOwnProperty('amount') || item.amount === undefined) itemMissingFields.push('amount');
1178
1179
  if (!item.hasOwnProperty('price') || item.price === undefined) itemMissingFields.push('price');
1179
1180
  if (!item.hasOwnProperty('quantity') || item.quantity === undefined) itemMissingFields.push('quantity');
1180
- 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
+ }
1181
1188
 
1182
1189
  if (itemMissingFields.length > 0) {
1183
1190
  invalidItems.push(`商品${index + 1}(${item.name || '未命名'})缺少字段: ${itemMissingFields.join(', ')}`);
@@ -1187,10 +1194,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1187
1194
  if (invalidItems.length > 0) {
1188
1195
  result = {
1189
1196
  success: false,
1190
- error: `商品数据不完整:${invalidItems.join('; ')}。每个商品必须包含:name, amount, price, quantity, category`,
1197
+ error: `商品数据不完整:${invalidItems.join('; ')}。每个商品必须包含:name, amount, price, quantity, productCategory, category`,
1191
1198
  code: 400
1192
1199
  };
1193
- 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 是记账分类,都是必填项`;
1194
1201
  break;
1195
1202
  }
1196
1203
 
@@ -2110,8 +2117,9 @@ function convertParams(toolName, args) {
2110
2117
  amount: amount,
2111
2118
  price: amount,
2112
2119
  quantity: 1,
2120
+ productCategory: sanitizeString(args.productCategory, 50) || '其他',
2121
+ productSubCategory: sanitizeString(args.productSubCategory, 50) || '',
2113
2122
  category: sanitizeString(args.category, 50) || '其他',
2114
- subCategory: sanitizeString(args.subCategory, 50) || '',
2115
2123
  unit: sanitizeString(args.unit, 20) || '',
2116
2124
  weight: '',
2117
2125
  marketPrice: sanitizeString(args.marketPrice, 50) || '',
@@ -2148,20 +2156,21 @@ function convertParams(toolName, args) {
2148
2156
  const marketPrice = sanitizeString(item.marketPrice, 50) || calculateMarketPrice(amount, weight);
2149
2157
 
2150
2158
  return {
2151
- itemIndex: index,
2152
- name: sanitizeString(item.name, 100),
2153
- originalName: sanitizeString(item.originalName, 200) || sanitizeString(item.name, 100),
2154
- amount: amount,
2155
- category: sanitizeString(item.category, 50) || '其他',
2156
- subCategory: sanitizeString(item.subCategory, 50) || '',
2157
- transactionType: sanitizeString(item.transactionType, 50) || 'expense',
2158
- price: validateAmount(item.price),
2159
- quantity: item.quantity ? String(item.quantity).substring(0, 20) : '1',
2160
- unit: sanitizeString(item.unit, 20),
2161
- weight: weight,
2162
- marketPrice: marketPrice,
2163
- note: sanitizeString(item.note, 500),
2164
- };
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
+ };
2165
2174
  }),
2166
2175
  store: sanitizeString(args.store, 100),
2167
2176
  receiptNo: sanitizeString(args.receiptNo, 50),
@@ -2185,7 +2194,7 @@ function convertParams(toolName, args) {
2185
2194
  };
2186
2195
  }
2187
2196
 
2188
- case 'save_receipt': {
2197
+ case 'get_expenses': {
2189
2198
  // 【新增】支持 period 参数
2190
2199
  const periodDates = args.period ? parsePeriod(args.period) : {};
2191
2200
  return {