chaimi-bookkeeping-mcp 2.2.6 → 2.2.7

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/bin/cli.js +1 -5
  2. package/package.json +1 -1
  3. package/server.js +57 -50
package/bin/cli.js CHANGED
@@ -10,10 +10,6 @@ const fs = require('fs');
10
10
  const os = require('os');
11
11
  const { spawn } = require('child_process');
12
12
 
13
- // 读取 package.json 获取版本
14
- const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
15
- const CURRENT_VERSION = packageJson.version;
16
-
17
13
  // 获取 server.js 的绝对路径
18
14
  const serverPath = path.join(__dirname, '..', 'server.js');
19
15
 
@@ -187,7 +183,7 @@ function configureAllAgents() {
187
183
  function showWelcome() {
188
184
  console.log('╔════════════════════════════════════════════════════════╗');
189
185
  console.log('║ ║');
190
- console.log(`║ 柴米记账 MCP Server v${CURRENT_VERSION} ║`);
186
+ console.log('║ 柴米记账 MCP Server v2.1.0 ║');
191
187
  console.log('║ ║');
192
188
  console.log('║ 支持: OpenClaw | WorkBuddy | Claude | Cursor ║');
193
189
  console.log('║ ║');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-bookkeeping-mcp",
3
- "version": "2.2.6",
3
+ "version": "2.2.7",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -25,6 +25,8 @@ const fs = require('fs');
25
25
  // 读取 package.json 获取版本
26
26
  const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
27
27
  const CURRENT_VERSION = packageJson.version;
28
+ const MCP_VERSION = packageJson.version;
29
+ console.log('柴米记账 MCP Server 版本:', MCP_VERSION);
28
30
 
29
31
  // 导入 OAuth 模块
30
32
  const { OAuthManager, FileTokenStorage } = require('./oauth.js');
@@ -86,7 +88,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
86
88
  tools: [
87
89
  {
88
90
  name: 'quick_book',
89
- description: '极简快捷记账 - 自动识别支出/收入,智能匹配分类。输入如:"午餐 30 元"、"工资 8000 元"、"木屋烧烤 35 元"',
91
+ description: '【推荐首选】极简快捷记账 - 自动识别支出/收入,智能匹配分类。仅需 name 和 amount 两个参数,其他自动补全。输入如:"午餐 30 元"、"工资 8000 元"、"木屋烧烤 35 元"',
90
92
  inputSchema: {
91
93
  type: 'object',
92
94
  properties: {
@@ -95,13 +97,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
95
97
  category: { type: 'string', description: '分类(可选,系统自动推荐)' },
96
98
  store: { type: 'string', description: '商家名称(可选)' },
97
99
  note: { type: 'string', description: '备注(可选)' },
100
+ agentType: { type: 'string', description: 'Agent 类型(如:workbuddy、claude、cursor)' },
101
+ apiProvider: { type: 'string', description: 'AI 提供商' },
102
+ rawInput: { type: 'string', description: '用户原始输入' },
103
+ mcp_version: { type: 'string', description: 'MCP Server 版本号(自动填充)' },
98
104
  },
99
105
  required: ['name', 'amount'],
100
106
  },
101
107
  },
102
108
  {
103
109
  name: 'save_expense',
104
- description: '保存单商品消费记录(AI 文字记账)',
110
+ description: '保存单商品消费记录(AI文字记账)',
105
111
  inputSchema: {
106
112
  type: 'object',
107
113
  properties: {
@@ -115,6 +121,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
115
121
  agentType: { type: 'string', description: 'Agent 类型(如:workbuddy、claude、cursor)' },
116
122
  apiProvider: { type: 'string', description: 'AI 提供商' },
117
123
  rawInput: { type: 'string', description: '用户原始输入' },
124
+ mcp_version: { type: 'string', description: 'MCP Server 版本号(自动填充)' },
118
125
  },
119
126
  required: ['name', 'amount', 'price', 'quantity'],
120
127
  },
@@ -152,9 +159,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
152
159
  required: ['name', 'amount', 'price', 'quantity'],
153
160
  },
154
161
  },
155
- agentType: { type: 'string', description: 'Agent类型(如:workbuddy、claude、cursor)' },
156
- apiProvider: { type: 'string', description: 'AI提供商' },
162
+ agentType: { type: 'string', description: 'Agent 类型(如:workbuddy、claude、cursor)' },
163
+ apiProvider: { type: 'string', description: 'AI 提供商' },
157
164
  rawInput: { type: 'string', description: '用户原始输入' },
165
+ mcp_version: { type: 'string', description: 'MCP Server 版本号(自动填充)' },
158
166
  },
159
167
  required: ['store', 'items'],
160
168
  },
@@ -203,9 +211,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
203
211
  category: { type: 'string', description: '收入分类(如:工资、奖金、投资)' },
204
212
  store: { type: 'string', description: '付款方(如:公司名称)' },
205
213
  note: { type: 'string', description: '备注' },
206
- agentType: { type: 'string', description: 'Agent类型(如:workbuddy、claude、cursor)' },
207
- apiProvider: { type: 'string', description: 'AI提供商' },
214
+ agentType: { type: 'string', description: 'Agent 类型(如:workbuddy、claude、cursor)' },
215
+ apiProvider: { type: 'string', description: 'AI 提供商' },
208
216
  rawInput: { type: 'string', description: '用户原始输入' },
217
+ mcp_version: { type: 'string', description: 'MCP Server 版本号(自动填充)' },
209
218
  },
210
219
  required: ['name', 'amount'],
211
220
  },
@@ -216,6 +225,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
216
225
 
217
226
  // 工具名称映射:SCF action -> mcpHub tool
218
227
  const toolMapping = {
228
+ 'quick_book': 'addExpense', // quick_book 内部会判断支出/收入
219
229
  'save_expense': 'addExpense',
220
230
  'save_receipt': 'addReceipt',
221
231
  'save_income': 'addIncome',
@@ -238,6 +248,10 @@ async function getToken() {
238
248
  }
239
249
  // 超过2小时,尝试刷新
240
250
  console.log('Token 超过2小时,尝试自动刷新...');
251
+ } else if (cachedToken && tokenExpireTime <= Date.now() + 5 * 60 * 1000) {
252
+ // Token 已过期,添加友好提示
253
+ console.log('🔐 Token 已过期,需要重新授权');
254
+ console.log('请使用 mcporter 调用 柴米记账.save_expense() 开始授权');
241
255
  }
242
256
 
243
257
  // 使用 OAuth 获取有效 Token
@@ -250,14 +264,32 @@ async function getToken() {
250
264
  cachedToken = oauthToken.accessToken;
251
265
  tokenExpireTime = oauthToken.expiresAt;
252
266
  lastRefreshTime = Date.now();
267
+ // 授权成功,清除授权状态
268
+ await oauthManager.clearAuthState();
253
269
  return cachedToken;
254
270
  } catch (err) {
255
- // OAuth Token 无效,尝试重新授权
256
- console.log('OAuth Token 无效,启动授权流程...');
271
+ // OAuth Token 无效,检查是否有未完成的授权
272
+ console.log('🔐 Token 已过期,需要重新授权');
273
+ console.log('请使用 mcporter 调用 柴米记账.save_expense() 开始授权');
274
+ console.log('OAuth Token 无效,检查授权状态...');
275
+
276
+ // 检查是否有未完成的授权
277
+ const authState = await oauthManager.loadAuthState();
278
+ if (authState && !oauthManager.isAuthStateExpired(authState)) {
279
+ console.log(`检测到未完成的授权,验证码: ${authState.userCode}`);
280
+ console.log('请在微信柴米记账小程序中输入此验证码完成授权');
281
+ console.log('授权完成后,请重新调用工具');
282
+ throw new Error(`授权进行中,请使用验证码 ${authState.userCode} 完成授权`);
283
+ }
284
+
285
+ // 没有未完成的授权,启动新的授权流程
286
+ console.log('启动新的授权流程...');
257
287
  const newToken = await oauthManager.startAuthFlow();
258
288
  cachedToken = newToken.accessToken;
259
289
  tokenExpireTime = newToken.expiresAt;
260
290
  lastRefreshTime = Date.now();
291
+ // 授权成功,清除授权状态
292
+ await oauthManager.clearAuthState();
261
293
  return cachedToken;
262
294
  }
263
295
  }
@@ -370,13 +402,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
370
402
  // 极简快捷记账:自动识别支出/收入,智能匹配分类
371
403
  console.log('处理极简快捷记账...');
372
404
 
373
- // 1. 判断是支出还是收入
405
+ // 1. 从环境变量补充参数(确保上报)
406
+ const agentType = process.env.AGENT_TYPE || process.env.MCP_AGENT_TYPE || args.agentType || '';
407
+ const apiProvider = process.env.API_PROVIDER || process.env.MCP_API_PROVIDER || args.apiProvider || '';
408
+ const rawInput = args.rawInput || args.name;
409
+
410
+ // 2. 判断是支出还是收入
374
411
  const isIncome = isIncomeName(args.name);
375
412
 
376
- // 2. 智能匹配分类
413
+ // 3. 智能匹配分类
377
414
  const category = args.category || getCategory(args.name);
378
415
 
379
- // 3. 自动补全参数
416
+ // 4. 自动补全参数
380
417
  const completedArgs = {
381
418
  name: sanitizeString(args.name, 100),
382
419
  amount: validateAmount(args.amount),
@@ -386,12 +423,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
386
423
  category: category,
387
424
  store: sanitizeString(args.store, 100) || '未知商家',
388
425
  note: sanitizeString(args.note, 500),
389
- agentType: args.agentType || '',
390
- apiProvider: args.apiProvider || '',
391
- rawInput: args.rawInput || args.name,
426
+ agentType: agentType,
427
+ apiProvider: apiProvider,
428
+ rawInput: rawInput,
429
+ mcp_version: MCP_VERSION,
392
430
  };
393
431
 
394
- // 4. 根据类型选择接口
432
+ // 5. 根据类型选择接口
395
433
  if (isIncome) {
396
434
  // 收入记账
397
435
  console.log('识别为收入,调用 save_income...');
@@ -415,7 +453,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
415
453
  }
416
454
 
417
455
  case 'save_expense': {
418
- // 步骤 1:校验数据完整性
456
+ // 步骤1:校验数据完整性
419
457
  console.log('校验消费数据完整性...');
420
458
  const validationResult = await callMcpPrompt(
421
459
  'validateResult',
@@ -652,6 +690,7 @@ function convertParams(toolName, args) {
652
690
  rawInput: sanitizeString(args.rawInput, 1000),
653
691
  agentType: args.agentType || '',
654
692
  apiProvider: args.apiProvider || '',
693
+ mcp_version: args.mcp_version || MCP_VERSION,
655
694
  };
656
695
  }
657
696
 
@@ -693,6 +732,7 @@ function convertParams(toolName, args) {
693
732
  rawInput: sanitizeString(args.rawInput, 2000),
694
733
  agentType: args.agentType || '',
695
734
  apiProvider: args.apiProvider || '',
735
+ mcp_version: args.mcp_version || MCP_VERSION,
696
736
  };
697
737
 
698
738
  case 'get_expenses':
@@ -719,6 +759,7 @@ function convertParams(toolName, args) {
719
759
  rawInput: sanitizeString(args.rawInput, 1000),
720
760
  agentType: args.agentType || '',
721
761
  apiProvider: args.apiProvider || '',
762
+ mcp_version: args.mcp_version || MCP_VERSION,
722
763
  };
723
764
 
724
765
  default:
@@ -726,40 +767,6 @@ function convertParams(toolName, args) {
726
767
  }
727
768
  }
728
769
 
729
- // 智能分类:根据商品名称匹配分类
730
- function getCategory(name) {
731
- if (!name) return '其他';
732
-
733
- const categoryMap = {
734
- '餐饮': ['饭', '菜', '餐', '酒', '饮料', '咖啡', '茶', '早餐', '午餐', '晚餐', '夜宵', '烧烤', '火锅', '快餐', '外卖', '小吃', '奶茶', '面包', '蛋糕'],
735
- '交通': ['车', '油', '票', '打车', '公交', '地铁', '加油', '停车', '租车', '火车', '飞机', '轮船', '客运'],
736
- '购物': ['买', '购', '商场', '超市', '衣服', '鞋', '包', '化妆品', '护肤品', '数码', '电器', '家具', '日用品'],
737
- '娱乐': ['玩', '游戏', '电影', '歌', 'KTV', '酒吧', '旅游', '景点', '门票', '健身', '运动', '书', '音乐'],
738
- '医疗': ['药', '医', '病', '医院', '诊所', '体检', '疫苗', '牙科', '眼科', '门诊'],
739
- '教育': ['学', '课', '培训', '考试', '学校', '学费', '教材', '文具', '培训费'],
740
- '居住': ['房', '租', '物业', '水电', '燃气', '网络', '宽带', '话费', '维修', '装修'],
741
- '工资': ['工资', '薪水', '奖金', '提成', '分红', '补贴', '津贴', '绩效'],
742
- '其他收入': ['红包', '转账', '借款', '还款', '退款', '理赔', '中奖', '利息'],
743
- };
744
-
745
- for (const [category, keywords] of Object.entries(categoryMap)) {
746
- if (keywords.some(keyword => name.includes(keyword))) {
747
- return category;
748
- }
749
- }
750
-
751
- return '其他';
752
- }
753
-
754
- // 判断是否为收入
755
- function isIncomeName(name) {
756
- if (!name) return false;
757
-
758
- const incomeKeywords = ['工资', '薪水', '奖金', '提成', '分红', '补贴', '津贴', '绩效', '红包', '转账', '借款', '还款', '退款', '理赔', '中奖', '利息', '收入', '入账', '到账'];
759
-
760
- return incomeKeywords.some(keyword => name.includes(keyword));
761
- }
762
-
763
770
  // 获取月份最后一天
764
771
  function getMonthEndDate(yearMonth) {
765
772
  const [year, month] = yearMonth.split('-').map(Number);