chaimi-keep-mcp 3.1.45-beta.7 → 3.1.46-beta.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.1.45-beta.7",
3
+ "version": "3.1.46-beta.0",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -8,7 +8,6 @@
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
- "utils/",
12
11
  "server.js",
13
12
  "oauth.js",
14
13
  "SKILL.md",
package/server.js CHANGED
@@ -5,105 +5,11 @@
5
5
  * 支持 Claude Desktop、Cursor、WorkBuddy、OpenClaw
6
6
  */
7
7
 
8
- // 加载环境变量 - 根据 NODE_ENV 自动选择配置文件
9
- const NODE_ENV = process.env.NODE_ENV || 'development';
10
- const envFile = `.env.${NODE_ENV}`;
11
-
12
- // ==================== 自动配置获取 ====================
13
- async function fetchConfigFromCloud() {
14
- const configUrl = 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp';
15
-
16
- try {
17
- console.error('🔧 首次启动,正在从云端获取配置...');
18
-
19
- const https = require('https');
20
- const response = await new Promise((resolve, reject) => {
21
- const req = https.request(configUrl, {
22
- method: 'POST',
23
- headers: {
24
- 'Content-Type': 'application/json'
25
- }
26
- }, (res) => {
27
- let data = '';
28
- res.on('data', chunk => data += chunk);
29
- res.on('end', () => {
30
- try {
31
- resolve(JSON.parse(data));
32
- } catch (e) {
33
- reject(new Error('配置解析失败'));
34
- }
35
- });
36
- });
37
-
38
- req.on('error', reject);
39
- req.write(JSON.stringify({ action: 'getMcpConfig' }));
40
- req.end();
41
- });
42
-
43
- if (!response.success || !response.data) {
44
- throw new Error('配置获取失败');
45
- }
46
-
47
- return response.data;
48
- } catch (err) {
49
- // 抛出错误让调用者处理,不要直接退出
50
- throw new Error(`配置获取失败: ${err.message}`);
51
- }
52
- }
53
-
54
- async function initializeConfig() {
55
- const fs = require('fs');
56
- const path = require('path');
57
- const envPath = path.join(__dirname, envFile);
58
-
59
- // 如果配置文件已存在,直接返回
60
- if (fs.existsSync(envPath)) {
61
- return;
62
- }
63
-
64
- // 首次启动,尝试从云端获取配置
65
- let config;
66
- try {
67
- config = await fetchConfigFromCloud();
68
- console.error('✅ 已从云端获取配置');
69
- } catch (err) {
70
- console.error('❌ 无法从云端获取配置');
71
- console.error(' 错误:' + err.message);
72
- console.error('');
73
- console.error('可能原因:');
74
- console.error(' 1. 网络连接异常');
75
- console.error(' 2. 云函数尚未部署或配置错误');
76
- console.error('');
77
- console.error('解决方案:');
78
- console.error(' 1. 检查网络连接');
79
- console.error(' 2. 确认云函数 mcpHub 已部署');
80
- console.error(' 3. 联系开发者获取帮助');
81
- process.exit(1);
82
- }
83
-
84
- // 生成配置文件
85
- const envContent = `# 柴米记账 MCP Server - ${NODE_ENV} 环境配置
86
- # 自动生成时间:${new Date().toISOString()}
87
-
88
- # 云函数 URL
89
- MCP_HUB_URL=${config.MCP_HUB_URL}
90
- MCP_PROMPT_URL=${config.MCP_PROMPT_URL}
91
- MCP_OAUTH_URL=${config.MCP_OAUTH_URL}
92
-
93
- # API 签名密钥
94
- CHAIMI_API_SECRET=${config.CHAIMI_API_SECRET}
95
-
96
- # JWT 密钥
97
- JWT_SECRET=${config.JWT_SECRET}
98
- `;
99
-
100
- fs.writeFileSync(envPath, envContent, { mode: 0o600 });
101
- console.error('✅ 配置文件已自动生成!');
102
- console.error(` 文件位置:${envPath}`);
103
- console.error('');
104
-
105
- // 重新加载环境变量
106
- require('dotenv').config({ path: envPath });
8
+ // 加载 .env 文件
9
+ try {
10
+ require('dotenv').config();
11
+ } catch (e) {
12
+ // dotenv 未安装,忽略
107
13
  }
108
14
 
109
15
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -124,90 +30,37 @@ const MCP_VERSION = packageJson.version;
124
30
  // 导入 OAuth 模块
125
31
  const { OAuthManager, FileTokenStorage } = require('./oauth.js');
126
32
 
127
- // 导入工具函数
128
- const { validateDate } = require('./utils/validators.js');
129
-
130
- // ==================== 密钥管理 ====================
131
-
132
- // Token 加密密钥文件路径
133
- const SECRET_KEY_FILE = path.join(__dirname, '.mcp-secret-key');
134
-
135
- // 自动生成或加载 Token 加密密钥
136
- function getOrCreateSecretKey() {
137
- // 1. 优先使用环境变量(高级用户可以手动配置)
138
- if (process.env.MCP_SECRET_KEY) {
139
- console.error('✅ 使用环境变量中的 Token 加密密钥');
140
- return process.env.MCP_SECRET_KEY;
141
- }
142
-
143
- // 2. 尝试从文件加载
144
- if (fs.existsSync(SECRET_KEY_FILE)) {
145
- const key = fs.readFileSync(SECRET_KEY_FILE, 'utf8').trim();
146
- console.error('✅ 从本地文件加载 Token 加密密钥');
147
- return key;
148
- }
149
-
150
- // 3. 自动生成新密钥(64 字符,256 位)
151
- const newKey = crypto.randomBytes(32).toString('hex');
152
- fs.writeFileSync(SECRET_KEY_FILE, newKey, { mode: 0o600 }); // 只有当前用户可读写
153
- console.error('✅ 已自动生成 Token 加密密钥,保存在 .mcp-secret-key 文件中');
154
- console.error('⚠️ 请勿删除此文件,否则已授权的 Token 将无法解密');
155
- return newKey;
156
- }
157
-
158
- // Token 加密密钥(自动生成或加载)
159
- const SECRET_KEY = getOrCreateSecretKey();
160
-
161
33
  // API 签名密钥(用于验证请求来自合法 MCP Server)
162
- // 注意:initializeConfig() 后会自动加载,这里先声明变量
163
- let CHAIMI_API_SECRET;
164
-
165
- // ==================== 加密/解密函数 ====================
166
-
167
- // AES-256-GCM 加密(用于 Token)
168
- function encrypt(text, secretKey) {
169
- const iv = crypto.randomBytes(16); // 随机初始化向量
170
- const cipher = crypto.createCipheriv(
171
- 'aes-256-gcm',
172
- Buffer.from(secretKey, 'hex'),
173
- iv
174
- );
175
-
176
- let encrypted = cipher.update(text, 'utf8', 'hex');
177
- encrypted += cipher.final('hex');
34
+ const CHAIMI_API_SECRET = 'chaimi-mcp-secret-2024';
178
35
 
179
- const authTag = cipher.getAuthTag(); // 认证标签(防篡改)
36
+ // URL 加密密钥
37
+ const URL_ENCRYPT_KEY = 'chaimi-url-key-2024';
180
38
 
181
- // 返回格式:iv:authTag:encrypted
182
- return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
183
- }
184
-
185
- // AES-256-GCM 解密(用于 Token)
186
- function decrypt(encryptedText, secretKey) {
187
- const [ivHex, authTagHex, encrypted] = encryptedText.split(':');
188
-
189
- const decipher = crypto.createDecipheriv(
190
- 'aes-256-gcm',
191
- Buffer.from(secretKey, 'hex'),
192
- Buffer.from(ivHex, 'hex')
193
- );
194
-
195
- decipher.setAuthTag(Buffer.from(authTagHex, 'hex'));
196
-
197
- let decrypted = decipher.update(encrypted, 'hex', 'utf8');
198
- decrypted += decipher.final('utf8');
39
+ // 加密的云函数 URL
40
+ const ENCRYPTED_URLS = {
41
+ hub: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f571320140b40044e05',
42
+ oauth: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f571327201c1901',
43
+ prompt: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f5713381306001959'
44
+ };
199
45
 
200
- return decrypted;
46
+ // 解密 URL 函数
47
+ function decryptUrl(encrypted, key) {
48
+ const hex = encrypted.replace('enc:v1:', '');
49
+ let result = '';
50
+ for (let i = 0; i < hex.length; i += 2) {
51
+ const byte = parseInt(hex.substr(i, 2), 16);
52
+ result += String.fromCharCode(byte ^ key.charCodeAt((i / 2) % key.length));
53
+ }
54
+ return result;
201
55
  }
202
56
 
203
- // ==================== 云函数 URL 配置 ====================
204
- // 注意:initializeConfig() 后会自动加载,这里先声明变量
205
- let MCP_HUB_URL;
206
- let MCP_PROMPT_URL;
207
- let MCP_OAUTH_URL;
57
+ // 配置 - 微信云函数(运行时解密)
58
+ const MCP_HUB_URL = process.env.MCP_HUB_URL || decryptUrl(ENCRYPTED_URLS.hub, URL_ENCRYPT_KEY);
59
+ const MCP_PROMPT_URL = process.env.MCP_PROMPT_URL || decryptUrl(ENCRYPTED_URLS.prompt, URL_ENCRYPT_KEY);
60
+ const MCP_OAUTH_URL = process.env.MCP_OAUTH_URL || decryptUrl(ENCRYPTED_URLS.oauth, URL_ENCRYPT_KEY);
208
61
 
209
- // Token 缓存(加密存储)
210
- let cachedEncryptedToken = null;
62
+ // Token 缓存
63
+ let cachedToken = null;
211
64
  let tokenExpireTime = 0;
212
65
 
213
66
  // OAuth 管理器实例
@@ -255,8 +108,8 @@ async function initOAuthManager() {
255
108
  // 检查 Token 是否过期(预留5分钟缓冲)
256
109
  const expiresAt = new Date(existingToken.expiresAt).getTime();
257
110
  if (expiresAt > Date.now() + 5 * 60 * 1000) {
258
- // Token 有效,加密后缓存
259
- cachedEncryptedToken = encrypt(existingToken.accessToken, SECRET_KEY);
111
+ // Token 有效,设置授权状态
112
+ cachedToken = existingToken.accessToken;
260
113
  tokenExpireTime = expiresAt;
261
114
  authState.isAuthorized = true;
262
115
  console.error('✅ 已从文件加载有效 Token,无需重新授权');
@@ -518,103 +371,15 @@ const toolMapping = {
518
371
  'export_data': 'exportData',
519
372
  };
520
373
 
521
- // ==================== 请求频率限制 ====================
522
-
523
- // 限流配置
524
- const RATE_LIMITS = {
525
- // 记账工具:1 分钟 10 次
526
- write: {
527
- tools: ['save_expense', 'save_receipt', 'save_income'],
528
- maxRequests: 10,
529
- windowMs: 60 * 1000, // 1 分钟
530
- },
531
- // 查询工具:1 天 10 次
532
- read: {
533
- tools: ['get_expenses', 'get_receipt_list', 'get_statistics', 'get_insights', 'export_data'],
534
- maxRequests: 10,
535
- windowMs: 24 * 60 * 60 * 1000, // 1 天
536
- },
537
- // 配置读取工具:1 分钟 10 次
538
- config: {
539
- tools: ['get_skill', 'get_parse_prompt', 'get_text_parse_prompt'],
540
- maxRequests: 10,
541
- windowMs: 60 * 1000, // 1 分钟
542
- },
543
- // 反馈提交:1 天 5 次
544
- feedback: {
545
- tools: ['submit_feedback'],
546
- maxRequests: 5,
547
- windowMs: 24 * 60 * 60 * 1000, // 1 天
548
- },
549
- };
550
-
551
- // 限流记录:{ toolName: [timestamp1, timestamp2, ...] }
552
- const rateLimitRecords = {};
553
-
554
- // 检查是否超过频率限制
555
- function checkRateLimit(toolName) {
556
- // 查找工具所属的限流组
557
- let limitConfig = null;
558
- let limitType = null;
559
-
560
- for (const [type, config] of Object.entries(RATE_LIMITS)) {
561
- if (config.tools.includes(toolName)) {
562
- limitConfig = config;
563
- limitType = type;
564
- break;
565
- }
566
- }
567
-
568
- // 如果工具不在限流列表中,直接通过
569
- if (!limitConfig) {
570
- return { allowed: true };
571
- }
572
-
573
- const now = Date.now();
574
- const windowStart = now - limitConfig.windowMs;
575
-
576
- // 初始化记录
577
- if (!rateLimitRecords[toolName]) {
578
- rateLimitRecords[toolName] = [];
579
- }
580
-
581
- // 清理过期记录
582
- rateLimitRecords[toolName] = rateLimitRecords[toolName].filter(
583
- timestamp => timestamp > windowStart
584
- );
585
-
586
- // 检查是否超过限制
587
- if (rateLimitRecords[toolName].length >= limitConfig.maxRequests) {
588
- const oldestRequest = rateLimitRecords[toolName][0];
589
- const resetTime = oldestRequest + limitConfig.windowMs;
590
- const waitSeconds = Math.ceil((resetTime - now) / 1000);
591
-
592
- return {
593
- allowed: false,
594
- limitType,
595
- maxRequests: limitConfig.maxRequests,
596
- windowMs: limitConfig.windowMs,
597
- resetTime,
598
- waitSeconds,
599
- };
600
- }
601
-
602
- // 记录本次请求
603
- rateLimitRecords[toolName].push(now);
604
-
605
- return { allowed: true };
606
- }
607
-
608
374
  // 获取或刷新 Token(OAuth 2.0 Device Flow)
609
375
  const TOKEN_REFRESH_INTERVAL = 2 * 60 * 60 * 1000;
610
376
  let lastRefreshTime = 0;
611
377
 
612
378
  async function getToken() {
613
379
  // 步骤1:检查内存缓存 Token(最快路径)
614
- if (cachedEncryptedToken && tokenExpireTime > Date.now() + 5 * 60 * 1000) {
380
+ if (cachedToken && tokenExpireTime > Date.now() + 5 * 60 * 1000) {
615
381
  if (Date.now() - lastRefreshTime < TOKEN_REFRESH_INTERVAL) {
616
- // 解密后返回
617
- return decrypt(cachedEncryptedToken, SECRET_KEY);
382
+ return cachedToken;
618
383
  }
619
384
  }
620
385
 
@@ -625,14 +390,12 @@ async function getToken() {
625
390
  // 步骤2:尝试从文件加载并刷新 Token
626
391
  try {
627
392
  const oauthToken = await oauthManager.getValidToken();
628
- // 加密后缓存
629
- cachedEncryptedToken = encrypt(oauthToken.accessToken, SECRET_KEY);
393
+ cachedToken = oauthToken.accessToken;
630
394
  tokenExpireTime = oauthToken.expiresAt;
631
395
  lastRefreshTime = Date.now();
632
396
  authState.isAuthorized = true;
633
397
  await oauthManager.clearAuthState();
634
- // 解密后返回
635
- return decrypt(cachedEncryptedToken, SECRET_KEY);
398
+ return cachedToken;
636
399
  } catch (err) {
637
400
  // Token 无效,需要重新授权
638
401
  console.error('⏰ Token 无效,需要重新授权:', err.message);
@@ -903,7 +666,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
903
666
  const startTime = Date.now();
904
667
  const traceId = generateTraceId();
905
668
  const osInfo = getOSInfo();
906
-
669
+
907
670
  // 提取元数据字段
908
671
  const agentType = args.agentType || process.env.AGENT_TYPE || process.env.MCP_AGENT_TYPE || '';
909
672
  const apiProvider = args.apiProvider || process.env.API_PROVIDER || process.env.MCP_API_PROVIDER || '';
@@ -923,44 +686,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
923
686
  logSource: 'mcp'
924
687
  });
925
688
 
926
- // 检查频率限制
927
- const rateLimitResult = checkRateLimit(name);
928
- if (!rateLimitResult.allowed) {
929
- const windowName = rateLimitResult.windowMs === 60 * 1000 ? '1 分钟' : '1 天';
930
- const waitTime = rateLimitResult.waitSeconds < 60
931
- ? `${rateLimitResult.waitSeconds} 秒`
932
- : `${Math.ceil(rateLimitResult.waitSeconds / 60)} 分钟`;
933
-
934
- // 记录限流
935
- logMcpCall({
936
- traceId,
937
- stage: 'rate_limited',
938
- toolName: name,
939
- error: 'RATE_LIMIT_EXCEEDED',
940
- duration: Date.now() - startTime,
941
- agentType,
942
- apiProvider,
943
- mcpVersion: MCP_VERSION,
944
- osType: osInfo.osType,
945
- osVersion: osInfo.osVersion,
946
- timestamp: new Date().toISOString(),
947
- logSource: 'mcp'
948
- });
949
-
950
- return {
951
- content: [
952
- {
953
- type: 'text',
954
- text: JSON.stringify({
955
- success: false,
956
- error: `请求过于频繁,${windowName}内最多调用 ${rateLimitResult.maxRequests} 次,请 ${waitTime} 后再试`,
957
- code: 429
958
- })
959
- }
960
- ]
961
- };
962
- }
963
-
964
689
  // 强制检查:记账工具必须先调用 get_skill
965
690
  if (name === 'save_expense' || name === 'save_receipt' || name === 'save_income') {
966
691
  const now = Date.now();
@@ -1014,13 +739,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1014
739
  if (processedArgs.items && typeof processedArgs.items === 'string') {
1015
740
  try {
1016
741
  processedArgs.items = JSON.parse(processedArgs.items);
1017
-
1018
- // 验证解析后的类型
1019
- if (!Array.isArray(processedArgs.items)) {
1020
- throw new Error('items 必须是数组');
1021
- }
1022
742
  } catch (e) {
1023
- throw new Error(`items 参数格式错误:${e.message}`);
1024
743
  }
1025
744
  }
1026
745
 
@@ -1086,17 +805,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1086
805
  userMessage = '❌ 记账失败:金额必须是正数';
1087
806
  break;
1088
807
  }
1089
-
1090
- // 日期合理性检查
808
+
809
+ // P1: 日期合理性检查
1091
810
  if (processedArgs.date) {
1092
- const validation = validateDate(processedArgs.date, '消费时间');
1093
- if (!validation.valid) {
811
+ const inputDate = new Date(processedArgs.date);
812
+ const now = new Date();
813
+ const sixtyYearsAgo = new Date();
814
+ sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
815
+
816
+ if (isNaN(inputDate.getTime())) {
817
+ result = {
818
+ success: false,
819
+ error: '日期格式无效',
820
+ code: 400
821
+ };
822
+ userMessage = '❌ 记账失败:日期格式无效,请使用毫秒级时间戳(13位数字,如:xxxxxxxxxxxxx)';
823
+ break;
824
+ }
825
+
826
+ if (inputDate > now) {
827
+ result = {
828
+ success: false,
829
+ error: '日期不能是未来时间',
830
+ code: 400
831
+ };
832
+ userMessage = '❌ 记账失败:日期不能是未来时间';
833
+ break;
834
+ }
835
+
836
+ if (inputDate < sixtyYearsAgo) {
1094
837
  result = {
1095
838
  success: false,
1096
- error: validation.message,
839
+ error: '日期不能是60年前',
1097
840
  code: 400
1098
841
  };
1099
- userMessage = `❌ 记账失败:${validation.message}`;
842
+ userMessage = '❌ 记账失败:日期不能是60年前';
1100
843
  break;
1101
844
  }
1102
845
  }
@@ -1225,17 +968,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1225
968
  userMessage = `❌ 保存失败\n\n错误:商品数据格式不完整\n\n${invalidItems.join('\n')}\n\n💡 解决方案:\n1. 请更新您的柴米记账 MCP Skill\n2. 确保每个商品包含完整的5个字段:name, amount, price, quantity, category\n3. category 是记账的基本信息,不能为空`;
1226
969
  break;
1227
970
  }
1228
-
1229
- // 日期合理性检查
971
+
972
+ // P1: 日期合理性检查
1230
973
  if (processedArgs.date) {
1231
- const validation = validateDate(processedArgs.date, '小票日期');
1232
- if (!validation.valid) {
974
+ const inputDate = new Date(processedArgs.date);
975
+ const now = new Date();
976
+ const sixtyYearsAgo = new Date();
977
+ sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
978
+
979
+ if (isNaN(inputDate.getTime())) {
980
+ result = {
981
+ success: false,
982
+ error: '日期格式无效',
983
+ code: 400
984
+ };
985
+ userMessage = '❌ 保存失败:日期格式无效,请使用毫秒级时间戳(13位数字,如:xxxxxxxxxxxxx)';
986
+ break;
987
+ }
988
+
989
+ if (inputDate > now) {
1233
990
  result = {
1234
991
  success: false,
1235
- error: validation.message,
992
+ error: '日期不能是未来时间',
1236
993
  code: 400
1237
994
  };
1238
- userMessage = `❌ 保存失败:${validation.message},请检查小票日期是否正确`;
995
+ userMessage = '❌ 保存失败:日期不能是未来时间,请检查小票日期是否正确';
996
+ break;
997
+ }
998
+
999
+ if (inputDate < sixtyYearsAgo) {
1000
+ result = {
1001
+ success: false,
1002
+ error: '日期不能是60年前',
1003
+ code: 400
1004
+ };
1005
+ userMessage = '❌ 保存失败:日期不能是60年前,请检查小票日期是否正确';
1239
1006
  break;
1240
1007
  }
1241
1008
  }
@@ -1673,11 +1440,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1673
1440
  userMessage = `❌ 操作失败:${result.error || '未知错误'}`;
1674
1441
  }
1675
1442
 
1443
+ // 构建完整的返回内容:userMessage + 完整的 result 数据
1444
+ const fullResponse = {
1445
+ userMessage: userMessage,
1446
+ result: result,
1447
+ mcpVersion: MCP_VERSION
1448
+ };
1449
+
1676
1450
  return {
1677
1451
  content: [
1678
1452
  {
1679
1453
  type: 'text',
1680
- text: `${userMessage}\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${safeStringify(result)}\n\`\`\``,
1454
+ text: `${userMessage}\n\n---\n📦 柴米记账 MCP v${MCP_VERSION}\n\n## 完整数据\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\``,
1681
1455
  },
1682
1456
  ],
1683
1457
  };
@@ -1784,24 +1558,11 @@ ${'='.repeat(60)}
1784
1558
  };
1785
1559
  }
1786
1560
 
1787
- // 区分开发/生产环境,避免泄露内部错误信息
1788
- const errorMessage = process.env.NODE_ENV === 'development'
1789
- ? `Error: ${error.message}`
1790
- : '操作失败,请稍后重试或联系客服';
1791
-
1792
- // 记录完整错误到日志(用于调试)
1793
- console.error('Tool execution error:', {
1794
- tool: request.params.name,
1795
- error: error.message,
1796
- stack: error.stack,
1797
- timestamp: new Date().toISOString()
1798
- });
1799
-
1800
1561
  return {
1801
1562
  content: [
1802
1563
  {
1803
1564
  type: 'text',
1804
- text: errorMessage,
1565
+ text: `Error: ${error.message}`,
1805
1566
  },
1806
1567
  ],
1807
1568
  isError: true,
@@ -1809,31 +1570,8 @@ ${'='.repeat(60)}
1809
1570
  }
1810
1571
  });
1811
1572
 
1812
- // 安全的 JSON 序列化函数,防止循环引用和超大数据导致内存溢出
1813
- function safeStringify(obj, maxLength = 100000) {
1814
- try {
1815
- const str = JSON.stringify(obj, null, 2);
1816
- if (str.length > maxLength) {
1817
- return JSON.stringify({
1818
- ...obj,
1819
- data: '[数据过大,已省略]',
1820
- _note: `完整数据大小:${str.length} 字符`
1821
- }, null, 2);
1822
- }
1823
- return str;
1824
- } catch (error) {
1825
- return JSON.stringify({ error: '数据序列化失败' });
1826
- }
1827
- }
1828
-
1829
1573
  function sanitizeString(str, maxLength = 200) {
1830
1574
  if (!str || typeof str !== 'string') return '';
1831
-
1832
- // 硬性限制:输入不能超过 maxLength 的 10 倍,防止超大字符串导致内存溢出
1833
- if (str.length > maxLength * 10) {
1834
- throw new Error(`输入过长,最大允许 ${maxLength} 字符`);
1835
- }
1836
-
1837
1575
  return str.replace(/<[^>]*>/g, '').substring(0, maxLength);
1838
1576
  }
1839
1577
 
@@ -2079,33 +1817,6 @@ let authState = {
2079
1817
  };
2080
1818
 
2081
1819
  async function main() {
2082
- // 首次启动时自动获取配置
2083
- await initializeConfig();
2084
-
2085
- // 加载环境变量后赋值
2086
- CHAIMI_API_SECRET = process.env.CHAIMI_API_SECRET;
2087
- MCP_HUB_URL = process.env.MCP_HUB_URL;
2088
- MCP_PROMPT_URL = process.env.MCP_PROMPT_URL;
2089
- MCP_OAUTH_URL = process.env.MCP_OAUTH_URL;
2090
-
2091
- // 验证必需的环境变量
2092
- if (!CHAIMI_API_SECRET) {
2093
- console.error('❌ 错误:缺少必需的环境变量 CHAIMI_API_SECRET');
2094
- console.error(' 配置文件可能未正确生成,请检查 .env.development 文件');
2095
- console.error(' 或手动运行:node setup.js');
2096
- process.exit(1);
2097
- }
2098
-
2099
- // 验证云函数 URL 配置
2100
- if (!MCP_HUB_URL || !MCP_PROMPT_URL || !MCP_OAUTH_URL) {
2101
- console.error('❌ 错误:缺少云函数 URL 配置');
2102
- console.error(' 请在 .env 文件中配置以下环境变量:');
2103
- console.error(' MCP_HUB_URL=https://your-cloud-function-url/mcpHub-mcp');
2104
- console.error(' MCP_PROMPT_URL=https://your-cloud-function-url/mcpPrompt');
2105
- console.error(' MCP_OAUTH_URL=https://your-cloud-function-url/mcpOAuth');
2106
- process.exit(1);
2107
- }
2108
-
2109
1820
  await initOAuthManager();
2110
1821
 
2111
1822
  const transport = new StdioServerTransport();
@@ -2261,13 +1972,8 @@ function sanitizeLogParams(params) {
2261
1972
  }
2262
1973
 
2263
1974
  // 注意:使用 console.error,避免污染 stdout(MCP 协议通信使用 stdout)
2264
- function startServer() {
2265
- main().catch((err) => {
2266
- console.error('❌ MCP Server 启动失败:', err.message);
2267
- console.error('堆栈:', err.stack);
2268
- process.exit(1);
2269
- });
2270
- }
2271
-
2272
- // 启动服务器
2273
- startServer();
1975
+ main().catch((err) => {
1976
+ console.error('❌ MCP Server 启动失败:', err.message);
1977
+ console.error('堆栈:', err.stack);
1978
+ process.exit(1);
1979
+ });
@@ -1,49 +0,0 @@
1
- /**
2
- * 数据校验工具函数
3
- */
4
-
5
- /**
6
- * 校验日期合理性
7
- * @param {number|string} dateInput - 日期(毫秒级时间戳或日期字符串)
8
- * @param {string} fieldName - 字段名称(用于错误提示)
9
- * @returns {Object} { valid: boolean, error?: string, message?: string }
10
- */
11
- function validateDate(dateInput, fieldName = '日期') {
12
- const inputDate = new Date(dateInput);
13
- const now = new Date();
14
- const sixtyYearsAgo = new Date();
15
- sixtyYearsAgo.setFullYear(now.getFullYear() - 60);
16
-
17
- // 校验 1:日期格式是否有效
18
- if (isNaN(inputDate.getTime())) {
19
- return {
20
- valid: false,
21
- error: '日期格式无效',
22
- message: `❌ ${fieldName}格式无效,请使用毫秒级时间戳(13位数字)`
23
- };
24
- }
25
-
26
- // 校验 2:日期不能是未来时间
27
- if (inputDate > now) {
28
- return {
29
- valid: false,
30
- error: '日期不能是未来时间',
31
- message: `❌ ${fieldName}不能是未来时间`
32
- };
33
- }
34
-
35
- // 校验 3:日期不能是 60 年前
36
- if (inputDate < sixtyYearsAgo) {
37
- return {
38
- valid: false,
39
- error: '日期不能是60年前',
40
- message: `❌ ${fieldName}不能是60年前`
41
- };
42
- }
43
-
44
- return { valid: true };
45
- }
46
-
47
- module.exports = {
48
- validateDate
49
- };