chaimi-keep-mcp 3.1.30 → 3.1.33-beta.1
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/SKILL.md +8 -9
- package/bin/sync-skill.js +143 -0
- package/package.json +3 -2
- package/server.js +186 -3
package/SKILL.md
CHANGED
|
@@ -142,7 +142,7 @@ Agent 通过 MCP 协议自动获取工具列表,无需手动查询。
|
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
145
|
-
### 🚨
|
|
145
|
+
### 🚨 小票字段说明(save_receipt)
|
|
146
146
|
|
|
147
147
|
**从 prompt 解析结果中提取的所有字段,必须全部传递给 `save_receipt`**:
|
|
148
148
|
|
|
@@ -167,14 +167,7 @@ Agent 通过 MCP 协议自动获取工具列表,无需手动查询。
|
|
|
167
167
|
| `quantity` | ✅ | 数量 |
|
|
168
168
|
| `category` | ✅ **新增** | 分类(如:食品、日用品)|
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
1. 提取完小票信息后,对照上表检查每个字段
|
|
172
|
-
2. **重点检查标记"高频遗漏"的字段**:date、actualAmount、originalAmount、discountAmount
|
|
173
|
-
3. **确保每个商品都有 category**,这是记账的基本信息
|
|
174
|
-
4. 确认所有字段都在 args 中后再调用 `save_receipt`
|
|
175
|
-
5. 如果某个字段小票上确实没有,可以传空字符串或 0,但不能省略
|
|
176
|
-
|
|
177
|
-
**⚠️ 重要**:缺少必填字段会导致保存失败,请严格按照清单核对!
|
|
170
|
+
**⚠️ 重要**:缺少必填字段会导致保存失败。服务器会自动校验数据完整性和准确性。
|
|
178
171
|
|
|
179
172
|
---
|
|
180
173
|
|
|
@@ -315,4 +308,10 @@ MCP Server: v3.1.23
|
|
|
315
308
|
|
|
316
309
|
---
|
|
317
310
|
|
|
311
|
+
## 相关文档
|
|
312
|
+
|
|
313
|
+
- [MCP优化需求跟踪表](../../../docs/08-项目管理/02-需求跟踪/MCP优化需求跟踪表.md) - 项目需求跟踪
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
318
317
|
*最后更新:2026-04-14*
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill 文件同步脚本
|
|
4
|
+
*
|
|
5
|
+
* 功能:安装时自动同步 SKILL.md 到 Skill 架构的 Agent
|
|
6
|
+
* 支持:OpenClaw、WorkBuddy 等
|
|
7
|
+
* 扩展:可通过 ~/.chaimi-mcp/skill-config.json 添加自定义路径
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
// 内置常见 Skill 架构 Agent 路径
|
|
15
|
+
const BUILT_IN_PATHS = [
|
|
16
|
+
{
|
|
17
|
+
name: 'OpenClaw',
|
|
18
|
+
skillPath: path.join(os.homedir(), '.openclaw', 'skills', 'chaimi-keep-mcp'),
|
|
19
|
+
platform: ['darwin', 'linux']
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'WorkBuddy',
|
|
23
|
+
skillPath: path.join(os.homedir(), '.workbuddy', 'skills', 'chaimi-keep-mcp'),
|
|
24
|
+
platform: ['darwin', 'linux', 'win32']
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// 用户自定义配置文件路径
|
|
29
|
+
const USER_CONFIG_PATH = path.join(os.homedir(), '.chaimi-mcp', 'skill-config.json');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 加载用户自定义配置
|
|
33
|
+
*/
|
|
34
|
+
function loadUserConfig() {
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(USER_CONFIG_PATH)) {
|
|
37
|
+
const config = JSON.parse(fs.readFileSync(USER_CONFIG_PATH, 'utf8'));
|
|
38
|
+
return config.skillDirectories || [];
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`⚠️ 读取用户配置失败: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 同步 SKILL.md 到指定目录
|
|
48
|
+
*/
|
|
49
|
+
function syncSkillToDirectory(targetPath, agentName) {
|
|
50
|
+
try {
|
|
51
|
+
const skillSourcePath = path.join(__dirname, '..', 'SKILL.md');
|
|
52
|
+
|
|
53
|
+
// 检查源文件是否存在
|
|
54
|
+
if (!fs.existsSync(skillSourcePath)) {
|
|
55
|
+
console.error(`❌ ${agentName}: SKILL.md 源文件不存在`);
|
|
56
|
+
return { success: false, error: 'source-not-found' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 检查父目录是否存在(Agent 是否安装)
|
|
60
|
+
const parentDir = path.dirname(targetPath);
|
|
61
|
+
if (!fs.existsSync(parentDir)) {
|
|
62
|
+
// Agent 未安装,静默跳过
|
|
63
|
+
return { success: false, reason: 'agent-not-installed' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 创建 skill 目录
|
|
67
|
+
if (!fs.existsSync(targetPath)) {
|
|
68
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 复制 SKILL.md
|
|
72
|
+
const targetFilePath = path.join(targetPath, 'SKILL.md');
|
|
73
|
+
fs.copyFileSync(skillSourcePath, targetFilePath);
|
|
74
|
+
|
|
75
|
+
return { success: true, path: targetFilePath };
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`❌ ${agentName}: 同步失败 - ${error.message}`);
|
|
79
|
+
return { success: false, error: error.message };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 主函数
|
|
85
|
+
*/
|
|
86
|
+
function main() {
|
|
87
|
+
console.log('📋 同步 Skill 文件到 Agent...\n');
|
|
88
|
+
|
|
89
|
+
const results = [];
|
|
90
|
+
|
|
91
|
+
// 1. 同步内置路径
|
|
92
|
+
for (const agent of BUILT_IN_PATHS) {
|
|
93
|
+
// 平台检查
|
|
94
|
+
if (agent.platform && !agent.platform.includes(os.platform())) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = syncSkillToDirectory(agent.skillPath, agent.name);
|
|
99
|
+
results.push({ agent: agent.name, ...result });
|
|
100
|
+
|
|
101
|
+
if (result.success) {
|
|
102
|
+
console.log(`✅ ${agent.name}: Skill 文件已同步`);
|
|
103
|
+
} else if (result.reason === 'agent-not-installed') {
|
|
104
|
+
// 静默跳过未安装的 Agent
|
|
105
|
+
} else {
|
|
106
|
+
console.error(`❌ ${agent.name}: 同步失败`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. 同步用户自定义路径
|
|
111
|
+
const userPaths = loadUserConfig();
|
|
112
|
+
for (const userPath of userPaths) {
|
|
113
|
+
if (!userPath.name || !userPath.path) {
|
|
114
|
+
console.error(`⚠️ 无效的用户配置: ${JSON.stringify(userPath)}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = syncSkillToDirectory(userPath.path, userPath.name);
|
|
119
|
+
results.push({ agent: userPath.name, ...result });
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
console.log(`✅ ${userPath.name}: Skill 文件已同步(自定义)`);
|
|
123
|
+
} else if (result.reason !== 'agent-not-installed') {
|
|
124
|
+
console.error(`❌ ${userPath.name}: 同步失败`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 统计
|
|
129
|
+
const successCount = results.filter(r => r.success).length;
|
|
130
|
+
const skipCount = results.filter(r => r.reason === 'agent-not-installed').length;
|
|
131
|
+
|
|
132
|
+
console.log(`\n📊 同步完成: ${successCount} 个成功, ${skipCount} 个跳过(未安装)\n`);
|
|
133
|
+
|
|
134
|
+
// 提示用户如何添加自定义路径
|
|
135
|
+
if (userPaths.length === 0) {
|
|
136
|
+
console.log('💡 提示: 如需添加其他 Skill 架构 Agent,可创建配置文件:');
|
|
137
|
+
console.log(` ${USER_CONFIG_PATH}`);
|
|
138
|
+
console.log(' 格式: {"skillDirectories": [{"name": "Agent名称", "path": "skill目录路径"}]}\n');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 执行
|
|
143
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaimi-keep-mcp",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.33-beta.1",
|
|
4
4
|
"description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"type": "commonjs",
|
|
19
19
|
"scripts": {
|
|
20
20
|
"start": "node server.js",
|
|
21
|
-
"dev": "node server.js"
|
|
21
|
+
"dev": "node server.js",
|
|
22
|
+
"postinstall": "node bin/sync-skill.js"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"mcp",
|
package/server.js
CHANGED
|
@@ -472,6 +472,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
472
472
|
break;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
// P1: 日期合理性检查
|
|
476
|
+
if (processedArgs.date) {
|
|
477
|
+
const inputDate = new Date(processedArgs.date);
|
|
478
|
+
const now = new Date();
|
|
479
|
+
const sixtyYearsAgo = new Date();
|
|
480
|
+
sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
|
|
481
|
+
|
|
482
|
+
if (isNaN(inputDate.getTime())) {
|
|
483
|
+
result = {
|
|
484
|
+
success: false,
|
|
485
|
+
error: '日期格式无效',
|
|
486
|
+
code: 400
|
|
487
|
+
};
|
|
488
|
+
userMessage = '❌ 记账失败:日期格式无效,请使用 ISO 8601 格式(如:2026-01-08T11:42:27)';
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (inputDate > now) {
|
|
493
|
+
result = {
|
|
494
|
+
success: false,
|
|
495
|
+
error: '日期不能是未来时间',
|
|
496
|
+
code: 400
|
|
497
|
+
};
|
|
498
|
+
userMessage = '❌ 记账失败:日期不能是未来时间';
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (inputDate < sixtyYearsAgo) {
|
|
503
|
+
result = {
|
|
504
|
+
success: false,
|
|
505
|
+
error: '日期不能是60年前',
|
|
506
|
+
code: 400
|
|
507
|
+
};
|
|
508
|
+
userMessage = '❌ 记账失败:日期不能是60年前';
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
475
513
|
const mcpParams = convertParams('save_expense', processedArgs);
|
|
476
514
|
result = await callMcpHub('addExpense', mcpParams, token);
|
|
477
515
|
|
|
@@ -571,6 +609,111 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
571
609
|
break;
|
|
572
610
|
}
|
|
573
611
|
|
|
612
|
+
// P1: 日期合理性检查
|
|
613
|
+
if (processedArgs.date) {
|
|
614
|
+
const inputDate = new Date(processedArgs.date);
|
|
615
|
+
const now = new Date();
|
|
616
|
+
const sixtyYearsAgo = new Date();
|
|
617
|
+
sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
|
|
618
|
+
|
|
619
|
+
if (isNaN(inputDate.getTime())) {
|
|
620
|
+
result = {
|
|
621
|
+
success: false,
|
|
622
|
+
error: '日期格式无效',
|
|
623
|
+
code: 400
|
|
624
|
+
};
|
|
625
|
+
userMessage = '❌ 保存失败:日期格式无效,请使用 ISO 8601 格式(如:2026-01-08T11:42:27)';
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (inputDate > now) {
|
|
630
|
+
result = {
|
|
631
|
+
success: false,
|
|
632
|
+
error: '日期不能是未来时间',
|
|
633
|
+
code: 400
|
|
634
|
+
};
|
|
635
|
+
userMessage = '❌ 保存失败:日期不能是未来时间,请检查小票日期是否正确';
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (inputDate < sixtyYearsAgo) {
|
|
640
|
+
result = {
|
|
641
|
+
success: false,
|
|
642
|
+
error: '日期不能是60年前',
|
|
643
|
+
code: 400
|
|
644
|
+
};
|
|
645
|
+
userMessage = '❌ 保存失败:日期不能是60年前,请检查小票日期是否正确';
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// P2: 智能补全 - 如果有缺失字段且提供了 rawInput,尝试重新解析
|
|
651
|
+
const missingFieldsForFill = [];
|
|
652
|
+
const fillableFields = ['store', 'date', 'totalAmount', 'actualAmount', 'originalAmount', 'discountAmount', 'paymentMethod'];
|
|
653
|
+
for (const field of fillableFields) {
|
|
654
|
+
if (processedArgs[field] === undefined || processedArgs[field] === null || processedArgs[field] === '') {
|
|
655
|
+
missingFieldsForFill.push(field);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (missingFieldsForFill.length > 0 && processedArgs.rawInput) {
|
|
660
|
+
console.error(`⚠️ 字段缺失,尝试从 rawInput 重新解析:${missingFieldsForFill.join(', ')}`);
|
|
661
|
+
|
|
662
|
+
const parseResult = await callMcpPrompt(
|
|
663
|
+
'parseReceipt',
|
|
664
|
+
{
|
|
665
|
+
text: processedArgs.rawInput,
|
|
666
|
+
type: 'parseReceipt'
|
|
667
|
+
},
|
|
668
|
+
token
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
if (parseResult.success && parseResult.data) {
|
|
672
|
+
// 只补全缺失的字段
|
|
673
|
+
for (const field of missingFieldsForFill) {
|
|
674
|
+
if (parseResult.data[field] !== undefined && parseResult.data[field] !== null && parseResult.data[field] !== '') {
|
|
675
|
+
processedArgs[field] = parseResult.data[field];
|
|
676
|
+
console.error(`✅ 补全字段:${field} = ${processedArgs[field]}`);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// 如果 items 也缺失,尝试补全
|
|
681
|
+
if ((!processedArgs.items || processedArgs.items.length === 0) && parseResult.data.items) {
|
|
682
|
+
processedArgs.items = parseResult.data.items;
|
|
683
|
+
console.error(`✅ 补全字段:items(${processedArgs.items.length} 个商品)`);
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
console.error(`❌ 重新解析失败:${parseResult.error || '未知错误'}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// P1: 金额一致性校验
|
|
691
|
+
const ERROR_MARGIN = 0.1; // 允许0.1元误差
|
|
692
|
+
|
|
693
|
+
// 校验1:items 各项 amount 之和 ≈ totalAmount
|
|
694
|
+
const itemsSum = processedArgs.items.reduce((sum, item) => sum + (item.amount || 0), 0);
|
|
695
|
+
if (Math.abs(itemsSum - processedArgs.totalAmount) > ERROR_MARGIN) {
|
|
696
|
+
result = {
|
|
697
|
+
success: false,
|
|
698
|
+
error: `商品金额总和 (${itemsSum.toFixed(2)}) 与总金额 (${processedArgs.totalAmount}) 不一致`,
|
|
699
|
+
code: 400
|
|
700
|
+
};
|
|
701
|
+
userMessage = `❌ 保存失败:金额校验不通过\n\n商品金额总和:${itemsSum.toFixed(2)}元\n小票总金额:${processedArgs.totalAmount}元\n\n💡 请检查:\n1. 每个商品的 amount 是否正确(amount = price × quantity)\n2. 是否有遗漏的商品`;
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 校验2:originalAmount - discountAmount ≈ actualAmount
|
|
706
|
+
const calculatedActual = processedArgs.originalAmount - processedArgs.discountAmount;
|
|
707
|
+
if (Math.abs(calculatedActual - processedArgs.actualAmount) > ERROR_MARGIN) {
|
|
708
|
+
result = {
|
|
709
|
+
success: false,
|
|
710
|
+
error: `优惠计算不正确:原价(${processedArgs.originalAmount}) - 优惠(${processedArgs.discountAmount}) = ${calculatedActual.toFixed(2)},不等于实付金额(${processedArgs.actualAmount})`,
|
|
711
|
+
code: 400
|
|
712
|
+
};
|
|
713
|
+
userMessage = `❌ 保存失败:金额校验不通过\n\n原价:${processedArgs.originalAmount}元\n优惠:${processedArgs.discountAmount}元\n计算实付:${calculatedActual.toFixed(2)}元\n实际实付:${processedArgs.actualAmount}元\n\n💡 请检查优惠金额是否正确`;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
|
|
574
717
|
// 1. 调用 parseReceipt 重新提取小票信息(覆盖 Agent 传的数据)
|
|
575
718
|
if (processedArgs.rawInput) {
|
|
576
719
|
const parseResult = await callMcpPrompt(
|
|
@@ -828,6 +971,46 @@ function validateQuantity(quantity) {
|
|
|
828
971
|
return isNaN(num) || num <= 0 ? 1 : num;
|
|
829
972
|
}
|
|
830
973
|
|
|
974
|
+
/**
|
|
975
|
+
* 格式化日期为 ISO 8601 格式(带时区)
|
|
976
|
+
* 解决时区转换问题:确保传入的本地时间正确存储
|
|
977
|
+
*/
|
|
978
|
+
function formatDateWithTimezone(dateStr) {
|
|
979
|
+
if (!dateStr) return '';
|
|
980
|
+
|
|
981
|
+
try {
|
|
982
|
+
// 如果已经是带时区的格式,直接返回
|
|
983
|
+
if (dateStr.match(/[+-]\d{2}:\d{2}$/)) {
|
|
984
|
+
return dateStr;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// 如果是简单的 ISO 格式(如 2026-04-14T12:00:00),添加本地时区
|
|
988
|
+
if (dateStr.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)) {
|
|
989
|
+
// 添加北京时间时区 +08:00
|
|
990
|
+
return dateStr + '+08:00';
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// 其他情况,尝试解析并格式化为带时区的格式
|
|
994
|
+
const date = new Date(dateStr);
|
|
995
|
+
if (isNaN(date.getTime())) {
|
|
996
|
+
return dateStr; // 解析失败,原样返回
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// 格式化为 ISO 8601 带时区格式
|
|
1000
|
+
const year = date.getFullYear();
|
|
1001
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1002
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
1003
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
1004
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
1005
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
1006
|
+
|
|
1007
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}+08:00`;
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
console.error('日期格式化错误:', error);
|
|
1010
|
+
return dateStr;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
831
1014
|
function extractWeightInGrams(weightStr) {
|
|
832
1015
|
if (!weightStr || typeof weightStr !== 'string') return 0;
|
|
833
1016
|
|
|
@@ -884,7 +1067,7 @@ function convertParams(toolName, args) {
|
|
|
884
1067
|
quantity: 1,
|
|
885
1068
|
category: sanitizeString(args.category, 50) || '其他',
|
|
886
1069
|
store: sanitizeString(args.store, 100) || '',
|
|
887
|
-
date: args.date,
|
|
1070
|
+
date: formatDateWithTimezone(args.date),
|
|
888
1071
|
transactionType: 'expense',
|
|
889
1072
|
unit: '',
|
|
890
1073
|
weight: '',
|
|
@@ -943,7 +1126,7 @@ function convertParams(toolName, args) {
|
|
|
943
1126
|
memberCardNo: sanitizeString(args.memberCardNo, 50),
|
|
944
1127
|
currentPoints: parseInt(args.currentPoints) || 0,
|
|
945
1128
|
totalPoints: parseInt(args.totalPoints) || 0,
|
|
946
|
-
date: args.date,
|
|
1129
|
+
date: formatDateWithTimezone(args.date),
|
|
947
1130
|
rawInput: sanitizeString(args.rawInput, 2000),
|
|
948
1131
|
agentType: args.agentType || '',
|
|
949
1132
|
apiProvider: args.apiProvider || '',
|
|
@@ -971,7 +1154,7 @@ function convertParams(toolName, args) {
|
|
|
971
1154
|
amount: validateAmount(args.amount),
|
|
972
1155
|
category: sanitizeString(args.category, 50) || '其他',
|
|
973
1156
|
store: sanitizeString(args.store, 100),
|
|
974
|
-
date: args.date,
|
|
1157
|
+
date: formatDateWithTimezone(args.date),
|
|
975
1158
|
note: sanitizeString(args.note, 500),
|
|
976
1159
|
transactionType: 'income',
|
|
977
1160
|
rawInput: sanitizeString(args.rawInput, 1000),
|