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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +188 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.1.42",
3
+ "version": "3.1.43",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
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: 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 callMcpHub('addExpense', mcpParams, token);
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 callMcpHub('addReceipt', mcpParams, token);
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 callMcpHub(toolName, mcpParams, token);
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 callMcpHub(toolName, mcpParams, token);
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 callMcpHub(toolName, mcpParams, token);
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 callMcpHub(toolName, mcpParams, token);
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 callMcpHub('addIncome', mcpParams, token);
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 callMcpHub('getTextParsePrompt', {}, token);
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 callMcpHub('addFeedback', feedbackData, token);
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);