lark-mcp-server 0.0.7 → 0.0.10
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/index.js +127 -3
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -5,6 +5,13 @@ import EventSource from 'eventsource';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
// 获取当前包版本
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
13
|
+
const CURRENT_VERSION = packageJson.version;
|
|
14
|
+
const PACKAGE_NAME = packageJson.name;
|
|
8
15
|
|
|
9
16
|
// 解析命令行参数
|
|
10
17
|
const args = process.argv.slice(2);
|
|
@@ -16,7 +23,7 @@ for (const arg of args) {
|
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
const SERVER_URL = params.server || 'https://basic.dev.peblla.top';
|
|
26
|
+
const SERVER_URL = params.server || 'https://basic.dev.peblla.top/api/feishu';
|
|
20
27
|
const USER_NAME = params.user || 'default';
|
|
21
28
|
|
|
22
29
|
// 日志输出到 stderr,不影响 stdio 通信
|
|
@@ -26,6 +33,79 @@ const log = (...args) => console.error('[lark-mcp-proxy]', ...args);
|
|
|
26
33
|
const TOKEN_DIR = path.join(os.homedir(), '.lark-mcp');
|
|
27
34
|
const TOKEN_FILE = path.join(TOKEN_DIR, 'tokens.json');
|
|
28
35
|
|
|
36
|
+
// 版本检测配置
|
|
37
|
+
const VERSION_CHECK_FILE = path.join(TOKEN_DIR, 'version-check.json');
|
|
38
|
+
const VERSION_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 小时检查一次
|
|
39
|
+
|
|
40
|
+
// 检查是否需要更新
|
|
41
|
+
async function checkForUpdates() {
|
|
42
|
+
try {
|
|
43
|
+
// 检查上次检查时间,避免频繁请求
|
|
44
|
+
let lastCheck = 0;
|
|
45
|
+
if (fs.existsSync(VERSION_CHECK_FILE)) {
|
|
46
|
+
const data = JSON.parse(fs.readFileSync(VERSION_CHECK_FILE, 'utf-8'));
|
|
47
|
+
lastCheck = data.lastCheck || 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (Date.now() - lastCheck < VERSION_CHECK_INTERVAL) {
|
|
51
|
+
return; // 24小时内已检查过
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 从 npm registry 获取最新版本
|
|
55
|
+
const res = await fetch(`https://registry.npmmirror.com/${PACKAGE_NAME}/latest`, {
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!res.ok) return;
|
|
60
|
+
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
const latestVersion = data.version;
|
|
63
|
+
|
|
64
|
+
// 保存检查时间
|
|
65
|
+
if (!fs.existsSync(TOKEN_DIR)) {
|
|
66
|
+
fs.mkdirSync(TOKEN_DIR, { recursive: true, mode: 0o700 });
|
|
67
|
+
}
|
|
68
|
+
fs.writeFileSync(VERSION_CHECK_FILE, JSON.stringify({
|
|
69
|
+
lastCheck: Date.now(),
|
|
70
|
+
latestVersion,
|
|
71
|
+
}), { mode: 0o600 });
|
|
72
|
+
|
|
73
|
+
// 比较版本
|
|
74
|
+
if (latestVersion && latestVersion !== CURRENT_VERSION) {
|
|
75
|
+
if (compareVersions(latestVersion, CURRENT_VERSION) > 0) {
|
|
76
|
+
log('');
|
|
77
|
+
log('╔════════════════════════════════════════════════════════════╗');
|
|
78
|
+
log('║ 🚀 New version available! ║');
|
|
79
|
+
log(`║ Current: ${CURRENT_VERSION.padEnd(10)} → Latest: ${latestVersion.padEnd(10)} ║`);
|
|
80
|
+
log('║ ║');
|
|
81
|
+
log('║ To update, run: ║');
|
|
82
|
+
log('║ npx lark-mcp-server@latest --user=YOUR_USER ║');
|
|
83
|
+
log('║ ║');
|
|
84
|
+
log('║ Or clear npx cache: ║');
|
|
85
|
+
log('║ rm -rf ~/.npm/_npx/*lark* && restart your IDE ║');
|
|
86
|
+
log('╚════════════════════════════════════════════════════════════╝');
|
|
87
|
+
log('');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// 版本检查失败不影响正常使用
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 版本比较函数
|
|
96
|
+
function compareVersions(v1, v2) {
|
|
97
|
+
const parts1 = v1.split('.').map(Number);
|
|
98
|
+
const parts2 = v2.split('.').map(Number);
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
101
|
+
const p1 = parts1[i] || 0;
|
|
102
|
+
const p2 = parts2[i] || 0;
|
|
103
|
+
if (p1 > p2) return 1;
|
|
104
|
+
if (p1 < p2) return -1;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
29
109
|
// 加载本地 token
|
|
30
110
|
function loadLocalToken(userName) {
|
|
31
111
|
try {
|
|
@@ -108,12 +188,43 @@ async function refreshLocalToken() {
|
|
|
108
188
|
}
|
|
109
189
|
}
|
|
110
190
|
|
|
191
|
+
// 优雅退出
|
|
192
|
+
function gracefulExit(reason) {
|
|
193
|
+
log(`Exiting: ${reason}`);
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 处理退出信号
|
|
198
|
+
process.on('SIGTERM', () => gracefulExit('SIGTERM'));
|
|
199
|
+
process.on('SIGINT', () => gracefulExit('SIGINT'));
|
|
200
|
+
process.on('SIGHUP', () => gracefulExit('SIGHUP'));
|
|
201
|
+
|
|
202
|
+
// 检测父进程是否存活(每 30 秒检查一次)
|
|
203
|
+
const PARENT_CHECK_INTERVAL = 30000;
|
|
204
|
+
const parentPid = process.ppid;
|
|
205
|
+
|
|
206
|
+
const parentCheckTimer = setInterval(() => {
|
|
207
|
+
try {
|
|
208
|
+
process.kill(parentPid, 0);
|
|
209
|
+
} catch (e) {
|
|
210
|
+
gracefulExit('Parent process exited');
|
|
211
|
+
}
|
|
212
|
+
}, PARENT_CHECK_INTERVAL);
|
|
213
|
+
|
|
214
|
+
// 让定时器不阻止进程退出
|
|
215
|
+
parentCheckTimer.unref();
|
|
216
|
+
|
|
111
217
|
// 存储 messages endpoint 和授权 URL
|
|
112
218
|
let messagesEndpoint = null;
|
|
113
219
|
let authUrl = null;
|
|
114
220
|
let sseConnected = false;
|
|
115
221
|
let pendingRequests = new Map();
|
|
116
222
|
|
|
223
|
+
// SSE 重连配置
|
|
224
|
+
const MAX_RECONNECT_ATTEMPTS = 10;
|
|
225
|
+
const RECONNECT_INTERVAL = 3000;
|
|
226
|
+
let reconnectAttempts = 0;
|
|
227
|
+
|
|
117
228
|
function normalizeEndpoint(endpoint) {
|
|
118
229
|
try {
|
|
119
230
|
const url = new URL(endpoint);
|
|
@@ -139,6 +250,7 @@ function connectSSE() {
|
|
|
139
250
|
messagesEndpoint = normalizeEndpoint(event.data);
|
|
140
251
|
log(`Got endpoint: ${messagesEndpoint}`);
|
|
141
252
|
sseConnected = true;
|
|
253
|
+
reconnectAttempts = 0; // 连接成功,重置重试计数
|
|
142
254
|
});
|
|
143
255
|
|
|
144
256
|
// 监听 token 事件(OAuth 回调后服务器推送)
|
|
@@ -198,7 +310,15 @@ function connectSSE() {
|
|
|
198
310
|
log('SSE error, reconnecting...');
|
|
199
311
|
sseConnected = false;
|
|
200
312
|
es.close();
|
|
201
|
-
|
|
313
|
+
|
|
314
|
+
reconnectAttempts++;
|
|
315
|
+
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
316
|
+
log(`Max reconnect attempts (${MAX_RECONNECT_ATTEMPTS}) reached, exiting`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
log(`Reconnect attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS}`);
|
|
321
|
+
setTimeout(connectSSE, RECONNECT_INTERVAL);
|
|
202
322
|
};
|
|
203
323
|
}
|
|
204
324
|
|
|
@@ -328,6 +448,10 @@ process.stdin.on('end', () => {
|
|
|
328
448
|
});
|
|
329
449
|
|
|
330
450
|
// 启动
|
|
331
|
-
log(`Starting proxy for user: ${USER_NAME}`);
|
|
451
|
+
log(`Starting proxy for user: ${USER_NAME} (v${CURRENT_VERSION})`);
|
|
332
452
|
log(`Server: ${SERVER_URL}`);
|
|
453
|
+
|
|
454
|
+
// 异步检查更新(不阻塞启动)
|
|
455
|
+
checkForUpdates();
|
|
456
|
+
|
|
333
457
|
connectSSE();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-mcp-server",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "飞书 MCP 本地代理 - 让 AI 编程工具读取飞书文档,Token 本地安全存储",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
+
"package.json",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|