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.
- package/bin/cli.js +1 -5
- package/package.json +1 -1
- 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(
|
|
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
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: '
|
|
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('
|
|
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
|
-
//
|
|
413
|
+
// 3. 智能匹配分类
|
|
377
414
|
const category = args.category || getCategory(args.name);
|
|
378
415
|
|
|
379
|
-
//
|
|
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:
|
|
390
|
-
apiProvider:
|
|
391
|
-
rawInput:
|
|
426
|
+
agentType: agentType,
|
|
427
|
+
apiProvider: apiProvider,
|
|
428
|
+
rawInput: rawInput,
|
|
429
|
+
mcp_version: MCP_VERSION,
|
|
392
430
|
};
|
|
393
431
|
|
|
394
|
-
//
|
|
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
|
-
// 步骤
|
|
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);
|