chaimi-keep-mcp 3.3.3-beta.2 → 3.3.3-beta.4
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/README.md +9 -0
- package/package.json +1 -1
- package/server.js +132 -5
package/README.md
CHANGED
|
@@ -89,6 +89,15 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
|
|
|
89
89
|
|
|
90
90
|
## Changelog
|
|
91
91
|
|
|
92
|
+
### v3.3.3-beta.4 (2026-04-29)
|
|
93
|
+
- **优化** 收入记账时间解析统一 - save_income 同样使用 time_description,修复 AI 计算时间戳错误
|
|
94
|
+
|
|
95
|
+
### v3.3.3-beta.3 (2026-04-29)
|
|
96
|
+
- **优化** 文本记账时间解析 - 使用 time_description 替代直接计算时间戳,解决 AI 计算时间错误的问题
|
|
97
|
+
|
|
98
|
+
### v3.3.3-beta.2 (2026-04-29)
|
|
99
|
+
- **修复** 时间入库错误 - 解决消费时间被错误替换为当前时间的问题
|
|
100
|
+
|
|
92
101
|
### v3.3.1-beta.0 (2026-04-27)
|
|
93
102
|
- **优化** 简化工具描述 - save_expense/save_receipt/save_income 描述更简洁,不暴露业务逻辑
|
|
94
103
|
- **优化** 参数类型自动转换 - 支持 CLI 工具的字符串传参自动转为数字
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -999,6 +999,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
999
999
|
break;
|
|
1000
1000
|
}
|
|
1001
1001
|
|
|
1002
|
+
// 【新增】处理 time_description(文本记账新时间格式)
|
|
1003
|
+
let timeNote = '';
|
|
1004
|
+
if (processedArgs.time_description) {
|
|
1005
|
+
const parsedDate = parseTimeDescription(processedArgs.time_description, Date.now());
|
|
1006
|
+
processedArgs.date = parsedDate;
|
|
1007
|
+
timeNote = getTimeNote(processedArgs.time_description);
|
|
1008
|
+
console.log(`[save_expense] 时间描述解析: ${processedArgs.time_description} → ${parsedDate} (${new Date(parsedDate).toLocaleString('zh-CN')})`);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1002
1011
|
// P1: 日期合理性检查
|
|
1003
1012
|
if (processedArgs.date) {
|
|
1004
1013
|
const validation = validateDate(processedArgs.date, '消费日期');
|
|
@@ -1011,6 +1020,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1011
1020
|
userMessage = validation.message;
|
|
1012
1021
|
break;
|
|
1013
1022
|
}
|
|
1023
|
+
|
|
1024
|
+
// 【新增】校验时间是否在合理范围内(±1年)
|
|
1025
|
+
const dateObj = new Date(processedArgs.date);
|
|
1026
|
+
const now = new Date();
|
|
1027
|
+
const oneYearAgo = new Date(now.getFullYear() - 1, 0, 1);
|
|
1028
|
+
const oneYearLater = new Date(now.getFullYear() + 1, 11, 31);
|
|
1029
|
+
|
|
1030
|
+
if (dateObj < oneYearAgo || dateObj > oneYearLater) {
|
|
1031
|
+
console.warn(`[save_expense] 时间异常:${processedArgs.date} (${dateObj.toLocaleString('zh-CN')}),使用当前时间`);
|
|
1032
|
+
processedArgs.date = Date.now();
|
|
1033
|
+
timeNote = '⏰ 时间说明:检测到时间异常,已使用当前时间';
|
|
1034
|
+
}
|
|
1014
1035
|
}
|
|
1015
1036
|
|
|
1016
1037
|
const mcpParams = convertParams('save_expense', processedArgs);
|
|
@@ -1057,7 +1078,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1057
1078
|
日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
|
|
1058
1079
|
正能量祝福语: friendlyEnding,
|
|
1059
1080
|
insightsText,
|
|
1060
|
-
achievementsText
|
|
1081
|
+
achievementsText,
|
|
1082
|
+
timeNote // 【新增】时间说明
|
|
1061
1083
|
};
|
|
1062
1084
|
}
|
|
1063
1085
|
break;
|
|
@@ -1459,8 +1481,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1459
1481
|
}
|
|
1460
1482
|
|
|
1461
1483
|
case 'save_income': {
|
|
1462
|
-
// P0: 数据完整性检查 -
|
|
1463
|
-
const requiredFields = ['name', 'amount', 'category', '
|
|
1484
|
+
// P0: 数据完整性检查 - 必填字段验证(date 改为 time_description,不强制检查)
|
|
1485
|
+
const requiredFields = ['name', 'amount', 'category', 'rawInput'];
|
|
1464
1486
|
const missingFields = [];
|
|
1465
1487
|
|
|
1466
1488
|
for (const field of requiredFields) {
|
|
@@ -1481,7 +1503,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1481
1503
|
docs: '调用 get_skill() 获取详细使用指南'
|
|
1482
1504
|
}
|
|
1483
1505
|
};
|
|
1484
|
-
userMessage = `❌ 收入记录失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从用户输入中提取了所有信息\n2. 确保传递以下字段:name(收入来源)、amount(金额)、category(分类)、
|
|
1506
|
+
userMessage = `❌ 收入记录失败\n\n错误:缺少必填字段:${missingFields.join(', ')}\n\n💡 解决方案:\n1. 请检查是否从用户输入中提取了所有信息\n2. 确保传递以下字段:name(收入来源)、amount(金额)、category(分类)、rawInput(原始输入)\n3. 参考 SKILL.md 中的"调用前检查清单"`;
|
|
1485
1507
|
break;
|
|
1486
1508
|
}
|
|
1487
1509
|
|
|
@@ -1496,6 +1518,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1496
1518
|
break;
|
|
1497
1519
|
}
|
|
1498
1520
|
|
|
1521
|
+
// 【新增】处理 time_description(收入记账新时间格式)
|
|
1522
|
+
let timeNote = '';
|
|
1523
|
+
if (processedArgs.time_description) {
|
|
1524
|
+
const parsedDate = parseTimeDescription(processedArgs.time_description, Date.now());
|
|
1525
|
+
processedArgs.date = parsedDate;
|
|
1526
|
+
timeNote = getTimeNote(processedArgs.time_description);
|
|
1527
|
+
console.log(`[save_income] 时间描述解析: ${processedArgs.time_description} → ${parsedDate} (${new Date(parsedDate).toLocaleString('zh-CN')})`);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1499
1530
|
// P1: 日期合理性检查
|
|
1500
1531
|
if (processedArgs.date) {
|
|
1501
1532
|
const validation = validateDate(processedArgs.date, '收入日期');
|
|
@@ -1508,6 +1539,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1508
1539
|
userMessage = validation.message;
|
|
1509
1540
|
break;
|
|
1510
1541
|
}
|
|
1542
|
+
|
|
1543
|
+
// 【新增】校验时间是否在合理范围内(±1年)
|
|
1544
|
+
const dateObj = new Date(processedArgs.date);
|
|
1545
|
+
const now = new Date();
|
|
1546
|
+
const oneYearAgo = new Date(now.getFullYear() - 1, 0, 1);
|
|
1547
|
+
const oneYearLater = new Date(now.getFullYear() + 1, 11, 31);
|
|
1548
|
+
|
|
1549
|
+
if (dateObj < oneYearAgo || dateObj > oneYearLater) {
|
|
1550
|
+
console.warn(`[save_income] 时间异常:${processedArgs.date} (${dateObj.toLocaleString('zh-CN')}),使用当前时间`);
|
|
1551
|
+
processedArgs.date = Date.now();
|
|
1552
|
+
timeNote = '⏰ 时间说明:检测到时间异常,已使用当前时间';
|
|
1553
|
+
}
|
|
1511
1554
|
}
|
|
1512
1555
|
|
|
1513
1556
|
const mcpParams = convertParams('save_income', processedArgs);
|
|
@@ -1529,7 +1572,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1529
1572
|
金额: displayAmount,
|
|
1530
1573
|
分类: result.data?.categoryName || displayCategory,
|
|
1531
1574
|
日期: result.data?.date ? new Date(result.data.date).toLocaleDateString('zh-CN') : new Date().toLocaleDateString('zh-CN'),
|
|
1532
|
-
正能量祝福语: '入账顺利!💰'
|
|
1575
|
+
正能量祝福语: '入账顺利!💰',
|
|
1576
|
+
timeNote // 【新增】时间说明
|
|
1533
1577
|
};
|
|
1534
1578
|
}
|
|
1535
1579
|
break;
|
|
@@ -1893,6 +1937,89 @@ function formatDateWithTimezone(dateInput) {
|
|
|
1893
1937
|
}
|
|
1894
1938
|
}
|
|
1895
1939
|
|
|
1940
|
+
/**
|
|
1941
|
+
* 解析时间描述为时间戳
|
|
1942
|
+
* @param {string} timeDesc - 时间描述(如 'yesterday', 'today')
|
|
1943
|
+
* @param {number} currentTimestamp - 当前时间戳
|
|
1944
|
+
* @returns {number} 时间戳
|
|
1945
|
+
*/
|
|
1946
|
+
function parseTimeDescription(timeDesc, currentTimestamp) {
|
|
1947
|
+
const now = new Date(currentTimestamp);
|
|
1948
|
+
const year = now.getFullYear();
|
|
1949
|
+
const month = now.getMonth();
|
|
1950
|
+
const day = now.getDate();
|
|
1951
|
+
|
|
1952
|
+
switch(timeDesc) {
|
|
1953
|
+
case 'just_now':
|
|
1954
|
+
case 'today':
|
|
1955
|
+
return currentTimestamp;
|
|
1956
|
+
|
|
1957
|
+
case 'yesterday':
|
|
1958
|
+
return new Date(year, month, day - 1, 12, 0, 0).getTime();
|
|
1959
|
+
|
|
1960
|
+
case 'yesterday_evening':
|
|
1961
|
+
return new Date(year, month, day - 1, 19, 0, 0).getTime();
|
|
1962
|
+
|
|
1963
|
+
case 'this_morning':
|
|
1964
|
+
return new Date(year, month, day, 8, 0, 0).getTime();
|
|
1965
|
+
|
|
1966
|
+
case 'this_noon':
|
|
1967
|
+
return new Date(year, month, day, 12, 0, 0).getTime();
|
|
1968
|
+
|
|
1969
|
+
case 'this_afternoon':
|
|
1970
|
+
return new Date(year, month, day, 14, 0, 0).getTime();
|
|
1971
|
+
|
|
1972
|
+
case 'this_evening':
|
|
1973
|
+
return new Date(year, month, day, 19, 0, 0).getTime();
|
|
1974
|
+
|
|
1975
|
+
default:
|
|
1976
|
+
// 尝试解析具体日期格式:2026-03-15 或 2026-03-15T15:00
|
|
1977
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(timeDesc)) {
|
|
1978
|
+
const [y, m, d] = timeDesc.split('-').map(Number);
|
|
1979
|
+
return new Date(y, m - 1, d, 12, 0, 0).getTime();
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(timeDesc)) {
|
|
1983
|
+
return new Date(timeDesc).getTime();
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// 兜底:返回当前时间
|
|
1987
|
+
return currentTimestamp;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
/**
|
|
1992
|
+
* 获取时间说明文本
|
|
1993
|
+
* @param {string} timeDesc - 时间描述
|
|
1994
|
+
* @returns {string} 时间说明
|
|
1995
|
+
*/
|
|
1996
|
+
function getTimeNote(timeDesc) {
|
|
1997
|
+
const descMap = {
|
|
1998
|
+
'just_now': '刚刚',
|
|
1999
|
+
'today': '今天',
|
|
2000
|
+
'yesterday': '昨天',
|
|
2001
|
+
'yesterday_evening': '昨晚',
|
|
2002
|
+
'this_morning': '今早',
|
|
2003
|
+
'this_noon': '今天中午',
|
|
2004
|
+
'this_afternoon': '今天下午',
|
|
2005
|
+
'this_evening': '今晚'
|
|
2006
|
+
};
|
|
2007
|
+
|
|
2008
|
+
if (descMap[timeDesc]) {
|
|
2009
|
+
return `⏰ 时间说明:系统使用"${descMap[timeDesc]}"作为默认时间`;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
if (timeDesc && timeDesc.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
2013
|
+
return `⏰ 时间说明:记录日期为 ${timeDesc}`;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
if (timeDesc && timeDesc.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/)) {
|
|
2017
|
+
return `⏰ 时间说明:记录精确时间为 ${timeDesc}`;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
return '';
|
|
2021
|
+
}
|
|
2022
|
+
|
|
1896
2023
|
function extractWeightInGrams(weightStr) {
|
|
1897
2024
|
if (!weightStr || typeof weightStr !== 'string') return 0;
|
|
1898
2025
|
|