chaimi-keep-mcp 3.1.42 → 3.1.44-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/package.json +1 -1
- package/server.js +208 -20
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -320,6 +320,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
320
320
|
required: ['name', 'amount', 'category', 'agentType', 'apiProvider', 'rawInput'],
|
|
321
321
|
},
|
|
322
322
|
},
|
|
323
|
+
{
|
|
324
|
+
name: 'get_text_parse_prompt',
|
|
325
|
+
description: '获取文字记账解析的Prompt模板,用于指导大模型如何解析用户输入的记账文字。返回的Prompt应作为system message,配合用户输入的记账文字作为user message调用大模型',
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {
|
|
329
|
+
agentType: { type: 'string', description: '【推荐自动填充】Agent类型,如:claude-desktop、cursor、openclaw、workbuddy、trae' },
|
|
330
|
+
apiProvider: { type: 'string', description: '【推荐自动填充】AI服务提供商,如:anthropic、openai、doubao、aliyun' },
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
323
334
|
{
|
|
324
335
|
name: 'get_parse_prompt',
|
|
325
336
|
description: '获取小票图片解析的Prompt模板,用于指导大模型如何格式化输出小票信息。返回的Prompt应作为system message,配合小票图片作为user message调用大模型',
|
|
@@ -483,11 +494,105 @@ function generateSignature(body, timestamp, secret) {
|
|
|
483
494
|
.digest('hex');
|
|
484
495
|
}
|
|
485
496
|
|
|
497
|
+
// MCP 调用日志记录函数
|
|
498
|
+
async function logMcpCall(logData) {
|
|
499
|
+
try {
|
|
500
|
+
// 异步发送日志到云函数,不阻塞主流程
|
|
501
|
+
const body = {
|
|
502
|
+
tool: 'logMcpCall',
|
|
503
|
+
params: logData,
|
|
504
|
+
};
|
|
505
|
+
const timestamp = Date.now().toString();
|
|
506
|
+
const signature = generateSignature(body, timestamp, CHAIMI_API_SECRET);
|
|
507
|
+
|
|
508
|
+
fetch(MCP_HUB_URL, {
|
|
509
|
+
method: 'POST',
|
|
510
|
+
headers: {
|
|
511
|
+
'Content-Type': 'application/json',
|
|
512
|
+
'Authorization': `Bearer ${await getToken()}`,
|
|
513
|
+
'X-Chaimi-Signature': signature,
|
|
514
|
+
'X-Chaimi-Timestamp': timestamp,
|
|
515
|
+
},
|
|
516
|
+
body: JSON.stringify(body),
|
|
517
|
+
}).catch(err => {
|
|
518
|
+
// 日志记录失败不影响主流程
|
|
519
|
+
console.error('MCP 调用日志记录失败:', err.message);
|
|
520
|
+
});
|
|
521
|
+
} catch (e) {
|
|
522
|
+
// 日志记录失败不影响主流程
|
|
523
|
+
console.error('MCP 调用日志记录异常:', e.message);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// 生成 traceId
|
|
528
|
+
function generateTraceId() {
|
|
529
|
+
return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 获取操作系统信息
|
|
533
|
+
function getOSInfo() {
|
|
534
|
+
return {
|
|
535
|
+
osType: process.platform, // 'darwin', 'win32', 'linux'
|
|
536
|
+
osVersion: os.release() // '23.6.0', '10.0.19045'
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// 调用 mcpHub 云函数(带日志和链路追踪)
|
|
541
|
+
async function callMcpHubWithLogging(tool, params, token, traceId, startTime, osInfo) {
|
|
542
|
+
// 记录即将调用云函数
|
|
543
|
+
logMcpCall({
|
|
544
|
+
traceId,
|
|
545
|
+
stage: 'cloud_calling',
|
|
546
|
+
toolName: tool,
|
|
547
|
+
timestamp: new Date().toISOString(),
|
|
548
|
+
logSource: 'mcp',
|
|
549
|
+
osType: osInfo?.osType,
|
|
550
|
+
osVersion: osInfo?.osVersion
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
try {
|
|
554
|
+
const result = await callMcpHub(tool, params, token, traceId, osInfo);
|
|
555
|
+
|
|
556
|
+
// 记录云函数调用成功
|
|
557
|
+
logMcpCall({
|
|
558
|
+
traceId,
|
|
559
|
+
stage: 'cloud_success',
|
|
560
|
+
toolName: tool,
|
|
561
|
+
duration: Date.now() - startTime,
|
|
562
|
+
timestamp: new Date().toISOString(),
|
|
563
|
+
logSource: 'mcp',
|
|
564
|
+
osType: osInfo?.osType,
|
|
565
|
+
osVersion: osInfo?.osVersion
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
return result;
|
|
569
|
+
} catch (cloudErr) {
|
|
570
|
+
// 记录云函数调用失败
|
|
571
|
+
logMcpCall({
|
|
572
|
+
traceId,
|
|
573
|
+
stage: 'cloud_failed',
|
|
574
|
+
toolName: tool,
|
|
575
|
+
error: cloudErr.message,
|
|
576
|
+
duration: Date.now() - startTime,
|
|
577
|
+
timestamp: new Date().toISOString(),
|
|
578
|
+
logSource: 'mcp',
|
|
579
|
+
osType: osInfo?.osType,
|
|
580
|
+
osVersion: osInfo?.osVersion
|
|
581
|
+
});
|
|
582
|
+
throw cloudErr;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
486
586
|
// 调用 mcpHub 云函数
|
|
487
|
-
async function callMcpHub(tool, params, token) {
|
|
587
|
+
async function callMcpHub(tool, params, token, traceId, osInfo) {
|
|
488
588
|
const body = {
|
|
489
589
|
tool: tool,
|
|
490
|
-
params:
|
|
590
|
+
params: {
|
|
591
|
+
...params,
|
|
592
|
+
_traceId: traceId, // 传递 traceId 用于链路追踪
|
|
593
|
+
osType: osInfo?.osType, // 传递操作系统类型
|
|
594
|
+
osVersion: osInfo?.osVersion, // 传递操作系统版本
|
|
595
|
+
},
|
|
491
596
|
};
|
|
492
597
|
const timestamp = Date.now().toString();
|
|
493
598
|
const signature = generateSignature(body, timestamp, CHAIMI_API_SECRET);
|
|
@@ -551,12 +656,42 @@ function recordSkillRead() {
|
|
|
551
656
|
// 工具调用处理
|
|
552
657
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
553
658
|
const { name, arguments: args } = request.params;
|
|
659
|
+
const startTime = Date.now();
|
|
660
|
+
const traceId = generateTraceId();
|
|
661
|
+
const osInfo = getOSInfo();
|
|
662
|
+
|
|
663
|
+
// 记录请求到达
|
|
664
|
+
logMcpCall({
|
|
665
|
+
traceId,
|
|
666
|
+
stage: 'request_received',
|
|
667
|
+
toolName: name,
|
|
668
|
+
params: sanitizeLogParams(args),
|
|
669
|
+
agentType: args.agentType || process.env.AGENT_TYPE || process.env.MCP_AGENT_TYPE || '',
|
|
670
|
+
apiProvider: args.apiProvider || process.env.API_PROVIDER || process.env.MCP_API_PROVIDER || '',
|
|
671
|
+
mcp_version: MCP_VERSION,
|
|
672
|
+
osType: osInfo.osType,
|
|
673
|
+
osVersion: osInfo.osVersion,
|
|
674
|
+
timestamp: new Date().toISOString(),
|
|
675
|
+
logSource: 'mcp'
|
|
676
|
+
});
|
|
554
677
|
|
|
555
678
|
// 强制检查:记账工具必须先调用 get_skill
|
|
556
679
|
if (name === 'save_expense' || name === 'save_receipt' || name === 'save_income') {
|
|
557
680
|
const now = Date.now();
|
|
558
681
|
const lastReadTime = getLastSkillReadTime();
|
|
559
682
|
if (now - lastReadTime > SKILL_VALID_DURATION) {
|
|
683
|
+
// 记录校验失败
|
|
684
|
+
logMcpCall({
|
|
685
|
+
traceId,
|
|
686
|
+
stage: 'validation_failed',
|
|
687
|
+
toolName: name,
|
|
688
|
+
error: 'SKILL_NOT_READ',
|
|
689
|
+
duration: Date.now() - startTime,
|
|
690
|
+
timestamp: new Date().toISOString(),
|
|
691
|
+
logSource: 'mcp',
|
|
692
|
+
osType: osInfo.osType,
|
|
693
|
+
osVersion: osInfo.osVersion
|
|
694
|
+
});
|
|
560
695
|
return {
|
|
561
696
|
content: [
|
|
562
697
|
{
|
|
@@ -696,7 +831,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
696
831
|
}
|
|
697
832
|
|
|
698
833
|
const mcpParams = convertParams('save_expense', processedArgs);
|
|
699
|
-
result = await
|
|
834
|
+
result = await callMcpHubWithLogging('addExpense', mcpParams, token, traceId, startTime, osInfo);
|
|
700
835
|
|
|
701
836
|
if (result.success) {
|
|
702
837
|
const displayName = processedArgs.name || '未知商品';
|
|
@@ -963,7 +1098,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
963
1098
|
}
|
|
964
1099
|
|
|
965
1100
|
const mcpParams = convertParams('save_receipt', processedArgs);
|
|
966
|
-
result = await
|
|
1101
|
+
result = await callMcpHubWithLogging('addReceipt', mcpParams, token, traceId, startTime, osInfo);
|
|
967
1102
|
|
|
968
1103
|
if (result.success) {
|
|
969
1104
|
const totalAmount = processedArgs.totalAmount || processedArgs.items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0;
|
|
@@ -1005,7 +1140,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1005
1140
|
case 'get_receipt_list': {
|
|
1006
1141
|
const toolName = toolMapping[name];
|
|
1007
1142
|
const mcpParams = convertParams(name, processedArgs);
|
|
1008
|
-
result = await
|
|
1143
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1009
1144
|
|
|
1010
1145
|
if (result.success) {
|
|
1011
1146
|
const total = result.data?.total || 0;
|
|
@@ -1046,7 +1181,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1046
1181
|
case 'get_statistics': {
|
|
1047
1182
|
const toolName = toolMapping[name];
|
|
1048
1183
|
const mcpParams = convertParams(name, processedArgs);
|
|
1049
|
-
result = await
|
|
1184
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1050
1185
|
|
|
1051
1186
|
if (result.success) {
|
|
1052
1187
|
const data = result.data || {};
|
|
@@ -1094,7 +1229,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1094
1229
|
case 'get_insights': {
|
|
1095
1230
|
const toolName = toolMapping[name];
|
|
1096
1231
|
const mcpParams = convertParams(name, processedArgs);
|
|
1097
|
-
result = await
|
|
1232
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1098
1233
|
|
|
1099
1234
|
if (result.success) {
|
|
1100
1235
|
const data = result.data || {};
|
|
@@ -1140,7 +1275,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1140
1275
|
case 'export_data': {
|
|
1141
1276
|
const toolName = toolMapping[name];
|
|
1142
1277
|
const mcpParams = convertParams(name, processedArgs);
|
|
1143
|
-
result = await
|
|
1278
|
+
result = await callMcpHubWithLogging(toolName, mcpParams, token, traceId, startTime, osInfo);
|
|
1144
1279
|
|
|
1145
1280
|
if (result.success) {
|
|
1146
1281
|
const summary = result.data?.summary || {};
|
|
@@ -1194,7 +1329,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1194
1329
|
}
|
|
1195
1330
|
|
|
1196
1331
|
const mcpParams = convertParams('save_income', processedArgs);
|
|
1197
|
-
result = await
|
|
1332
|
+
result = await callMcpHubWithLogging('addIncome', mcpParams, token, traceId, startTime, osInfo);
|
|
1198
1333
|
|
|
1199
1334
|
if (result.success) {
|
|
1200
1335
|
const displayName = processedArgs.name || '未知收入';
|
|
@@ -1233,23 +1368,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1233
1368
|
}
|
|
1234
1369
|
|
|
1235
1370
|
case 'get_text_parse_prompt': {
|
|
1236
|
-
//
|
|
1237
|
-
const
|
|
1371
|
+
// 调用 mcpPrompt 云函数获取文字记账解析 Prompt
|
|
1372
|
+
const promptResult = await callMcpPrompt('getTextParsePrompt', {}, token);
|
|
1238
1373
|
|
|
1239
|
-
if (
|
|
1374
|
+
if (promptResult.success) {
|
|
1240
1375
|
result = {
|
|
1241
1376
|
success: true,
|
|
1242
1377
|
data: {
|
|
1243
|
-
prompt:
|
|
1244
|
-
currentDate:
|
|
1245
|
-
currentTime:
|
|
1246
|
-
instructions:
|
|
1378
|
+
prompt: promptResult.data.prompt,
|
|
1379
|
+
currentDate: promptResult.data.currentDate,
|
|
1380
|
+
currentTime: promptResult.data.currentTime,
|
|
1381
|
+
instructions: promptResult.data.instructions
|
|
1247
1382
|
}
|
|
1248
1383
|
};
|
|
1249
|
-
userMessage = `✅ 获取文字记账解析 Prompt 成功\n\n当前日期: ${
|
|
1384
|
+
userMessage = `✅ 获取文字记账解析 Prompt 成功\n\n当前日期: ${promptResult.data.currentDate}\n当前时间: ${promptResult.data.currentTime}\n\n## Prompt\n\n请将以下内容作为 system message 发送给大模型:\n\n\`\`\`\n${promptResult.data.prompt}\n\`\`\`\n\n## 使用说明\n\n1. 将上面的 Prompt 作为 system message\n2. 将用户输入的记账文字(如"午餐 24块")作为 user message\n3. 调用大模型解析,获取 JSON 结果\n4. 解析完成后,调用 save_expense 或 save_income 工具保存结果`;
|
|
1250
1385
|
} else {
|
|
1251
|
-
result = { success: false, error:
|
|
1252
|
-
userMessage = `❌ 获取文字记账解析 Prompt 失败:${
|
|
1386
|
+
result = { success: false, error: promptResult.error };
|
|
1387
|
+
userMessage = `❌ 获取文字记账解析 Prompt 失败:${promptResult.error}`;
|
|
1253
1388
|
}
|
|
1254
1389
|
break;
|
|
1255
1390
|
}
|
|
@@ -1266,7 +1401,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1266
1401
|
userAgent: request.headers?.['user-agent'] || ''
|
|
1267
1402
|
};
|
|
1268
1403
|
|
|
1269
|
-
result = await
|
|
1404
|
+
result = await callMcpHubWithLogging('addFeedback', feedbackData, token, traceId, startTime, osInfo);
|
|
1270
1405
|
|
|
1271
1406
|
if (result.success) {
|
|
1272
1407
|
const typeText = {
|
|
@@ -1307,6 +1442,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1307
1442
|
],
|
|
1308
1443
|
};
|
|
1309
1444
|
} catch (error) {
|
|
1445
|
+
// 记录异常错误
|
|
1446
|
+
logMcpCall({
|
|
1447
|
+
traceId,
|
|
1448
|
+
stage: 'mcp_error',
|
|
1449
|
+
toolName: name,
|
|
1450
|
+
error: error.message,
|
|
1451
|
+
duration: Date.now() - startTime,
|
|
1452
|
+
timestamp: new Date().toISOString(),
|
|
1453
|
+
logSource: 'mcp',
|
|
1454
|
+
osType: osInfo.osType,
|
|
1455
|
+
osVersion: osInfo.osVersion
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1310
1458
|
// 处理授权错误
|
|
1311
1459
|
if (error.message.startsWith('NEED_AUTH:')) {
|
|
1312
1460
|
const userCode = error.message.replace('NEED_AUTH:', '');
|
|
@@ -1766,6 +1914,46 @@ async function startAuthFlowAndWait(timeout = 120000) {
|
|
|
1766
1914
|
}
|
|
1767
1915
|
}
|
|
1768
1916
|
|
|
1917
|
+
/**
|
|
1918
|
+
* 脱敏处理日志参数
|
|
1919
|
+
* 移除敏感信息,限制长度
|
|
1920
|
+
*/
|
|
1921
|
+
function sanitizeLogParams(params) {
|
|
1922
|
+
if (!params || typeof params !== 'object') {
|
|
1923
|
+
return {};
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
const sanitized = {};
|
|
1927
|
+
const allowedFields = [
|
|
1928
|
+
'name', 'amount', 'category', 'store', 'date', 'period',
|
|
1929
|
+
'format', 'types', 'limit', 'skip', 'content', 'feedType',
|
|
1930
|
+
'yearMonth', 'startDate', 'endDate', 'includeReceipts'
|
|
1931
|
+
];
|
|
1932
|
+
|
|
1933
|
+
for (const key of allowedFields) {
|
|
1934
|
+
if (params[key] !== undefined) {
|
|
1935
|
+
// 字符串字段长度限制
|
|
1936
|
+
if (typeof params[key] === 'string' && params[key].length > 200) {
|
|
1937
|
+
sanitized[key] = params[key].substring(0, 200) + '...';
|
|
1938
|
+
} else {
|
|
1939
|
+
sanitized[key] = params[key];
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// 特殊处理 items 数组(小票商品列表)
|
|
1945
|
+
if (params.items && Array.isArray(params.items)) {
|
|
1946
|
+
sanitized.itemsCount = params.items.length;
|
|
1947
|
+
sanitized.items = params.items.slice(0, 3).map(item => ({
|
|
1948
|
+
name: item.name,
|
|
1949
|
+
amount: item.amount,
|
|
1950
|
+
category: item.category
|
|
1951
|
+
}));
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
return sanitized;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1769
1957
|
// 注意:使用 console.error,避免污染 stdout(MCP 协议通信使用 stdout)
|
|
1770
1958
|
main().catch((err) => {
|
|
1771
1959
|
console.error('❌ MCP Server 启动失败:', err.message);
|