chaimi-keep-mcp 3.1.45-beta.0 → 3.1.45-beta.2

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.0",
3
+ "version": "3.1.45-beta.2",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
+ "utils/",
11
12
  "server.js",
12
13
  "oauth.js",
13
14
  "SKILL.md",
package/server.js CHANGED
@@ -9,23 +9,99 @@
9
9
  const NODE_ENV = process.env.NODE_ENV || 'development';
10
10
  const envFile = `.env.${NODE_ENV}`;
11
11
 
12
- try {
13
- const dotenv = require('dotenv');
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() {
14
55
  const fs = require('fs');
15
56
  const path = require('path');
16
-
17
57
  const envPath = path.join(__dirname, envFile);
18
58
 
59
+ // 如果配置文件已存在,直接返回
19
60
  if (fs.existsSync(envPath)) {
20
- dotenv.config({ path: envPath });
21
- console.log(`✅ 已加载 ${NODE_ENV} 环境配置`);
22
- } else {
23
- console.warn(`⚠️ 警告:${envFile} 文件不存在,请运行 setup.js 生成配置`);
24
- // 尝试加载默认 .env 文件
25
- dotenv.config();
61
+ return;
26
62
  }
27
- } catch (e) {
28
- console.warn('⚠️ 警告:dotenv 未安装或加载失败');
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
+ // 使用默认配置(硬编码URL,向后兼容)
73
+ config = {
74
+ MCP_HUB_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp',
75
+ MCP_PROMPT_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt',
76
+ MCP_OAUTH_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth',
77
+ CHAIMI_API_SECRET: 'chaimi-mcp-secret-2024',
78
+ JWT_SECRET: 'chaimi-jwt-secret-2024'
79
+ };
80
+ }
81
+
82
+ // 生成配置文件
83
+ const envContent = `# 柴米记账 MCP Server - ${NODE_ENV} 环境配置
84
+ # 自动生成时间:${new Date().toISOString()}
85
+
86
+ # 云函数 URL
87
+ MCP_HUB_URL=${config.MCP_HUB_URL}
88
+ MCP_PROMPT_URL=${config.MCP_PROMPT_URL}
89
+ MCP_OAUTH_URL=${config.MCP_OAUTH_URL}
90
+
91
+ # API 签名密钥
92
+ CHAIMI_API_SECRET=${config.CHAIMI_API_SECRET}
93
+
94
+ # JWT 密钥
95
+ JWT_SECRET=${config.JWT_SECRET}
96
+ `;
97
+
98
+ fs.writeFileSync(envPath, envContent, { mode: 0o600 });
99
+ console.error('✅ 配置文件已自动生成!');
100
+ console.error(` 文件位置:${envPath}`);
101
+ console.error('');
102
+
103
+ // 重新加载环境变量
104
+ require('dotenv').config({ path: envPath });
29
105
  }
30
106
 
31
107
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -81,22 +157,8 @@ function getOrCreateSecretKey() {
81
157
  const SECRET_KEY = getOrCreateSecretKey();
82
158
 
83
159
  // API 签名密钥(用于验证请求来自合法 MCP Server)
84
- const CHAIMI_API_SECRET = process.env.CHAIMI_API_SECRET;
85
-
86
- // URL 加密密钥
87
- const URL_ENCRYPT_KEY = process.env.URL_ENCRYPT_KEY;
88
-
89
- // 验证必需的环境变量
90
- if (!CHAIMI_API_SECRET || !URL_ENCRYPT_KEY) {
91
- console.error('❌ 错误:缺少必需的环境变量');
92
- console.error(' 请运行以下命令生成配置:');
93
- console.error(' node setup.js');
94
- console.error('');
95
- console.error(' 然后使用以下命令启动:');
96
- console.error(' NODE_ENV=development node server.js # 开发环境');
97
- console.error(' NODE_ENV=production node server.js # 生产环境');
98
- process.exit(1);
99
- }
160
+ // 注意:initializeConfig() 后会自动加载,这里先声明变量
161
+ let CHAIMI_API_SECRET;
100
162
 
101
163
  // ==================== 加密/解密函数 ====================
102
164
 
@@ -136,54 +198,11 @@ function decrypt(encryptedText, secretKey) {
136
198
  return decrypted;
137
199
  }
138
200
 
139
- // 旧版 XOR 解密(向后兼容)
140
- function decryptOldXOR(encrypted, key) {
141
- const hex = encrypted.replace('enc:v1:', '');
142
- let result = '';
143
- for (let i = 0; i < hex.length; i += 2) {
144
- const byte = parseInt(hex.substr(i, 2), 16);
145
- result += String.fromCharCode(byte ^ key.charCodeAt((i / 2) % key.length));
146
- }
147
- return result;
148
- }
149
-
150
- // 加密的云函数 URL
151
- const ENCRYPTED_URLS = {
152
- hub: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f571320140b40044e05',
153
- oauth: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f571327201c1901',
154
- prompt: 'enc:v1:0b1c15191e53025a1100421e0148000057545156020903080f1d431054180f484819030203035158595043085d5801044c0502114c5b1e53441346150a01065811100d5e0e4b1a425f1f5f5713381306001959'
155
- };
156
-
157
- // 解密 URL 函数(兼容新旧版本)
158
- function decryptUrl(encrypted, key) {
159
- // 新版本(AES-256-GCM)
160
- if (encrypted.startsWith('enc:v2:')) {
161
- const parts = encrypted.replace('enc:v2:', '').split(':');
162
- const iv = Buffer.from(parts[0], 'hex');
163
- const authTag = Buffer.from(parts[1], 'hex');
164
- const ciphertext = parts[2];
165
-
166
- const keyBuffer = crypto.scryptSync(key, 'salt', 32);
167
- const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuffer, iv);
168
- decipher.setAuthTag(authTag);
169
-
170
- let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
171
- decrypted += decipher.final('utf8');
172
- return decrypted;
173
- }
174
-
175
- // 旧版本(XOR)- 向后兼容
176
- if (encrypted.startsWith('enc:v1:')) {
177
- return decryptOldXOR(encrypted, key);
178
- }
179
-
180
- throw new Error('不支持的加密格式');
181
- }
182
-
183
- // 配置 - 微信云函数(运行时解密)
184
- const MCP_HUB_URL = process.env.MCP_HUB_URL || decryptUrl(ENCRYPTED_URLS.hub, URL_ENCRYPT_KEY);
185
- const MCP_PROMPT_URL = process.env.MCP_PROMPT_URL || decryptUrl(ENCRYPTED_URLS.prompt, URL_ENCRYPT_KEY);
186
- const MCP_OAUTH_URL = process.env.MCP_OAUTH_URL || decryptUrl(ENCRYPTED_URLS.oauth, URL_ENCRYPT_KEY);
201
+ // ==================== 云函数 URL 配置 ====================
202
+ // 注意:initializeConfig() 后会自动加载,这里先声明变量
203
+ let MCP_HUB_URL;
204
+ let MCP_PROMPT_URL;
205
+ let MCP_OAUTH_URL;
187
206
 
188
207
  // Token 缓存(加密存储)
189
208
  let cachedEncryptedToken = null;
@@ -2058,6 +2077,33 @@ let authState = {
2058
2077
  };
2059
2078
 
2060
2079
  async function main() {
2080
+ // 首次启动时自动获取配置
2081
+ await initializeConfig();
2082
+
2083
+ // 加载环境变量后赋值
2084
+ CHAIMI_API_SECRET = process.env.CHAIMI_API_SECRET;
2085
+ MCP_HUB_URL = process.env.MCP_HUB_URL;
2086
+ MCP_PROMPT_URL = process.env.MCP_PROMPT_URL;
2087
+ MCP_OAUTH_URL = process.env.MCP_OAUTH_URL;
2088
+
2089
+ // 验证必需的环境变量
2090
+ if (!CHAIMI_API_SECRET) {
2091
+ console.error('❌ 错误:缺少必需的环境变量 CHAIMI_API_SECRET');
2092
+ console.error(' 配置文件可能未正确生成,请检查 .env.development 文件');
2093
+ console.error(' 或手动运行:node setup.js');
2094
+ process.exit(1);
2095
+ }
2096
+
2097
+ // 验证云函数 URL 配置
2098
+ if (!MCP_HUB_URL || !MCP_PROMPT_URL || !MCP_OAUTH_URL) {
2099
+ console.error('❌ 错误:缺少云函数 URL 配置');
2100
+ console.error(' 请在 .env 文件中配置以下环境变量:');
2101
+ console.error(' MCP_HUB_URL=https://your-cloud-function-url/mcpHub-mcp');
2102
+ console.error(' MCP_PROMPT_URL=https://your-cloud-function-url/mcpPrompt');
2103
+ console.error(' MCP_OAUTH_URL=https://your-cloud-function-url/mcpOAuth');
2104
+ process.exit(1);
2105
+ }
2106
+
2061
2107
  await initOAuthManager();
2062
2108
 
2063
2109
  const transport = new StdioServerTransport();
@@ -2213,8 +2259,13 @@ function sanitizeLogParams(params) {
2213
2259
  }
2214
2260
 
2215
2261
  // 注意:使用 console.error,避免污染 stdout(MCP 协议通信使用 stdout)
2216
- main().catch((err) => {
2217
- console.error('❌ MCP Server 启动失败:', err.message);
2218
- console.error('堆栈:', err.stack);
2219
- process.exit(1);
2220
- });
2262
+ function startServer() {
2263
+ main().catch((err) => {
2264
+ console.error('❌ MCP Server 启动失败:', err.message);
2265
+ console.error('堆栈:', err.stack);
2266
+ process.exit(1);
2267
+ });
2268
+ }
2269
+
2270
+ // 启动服务器
2271
+ startServer();
@@ -0,0 +1,49 @@
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
+ };