chaimi-keep-mcp 3.1.47-beta.4 → 3.1.47-beta.6
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 +7 -0
- package/SKILL.md +4 -5
- package/package.json +1 -1
- package/server.js +63 -65
package/README.md
CHANGED
|
@@ -87,6 +87,13 @@ export MCP_PROMPT_URL="你的Prompt服务地址"
|
|
|
87
87
|
|
|
88
88
|
> 默认使用柴米AI记账官方云服务,一般无需修改。
|
|
89
89
|
|
|
90
|
+
## Changelog
|
|
91
|
+
|
|
92
|
+
### v3.1.47-beta.5 (2026-04-22)
|
|
93
|
+
- **修复** 输入长度限制 - 添加硬性长度检查,防止超大字符串攻击
|
|
94
|
+
- **修复** 错误信息泄露 - 生产环境只显示友好提示,不暴露内部错误
|
|
95
|
+
- **修复** JSON.stringify DoS - 添加安全序列化,防止循环引用和超大数据
|
|
96
|
+
|
|
90
97
|
## 常见问题
|
|
91
98
|
|
|
92
99
|
**Q: 提示 "command not found: chaimi-keep-mcp"**
|
package/SKILL.md
CHANGED
|
@@ -290,8 +290,8 @@ Agent **直接使用当前时间**作为消费时间,无需进行时间语义
|
|
|
290
290
|
|
|
291
291
|
```
|
|
292
292
|
✅ 【商品名/店名】¥【金额】
|
|
293
|
-
|
|
294
|
-
【建议增加】自定义消费的内容,让记账信息更全面准确。
|
|
293
|
+
已录入【柴米AI记账】
|
|
294
|
+
【建议增加】自定义消费的内容,让记账信息更全面准确。
|
|
295
295
|
✅ 【友好结束语】
|
|
296
296
|
|
|
297
297
|
消费洞察:【洞察内容】(可选)
|
|
@@ -314,9 +314,8 @@ chaimi-keep-mcp: v【版本号】
|
|
|
314
314
|
**真实示例:**
|
|
315
315
|
```
|
|
316
316
|
✅ 【牛肉】¥24
|
|
317
|
-
|
|
318
|
-
华润万家超市,2026年4月16日购买
|
|
319
|
-
|
|
317
|
+
已录入【柴米AI记账】
|
|
318
|
+
华润万家超市,2026年4月16日购买
|
|
320
319
|
✅ 好好吃饭哦~
|
|
321
320
|
|
|
322
321
|
消费洞察:这是你本周第19次餐饮消费,要不要看看本周餐饮花了多少钱?
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -30,6 +30,9 @@ const MCP_VERSION = packageJson.version;
|
|
|
30
30
|
// 导入 OAuth 模块
|
|
31
31
|
const { OAuthManager, FileTokenStorage } = require('./oauth.js');
|
|
32
32
|
|
|
33
|
+
// 导入校验工具
|
|
34
|
+
const { validateDate } = require('./utils/validators.js');
|
|
35
|
+
|
|
33
36
|
// API 签名密钥(用于验证请求来自合法 MCP Server)
|
|
34
37
|
const CHAIMI_API_SECRET = 'chaimi-mcp-secret-2024';
|
|
35
38
|
|
|
@@ -795,38 +798,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
795
798
|
|
|
796
799
|
// P1: 日期合理性检查
|
|
797
800
|
if (processedArgs.date) {
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
const sixtyYearsAgo = new Date();
|
|
801
|
-
sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
|
|
802
|
-
|
|
803
|
-
if (isNaN(inputDate.getTime())) {
|
|
804
|
-
result = {
|
|
805
|
-
success: false,
|
|
806
|
-
error: '日期格式无效',
|
|
807
|
-
code: 400
|
|
808
|
-
};
|
|
809
|
-
userMessage = '❌ 记账失败:日期格式无效,请使用毫秒级时间戳(13位数字,如:xxxxxxxxxxxxx)';
|
|
810
|
-
break;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
if (inputDate > now) {
|
|
814
|
-
result = {
|
|
815
|
-
success: false,
|
|
816
|
-
error: '日期不能是未来时间',
|
|
817
|
-
code: 400
|
|
818
|
-
};
|
|
819
|
-
userMessage = '❌ 记账失败:日期不能是未来时间';
|
|
820
|
-
break;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (inputDate < sixtyYearsAgo) {
|
|
801
|
+
const validation = validateDate(processedArgs.date, '消费日期');
|
|
802
|
+
if (!validation.valid) {
|
|
824
803
|
result = {
|
|
825
804
|
success: false,
|
|
826
|
-
error:
|
|
805
|
+
error: validation.error,
|
|
827
806
|
code: 400
|
|
828
807
|
};
|
|
829
|
-
userMessage =
|
|
808
|
+
userMessage = validation.message;
|
|
830
809
|
break;
|
|
831
810
|
}
|
|
832
811
|
}
|
|
@@ -958,38 +937,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
958
937
|
|
|
959
938
|
// P1: 日期合理性检查
|
|
960
939
|
if (processedArgs.date) {
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
const sixtyYearsAgo = new Date();
|
|
964
|
-
sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
|
|
965
|
-
|
|
966
|
-
if (isNaN(inputDate.getTime())) {
|
|
967
|
-
result = {
|
|
968
|
-
success: false,
|
|
969
|
-
error: '日期格式无效',
|
|
970
|
-
code: 400
|
|
971
|
-
};
|
|
972
|
-
userMessage = '❌ 保存失败:日期格式无效,请使用毫秒级时间戳(13位数字,如:xxxxxxxxxxxxx)';
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
if (inputDate > now) {
|
|
940
|
+
const validation = validateDate(processedArgs.date, '小票日期');
|
|
941
|
+
if (!validation.valid) {
|
|
977
942
|
result = {
|
|
978
943
|
success: false,
|
|
979
|
-
error:
|
|
944
|
+
error: validation.error,
|
|
980
945
|
code: 400
|
|
981
946
|
};
|
|
982
|
-
userMessage =
|
|
983
|
-
break;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
if (inputDate < sixtyYearsAgo) {
|
|
987
|
-
result = {
|
|
988
|
-
success: false,
|
|
989
|
-
error: '日期不能是60年前',
|
|
990
|
-
code: 400
|
|
991
|
-
};
|
|
992
|
-
userMessage = '❌ 保存失败:日期不能是60年前,请检查小票日期是否正确';
|
|
947
|
+
userMessage = validation.message + ',请检查小票日期是否正确';
|
|
993
948
|
break;
|
|
994
949
|
}
|
|
995
950
|
}
|
|
@@ -1329,6 +1284,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1329
1284
|
break;
|
|
1330
1285
|
}
|
|
1331
1286
|
|
|
1287
|
+
// P1: 日期合理性检查
|
|
1288
|
+
if (processedArgs.date) {
|
|
1289
|
+
const validation = validateDate(processedArgs.date, '收入日期');
|
|
1290
|
+
if (!validation.valid) {
|
|
1291
|
+
result = {
|
|
1292
|
+
success: false,
|
|
1293
|
+
error: validation.error,
|
|
1294
|
+
code: 400
|
|
1295
|
+
};
|
|
1296
|
+
userMessage = validation.message;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1332
1301
|
const mcpParams = convertParams('save_income', processedArgs);
|
|
1333
1302
|
result = await callMcpHubWithLogging('addIncome', mcpParams, token, traceId, startTime, osInfo, agentType, apiProvider);
|
|
1334
1303
|
|
|
@@ -1427,18 +1396,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1427
1396
|
userMessage = `❌ 操作失败:${result.error || '未知错误'}`;
|
|
1428
1397
|
}
|
|
1429
1398
|
|
|
1430
|
-
// 构建完整的返回内容:userMessage + 完整的 result 数据
|
|
1431
|
-
const fullResponse = {
|
|
1432
|
-
userMessage: userMessage,
|
|
1433
|
-
result: result,
|
|
1434
|
-
mcpVersion: MCP_VERSION
|
|
1435
|
-
};
|
|
1436
|
-
|
|
1437
1399
|
return {
|
|
1438
1400
|
content: [
|
|
1439
1401
|
{
|
|
1440
1402
|
type: 'text',
|
|
1441
|
-
text: `${userMessage}\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${
|
|
1403
|
+
text: `${userMessage}\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${safeStringify(result)}\n\`\`\``,
|
|
1442
1404
|
},
|
|
1443
1405
|
],
|
|
1444
1406
|
};
|
|
@@ -1545,11 +1507,24 @@ ${'='.repeat(60)}
|
|
|
1545
1507
|
};
|
|
1546
1508
|
}
|
|
1547
1509
|
|
|
1510
|
+
// 区分开发/生产环境,避免泄露内部错误信息
|
|
1511
|
+
const errorMessage = process.env.NODE_ENV === 'development'
|
|
1512
|
+
? `Error: ${error.message}`
|
|
1513
|
+
: '操作失败,请稍后重试或联系客服';
|
|
1514
|
+
|
|
1515
|
+
// 记录完整错误到日志(用于调试)
|
|
1516
|
+
console.error('Tool execution error:', {
|
|
1517
|
+
tool: request.params.name,
|
|
1518
|
+
error: error.message,
|
|
1519
|
+
stack: error.stack,
|
|
1520
|
+
timestamp: new Date().toISOString()
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1548
1523
|
return {
|
|
1549
1524
|
content: [
|
|
1550
1525
|
{
|
|
1551
1526
|
type: 'text',
|
|
1552
|
-
text:
|
|
1527
|
+
text: errorMessage,
|
|
1553
1528
|
},
|
|
1554
1529
|
],
|
|
1555
1530
|
isError: true,
|
|
@@ -1557,8 +1532,31 @@ ${'='.repeat(60)}
|
|
|
1557
1532
|
}
|
|
1558
1533
|
});
|
|
1559
1534
|
|
|
1535
|
+
// 安全的 JSON 序列化函数,防止循环引用和超大数据导致内存溢出
|
|
1536
|
+
function safeStringify(obj, maxLength = 100000) {
|
|
1537
|
+
try {
|
|
1538
|
+
const str = JSON.stringify(obj, null, 2);
|
|
1539
|
+
if (str.length > maxLength) {
|
|
1540
|
+
return JSON.stringify({
|
|
1541
|
+
...obj,
|
|
1542
|
+
data: '[数据过大,已省略]',
|
|
1543
|
+
_note: `完整数据大小:${str.length} 字符`
|
|
1544
|
+
}, null, 2);
|
|
1545
|
+
}
|
|
1546
|
+
return str;
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
return JSON.stringify({ error: '数据序列化失败' });
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1560
1552
|
function sanitizeString(str, maxLength = 200) {
|
|
1561
1553
|
if (!str || typeof str !== 'string') return '';
|
|
1554
|
+
|
|
1555
|
+
// 硬性限制:输入不能超过 maxLength 的 10 倍,防止超大字符串导致内存溢出
|
|
1556
|
+
if (str.length > maxLength * 10) {
|
|
1557
|
+
throw new Error(`输入过长,最大允许 ${maxLength} 字符`);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1562
1560
|
return str.replace(/<[^>]*>/g, '').substring(0, maxLength);
|
|
1563
1561
|
}
|
|
1564
1562
|
|