chaimi-keep-mcp 3.1.42 → 3.1.43
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/package.json +1 -1
- package/server.js +188 -11
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -483,11 +483,105 @@ function generateSignature(body, timestamp, secret) {
|
|
|
483
483
|
.digest('hex');
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
// MCP 调用日志记录函数
|
|
487
|
+
async function logMcpCall(logData) {
|
|
488
|
+
try {
|
|
489
|
+
// 异步发送日志到云函数,不阻塞主流程
|
|
490
|
+
const body = {
|
|
491
|
+
tool: 'logMcpCall',
|
|
492
|
+
params: logData,
|
|
493
|
+
};
|
|
494
|
+
const timestamp = Date.now().toString();
|
|
495
|
+
const signature = generateSignature(body, timestamp, CHAIMI_API_SECRET);
|
|
496
|
+
|
|
497
|
+
fetch(MCP_HUB_URL, {
|
|
498
|
+
method: 'POST',
|
|
499
|
+
headers: {
|
|
500
|
+
'Content-Type': 'application/json',
|
|
501
|
+
'Authorization': `Bearer ${await getToken()}`,
|
|
502
|
+
'X-Chaimi-Signature': signature,
|
|
503
|
+
'X-Chaimi-Timestamp': timestamp,
|
|
504
|
+
},
|
|
505
|
+
body: JSON.stringify(body),
|
|
506
|
+
}).catch(err => {
|
|
507
|
+
// 日志记录失败不影响主流程
|
|
508
|
+
console.error('MCP 调用日志记录失败:', err.message);
|
|
509
|
+
});
|
|
510
|
+
} catch (e) {
|
|
511
|
+
// 日志记录失败不影响主流程
|
|
512
|
+
console.error('MCP 调用日志记录异常:', e.message);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// 生成 traceId
|
|
517
|
+
function generateTraceId() {
|
|
518
|
+
return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 获取操作系统信息
|
|
522
|
+
function getOSInfo() {
|
|
523
|
+
return {
|
|
524
|
+
osType: process.platform, // 'darwin', 'win32', 'linux'
|
|
525
|
+
osVersion: os.release() // '23.6.0', '10.0.19045'
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 调用 mcpHub 云函数(带日志和链路追踪)
|
|
530
|
+
async function callMcpHubWithLogging(tool, params, token, traceId, startTime, osInfo) {
|
|
531
|
+
// 记录即将调用云函数
|
|
532
|
+
logMcpCall({
|
|
533
|
+
traceId,
|
|
534
|
+
stage: 'cloud_calling',
|
|
535
|
+
toolName: tool,
|
|
536
|
+
timestamp: new Date().toISOString(),
|
|
537
|
+
logSource: 'mcp',
|
|
538
|
+
osType: osInfo?.osType,
|
|
539
|
+
osVersion: osInfo?.osVersion
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const result = await callMcpHub(tool, params, token, traceId, osInfo);
|
|
544
|
+
|
|
545
|
+
// 记录云函数调用成功
|
|
546
|
+
logMcpCall({
|
|
547
|
+
traceId,
|
|
548
|
+
stage: 'cloud_success',
|
|
549
|
+
toolName: tool,
|
|
550
|
+
duration: Date.now() - startTime,
|
|
551
|
+
timestamp: new Date().toISOString(),
|
|
552
|
+
logSource: 'mcp',
|
|
553
|
+
osType: osInfo?.osType,
|
|
554
|
+
osVersion: osInfo?.osVersion
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
return result;
|
|
558
|
+
} catch (cloudErr) {
|
|
559
|
+
// 记录云函数调用失败
|
|
560
|
+
logMcpCall({
|
|
561
|
+
traceId,
|
|
562
|
+
stage: 'cloud_failed',
|
|
563
|
+
toolName: tool,
|
|
564
|
+
error: cloudErr.message,
|
|
565
|
+
duration: Date.now() - startTime,
|
|
566
|
+
timestamp: new Date().toISOString(),
|
|
567
|
+
logSource: 'mcp',
|
|
568
|
+
osType: osInfo?.osType,
|
|
569
|
+
osVersion: osInfo?.osVersion
|
|
570
|
+
});
|
|
571
|
+
throw cloudErr;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
486
575
|
// 调用 mcpHub 云函数
|
|
487
|
-
async function callMcpHub(tool, params, token) {
|
|
576
|
+
async function callMcpHub(tool, params, token, traceId, osInfo) {
|
|
488
577
|
const body = {
|
|
489
578
|
tool: tool,
|
|
490
|
-
params:
|
|
579
|
+
params: {
|
|
580
|
+
...params,
|
|
581
|
+
_traceId: traceId, // 传递 traceId 用于链路追踪
|
|
582
|
+
osType: osInfo?.osType, // 传递操作系统类型
|
|
583
|
+
osVersion: osInfo?.osVersion, // 传递操作系统版本
|
|
584
|
+
},
|
|
491
585
|
};
|
|
492
586
|
const timestamp = Date.now().toString();
|
|
493
587
|
const signature = generateSignature(body, timestamp, CHAIMI_API_SECRET);
|
|
@@ -551,12 +645,42 @@ function recordSkillRead() {
|
|
|
551
645
|
// 工具调用处理
|
|
552
646
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
553
647
|
const { name, arguments: args } = request.params;
|
|
648
|
+
const startTime = Date.now();
|
|
649
|
+
const traceId = generateTraceId();
|
|
650
|
+
const osInfo = getOSInfo();
|
|
651
|
+
|
|
652
|
+
// 记录请求到达
|
|
653
|
+
logMcpCall({
|
|
654
|
+
traceId,
|
|
655
|
+
stage: 'request_received',
|
|
656
|
+
toolName: name,
|
|
657
|
+
params: sanitizeLogParams(args),
|
|
658
|
+
agentType: args.agentType || process.env.AGENT_TYPE || process.env.MCP_AGENT_TYPE || '',
|
|
659
|
+
apiProvider: args.apiProvider || process.env.API_PROVIDER || process.env.MCP_API_PROVIDER || '',
|
|
660
|
+
mcp_version: MCP_VERSION,
|
|
661
|
+
osType: osInfo.osType,
|
|
662
|
+
osVersion: osInfo.osVersion,
|
|
663
|
+
timestamp: new Date().toISOString(),
|
|
664
|
+
logSource: 'mcp'
|
|
665
|
+
});
|
|
554
666
|
|
|
555
667
|
// 强制检查:记账工具必须先调用 get_skill
|
|
556
668
|
if (name === 'save_expense' || name === 'save_receipt' || name === 'save_income') {
|
|
557
669
|
const now = Date.now();
|
|
558
670
|
const lastReadTime = getLastSkillReadTime();
|
|
559
671
|
if (now - lastReadTime > SKILL_VALID_DURATION) {
|
|
672
|
+
// 记录校验失败
|
|
673
|
+
logMcpCall({
|
|
674
|
+
traceId,
|
|
675
|
+
stage: 'validation_failed',
|
|
676
|
+
toolName: name,
|
|
677
|
+
error: 'SKILL_NOT_READ',
|
|
678
|
+
duration: Date.now() - startTime,
|
|
679
|
+
timestamp: new Date().toISOString(),
|
|
680
|
+
logSource: 'mcp',
|
|
681
|
+
osType: osInfo.osType,
|
|
682
|
+
osVersion: osInfo.osVersion
|
|
683
|
+
});
|
|
560
684
|
return {
|
|
561
685
|
content: [
|
|
562
686
|
{
|
|
@@ -696,7 +820,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
696
820
|
}
|
|
697
821
|
|
|
698
822
|
const mcpParams = convertParams('save_expense', processedArgs);
|
|
699
|
-
result = await
|
|
823
|
+
result = await callMcpHubWithLogging('addExpense', mcpParams, token, traceId, startTime, osInfo);
|
|
700
824
|
|
|
701
825
|
if (result.success) {
|
|
702
826
|
const displayName = processedArgs.name || '未知商品';
|
|
@@ -963,7 +1087,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
963
1087
|
}
|
|
964
1088
|
|
|
965
1089
|
const mcpParams = convertParams('save_receipt', processedArgs);
|
|
966
|
-
result = await
|
|
1090
|
+
result = await callMcpHubWithLogging('addReceipt', mcpParams, token, traceId, startTime, osInfo);
|
|
967
1091
|
|
|
968
1092
|
if (result.success) {
|
|
969
1093
|
const totalAmount = processedArgs.totalAmount || processedArgs.items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
|
@@ -1005,7 +1129,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1005
1129
|
case 'get_receipt_list': {
|
|
1006
1130
|
const toolName = toolMapping[name];
|
|
1007
1131
|
const mcpParams = convertParams(name, processedArgs);
|
|
1008
|
-
result = await
|
|
1132
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1009
1133
|
|
|
1010
1134
|
if (result.success) {
|
|
1011
1135
|
const total = result.data?.total || 0;
|
|
@@ -1046,7 +1170,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1046
1170
|
case 'get_statistics': {
|
|
1047
1171
|
const toolName = toolMapping[name];
|
|
1048
1172
|
const mcpParams = convertParams(name, processedArgs);
|
|
1049
|
-
result = await
|
|
1173
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1050
1174
|
|
|
1051
1175
|
if (result.success) {
|
|
1052
1176
|
const data = result.data || {};
|
|
@@ -1094,7 +1218,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1094
1218
|
case 'get_insights': {
|
|
1095
1219
|
const toolName = toolMapping[name];
|
|
1096
1220
|
const mcpParams = convertParams(name, processedArgs);
|
|
1097
|
-
result = await
|
|
1221
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1098
1222
|
|
|
1099
1223
|
if (result.success) {
|
|
1100
1224
|
const data = result.data || {};
|
|
@@ -1140,7 +1264,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1140
1264
|
case 'export_data': {
|
|
1141
1265
|
const toolName = toolMapping[name];
|
|
1142
1266
|
const mcpParams = convertParams(name, processedArgs);
|
|
1143
|
-
result = await
|
|
1267
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1144
1268
|
|
|
1145
1269
|
if (result.success) {
|
|
1146
1270
|
const summary = result.data?.summary || {};
|
|
@@ -1194,7 +1318,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1194
1318
|
}
|
|
1195
1319
|
|
|
1196
1320
|
const mcpParams = convertParams('save_income', processedArgs);
|
|
1197
|
-
result = await
|
|
1321
|
+
result = await callMcpHubWithLogging('addIncome', mcpParams, token, traceId, startTime, osInfo);
|
|
1198
1322
|
|
|
1199
1323
|
if (result.success) {
|
|
1200
1324
|
const displayName = processedArgs.name || '未知收入';
|
|
@@ -1234,7 +1358,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1234
1358
|
|
|
1235
1359
|
case 'get_text_parse_prompt': {
|
|
1236
1360
|
// 获取文字记账解析 Prompt
|
|
1237
|
-
const textParseResult = await
|
|
1361
|
+
const textParseResult = await callMcpHubWithLogging('getTextParsePrompt', {}, token, traceId, startTime, osInfo);
|
|
1238
1362
|
|
|
1239
1363
|
if (textParseResult.success) {
|
|
1240
1364
|
result = {
|
|
@@ -1266,7 +1390,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1266
1390
|
userAgent: request.headers?.['user-agent'] || ''
|
|
1267
1391
|
};
|
|
1268
1392
|
|
|
1269
|
-
result = await
|
|
1393
|
+
result = await callMcpHubWithLogging('addFeedback', feedbackData, token, traceId, startTime, osInfo);
|
|
1270
1394
|
|
|
1271
1395
|
if (result.success) {
|
|
1272
1396
|
const typeText = {
|
|
@@ -1307,6 +1431,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1307
1431
|
],
|
|
1308
1432
|
};
|
|
1309
1433
|
} catch (error) {
|
|
1434
|
+
// 记录异常错误
|
|
1435
|
+
logMcpCall({
|
|
1436
|
+
traceId,
|
|
1437
|
+
stage: 'mcp_error',
|
|
1438
|
+
toolName: name,
|
|
1439
|
+
error: error.message,
|
|
1440
|
+
duration: Date.now() - startTime,
|
|
1441
|
+
timestamp: new Date().toISOString(),
|
|
1442
|
+
logSource: 'mcp',
|
|
1443
|
+
osType: osInfo.osType,
|
|
1444
|
+
osVersion: osInfo.osVersion
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1310
1447
|
// 处理授权错误
|
|
1311
1448
|
if (error.message.startsWith('NEED_AUTH:')) {
|
|
1312
1449
|
const userCode = error.message.replace('NEED_AUTH:', '');
|
|
@@ -1766,6 +1903,46 @@ async function startAuthFlowAndWait(timeout = 120000) {
|
|
|
1766
1903
|
}
|
|
1767
1904
|
}
|
|
1768
1905
|
|
|
1906
|
+
/**
|
|
1907
|
+
* 脱敏处理日志参数
|
|
1908
|
+
* 移除敏感信息,限制长度
|
|
1909
|
+
*/
|
|
1910
|
+
function sanitizeLogParams(params) {
|
|
1911
|
+
if (!params || typeof params !== 'object') {
|
|
1912
|
+
return {};
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
const sanitized = {};
|
|
1916
|
+
const allowedFields = [
|
|
1917
|
+
'name', 'amount', 'category', 'store', 'date', 'period',
|
|
1918
|
+
'format', 'types', 'limit', 'skip', 'content', 'feedType',
|
|
1919
|
+
'yearMonth', 'startDate', 'endDate', 'includeReceipts'
|
|
1920
|
+
];
|
|
1921
|
+
|
|
1922
|
+
for (const key of allowedFields) {
|
|
1923
|
+
if (params[key] !== undefined) {
|
|
1924
|
+
// 字符串字段长度限制
|
|
1925
|
+
if (typeof params[key] === 'string' && params[key].length > 200) {
|
|
1926
|
+
sanitized[key] = params[key].substring(0, 200) + '...';
|
|
1927
|
+
} else {
|
|
1928
|
+
sanitized[key] = params[key];
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// 特殊处理 items 数组(小票商品列表)
|
|
1934
|
+
if (params.items && Array.isArray(params.items)) {
|
|
1935
|
+
sanitized.itemsCount = params.items.length;
|
|
1936
|
+
sanitized.items = params.items.slice(0, 3).map(item => ({
|
|
1937
|
+
name: item.name,
|
|
1938
|
+
amount: item.amount,
|
|
1939
|
+
category: item.category
|
|
1940
|
+
}));
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
return sanitized;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1769
1946
|
// 注意:使用 console.error,避免污染 stdout(MCP 协议通信使用 stdout)
|
|
1770
1947
|
main().catch((err) => {
|
|
1771
1948
|
console.error('❌ MCP Server 启动失败:', err.message);
|