mcp-log-query-server 3.4.1 → 3.5.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/config.js +57 -12
- package/index.js +32 -10
- package/package.json +1 -1
- package/ssh-client.js +47 -2
package/config.js
CHANGED
|
@@ -49,10 +49,10 @@ export const SERVICES = {
|
|
|
49
49
|
name: 'clife-senior-archives',
|
|
50
50
|
description: '养老档案服务',
|
|
51
51
|
namespace: 'saas-itest',
|
|
52
|
-
podPattern: 'clife-senior-
|
|
53
|
-
logPath: '/www/logs/clife-senior-
|
|
52
|
+
podPattern: 'clife-senior-archive-app',
|
|
53
|
+
logPath: '/www/logs/clife-senior-archive-app/normal_logs',
|
|
54
54
|
logFile: 'normal.log',
|
|
55
|
-
aliases: ['archives', '档案']
|
|
55
|
+
aliases: ['archive', 'archives', '档案']
|
|
56
56
|
},
|
|
57
57
|
'clife-senior-assess': {
|
|
58
58
|
name: 'clife-senior-assess',
|
|
@@ -81,14 +81,50 @@ export const SERVICES = {
|
|
|
81
81
|
logFile: 'normal.log',
|
|
82
82
|
aliases: ['core', '核心']
|
|
83
83
|
},
|
|
84
|
-
'clife-senior-device-
|
|
85
|
-
name: 'clife-senior-device-
|
|
86
|
-
description: '
|
|
84
|
+
'clife-senior-device-scene-base': {
|
|
85
|
+
name: 'clife-senior-device-scene-base',
|
|
86
|
+
description: '养老设备场景基础服务',
|
|
87
87
|
namespace: 'saas-itest',
|
|
88
|
-
podPattern: 'clife-senior-device-
|
|
89
|
-
logPath: '/www/logs/clife-senior-device-
|
|
88
|
+
podPattern: 'clife-senior-device-scene-base-service',
|
|
89
|
+
logPath: '/www/logs/clife-senior-device-scene-base-service/normal_logs',
|
|
90
90
|
logFile: 'normal.log',
|
|
91
|
-
aliases: ['device-
|
|
91
|
+
aliases: ['device-scene-base', 'scene-base', '设备场景基础']
|
|
92
|
+
},
|
|
93
|
+
'clife-senior-device-manage': {
|
|
94
|
+
name: 'clife-senior-device-manage',
|
|
95
|
+
description: '养老设备管理服务',
|
|
96
|
+
namespace: 'saas-itest',
|
|
97
|
+
podPattern: 'clife-senior-device-manage-service',
|
|
98
|
+
logPath: '/www/logs/clife-senior-device-manage-service/normal_logs',
|
|
99
|
+
logFile: 'normal.log',
|
|
100
|
+
aliases: ['device-manage', '设备管理']
|
|
101
|
+
},
|
|
102
|
+
'clife-senior-device-iot5-source': {
|
|
103
|
+
name: 'clife-senior-device-iot5-source',
|
|
104
|
+
description: '养老设备IoT5数据源服务',
|
|
105
|
+
namespace: 'saas-itest',
|
|
106
|
+
podPattern: 'clife-senior-device-iot5-source-service',
|
|
107
|
+
logPath: '/www/logs/clife-senior-device-iot5-source-service/normal_logs',
|
|
108
|
+
logFile: 'normal.log',
|
|
109
|
+
aliases: ['device-iot5-source', 'device-iot5', 'iot5', 'iot5-source', '设备IoT5', '设备IoT5数据源']
|
|
110
|
+
},
|
|
111
|
+
'clife-senior-device-simulation-source': {
|
|
112
|
+
name: 'clife-senior-device-simulation-source',
|
|
113
|
+
description: '养老设备模拟数据源服务',
|
|
114
|
+
namespace: 'saas-itest',
|
|
115
|
+
podPattern: 'clife-senior-device-simulation-source-service',
|
|
116
|
+
logPath: '/www/logs/clife-senior-device-simulation-source-service/normal_logs',
|
|
117
|
+
logFile: 'normal.log',
|
|
118
|
+
aliases: ['device-simulation-source', 'device-simulation', 'simulation-source', 'simulation', '设备模拟', '模拟数据源']
|
|
119
|
+
},
|
|
120
|
+
'clife-senior-device-third-source': {
|
|
121
|
+
name: 'clife-senior-device-third-source',
|
|
122
|
+
description: '养老设备第三方数据源服务',
|
|
123
|
+
namespace: 'saas-itest',
|
|
124
|
+
podPattern: 'clife-senior-device-third-source-app',
|
|
125
|
+
logPath: '/www/logs/clife-senior-device-third-source-app/normal_logs',
|
|
126
|
+
logFile: 'normal.log',
|
|
127
|
+
aliases: ['device-third-source', 'third-source', '设备第三方数据源', '第三方数据源']
|
|
92
128
|
},
|
|
93
129
|
'clife-senior-device-warn': {
|
|
94
130
|
name: 'clife-senior-device-warn',
|
|
@@ -220,10 +256,10 @@ export const SERVICES = {
|
|
|
220
256
|
name: 'clife-senior-open',
|
|
221
257
|
description: '养老开放平台服务',
|
|
222
258
|
namespace: 'saas-itest',
|
|
223
|
-
podPattern: 'clife-senior-open-
|
|
224
|
-
logPath: '/www/logs/clife-senior-open-
|
|
259
|
+
podPattern: 'clife-senior-open-service',
|
|
260
|
+
logPath: '/www/logs/clife-senior-open-service/normal_logs',
|
|
225
261
|
logFile: 'normal.log',
|
|
226
|
-
aliases: ['open', '开放平台']
|
|
262
|
+
aliases: ['open', 'open-service', '开放平台']
|
|
227
263
|
},
|
|
228
264
|
'clife-senior-optimal-aging': {
|
|
229
265
|
name: 'clife-senior-optimal-aging',
|
|
@@ -324,6 +360,15 @@ export const SERVICES = {
|
|
|
324
360
|
logFile: 'normal.log',
|
|
325
361
|
aliases: ['station-assistant-bff', 'station-assistant', '驿站助手', '驿站BFF']
|
|
326
362
|
},
|
|
363
|
+
'clife-senior-station-cmini-bff': {
|
|
364
|
+
name: 'clife-senior-station-cmini-bff',
|
|
365
|
+
description: '养老驿站小程序BFF服务',
|
|
366
|
+
namespace: 'saas-itest',
|
|
367
|
+
podPattern: 'clife-senior-station-cmini-bff-app',
|
|
368
|
+
logPath: '/www/logs/clife-senior-station-cmini-bff-app/normal_logs',
|
|
369
|
+
logFile: 'normal.log',
|
|
370
|
+
aliases: ['station-cmini-bff', 'station-cmini', 'cmini-bff', '驿站小程序', '驿站小程序BFF']
|
|
371
|
+
},
|
|
327
372
|
'clife-senior-third-bff': {
|
|
328
373
|
name: 'clife-senior-third-bff',
|
|
329
374
|
description: '养老第三方BFF服务',
|
package/index.js
CHANGED
|
@@ -35,8 +35,8 @@ import {
|
|
|
35
35
|
} from './loki-client.js';
|
|
36
36
|
|
|
37
37
|
// 超时配置
|
|
38
|
-
const REQUEST_TIMEOUT = 60000;
|
|
39
|
-
const
|
|
38
|
+
const REQUEST_TIMEOUT = 60000; // MCP 请求兑底超时 60s(withTimeout 强制终止)
|
|
39
|
+
const WATCHDOG_WARN_TIMEOUT = 120000; // 看门狗 120s,仅记录告警(不再 process.exit)
|
|
40
40
|
|
|
41
41
|
function withTimeout(promise, ms, label) {
|
|
42
42
|
return Promise.race([
|
|
@@ -47,15 +47,16 @@ function withTimeout(promise, ms, label) {
|
|
|
47
47
|
]);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
//
|
|
50
|
+
// 进程级安全网:只记录日志,不退出进程
|
|
51
|
+
// 退出会导致 stdio 断开,整个 MCP 不可用直到 IDE 重启;单次请求错误不应拖死服务
|
|
51
52
|
process.on('unhandledRejection', (err) => console.error('[unhandledRejection]', err));
|
|
52
|
-
process.on('uncaughtException', (err) =>
|
|
53
|
+
process.on('uncaughtException', (err) => console.error('[uncaughtException]', err));
|
|
53
54
|
|
|
54
55
|
// 创建 MCP Server
|
|
55
56
|
const server = new Server(
|
|
56
57
|
{
|
|
57
58
|
name: 'mcp-log-query',
|
|
58
|
-
version: '3.
|
|
59
|
+
version: '3.5.0',
|
|
59
60
|
},
|
|
60
61
|
{
|
|
61
62
|
capabilities: {
|
|
@@ -339,11 +340,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
339
340
|
const { name, arguments: args } = request.params;
|
|
340
341
|
const startTime = Date.now();
|
|
341
342
|
|
|
342
|
-
//
|
|
343
|
+
// 看门狗:仅记录长时间未完成的请求,不再退出进程
|
|
344
|
+
// withTimeout(REQUEST_TIMEOUT) 已经保证单次请求超时会抛错
|
|
343
345
|
const watchdog = setTimeout(() => {
|
|
344
|
-
console.error(`[Watchdog] ${name}
|
|
345
|
-
|
|
346
|
-
}, WATCHDOG_TIMEOUT);
|
|
346
|
+
console.error(`[Watchdog] ${name} 仍在运行超过 ${WATCHDOG_WARN_TIMEOUT}ms(仅记录,不退出进程)`);
|
|
347
|
+
}, WATCHDOG_WARN_TIMEOUT);
|
|
347
348
|
watchdog.unref();
|
|
348
349
|
|
|
349
350
|
try {
|
|
@@ -818,11 +819,32 @@ async function handleToolCall(name, args) {
|
|
|
818
819
|
}
|
|
819
820
|
}
|
|
820
821
|
|
|
822
|
+
// 优雅关闭(对齐 auggie MCP 启动代码)
|
|
823
|
+
function gracefulShutdown() {
|
|
824
|
+
console.error('[MCP] 优雅关闭...');
|
|
825
|
+
server.close().catch(() => {});
|
|
826
|
+
// 给 close 一点时间完成,然后强制退出
|
|
827
|
+
setTimeout(() => process.exit(0), 500).unref();
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
process.on('SIGINT', gracefulShutdown);
|
|
831
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
832
|
+
|
|
821
833
|
// 启动服务器
|
|
822
834
|
async function main() {
|
|
835
|
+
// 对齐 auggie: 监听 stdin end/close,宿主进程断开时优雅关闭
|
|
836
|
+
process.stdin.on('end', () => {
|
|
837
|
+
console.error('[MCP] stdin end, initiating graceful shutdown');
|
|
838
|
+
gracefulShutdown();
|
|
839
|
+
});
|
|
840
|
+
process.stdin.on('close', () => {
|
|
841
|
+
console.error('[MCP] stdin close, initiating graceful shutdown');
|
|
842
|
+
gracefulShutdown();
|
|
843
|
+
});
|
|
844
|
+
|
|
823
845
|
const transport = new StdioServerTransport();
|
|
824
846
|
await server.connect(transport);
|
|
825
|
-
console.error('[MCP] Log Query Server v3.
|
|
847
|
+
console.error('[MCP] Log Query Server v3.5.0 已启动 (超时保护 + SSH 并发限制 + 进程不自杀 + stdin 优雅关闭)');
|
|
826
848
|
}
|
|
827
849
|
|
|
828
850
|
main().catch((error) => {
|
package/package.json
CHANGED
package/ssh-client.js
CHANGED
|
@@ -16,6 +16,41 @@
|
|
|
16
16
|
import { Client } from 'ssh2';
|
|
17
17
|
import { JUMP_HOST, K8S_SERVER, DEFAULTS } from './config.js';
|
|
18
18
|
|
|
19
|
+
// ============================================================
|
|
20
|
+
// 并发信号量:防止同时打开过多堡垒机会话被踢
|
|
21
|
+
// ============================================================
|
|
22
|
+
const SSH_MAX_CONCURRENT = parseInt(process.env.SSH_MAX_CONCURRENT || '3', 10);
|
|
23
|
+
const SSH_QUEUE_MAX = parseInt(process.env.SSH_QUEUE_MAX || '50', 10);
|
|
24
|
+
|
|
25
|
+
const _sshSem = {
|
|
26
|
+
active: 0,
|
|
27
|
+
queue: [],
|
|
28
|
+
max: SSH_MAX_CONCURRENT,
|
|
29
|
+
queueMax: SSH_QUEUE_MAX,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function sshAcquire() {
|
|
33
|
+
if (_sshSem.active < _sshSem.max) {
|
|
34
|
+
_sshSem.active++;
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
if (_sshSem.queue.length >= _sshSem.queueMax) {
|
|
38
|
+
return Promise.reject(new Error(`SSH 并发队列已满(>${_sshSem.queueMax}),请稍后重试`));
|
|
39
|
+
}
|
|
40
|
+
return new Promise(resolve => {
|
|
41
|
+
_sshSem.queue.push(() => {
|
|
42
|
+
_sshSem.active++;
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sshRelease() {
|
|
49
|
+
_sshSem.active = Math.max(0, _sshSem.active - 1);
|
|
50
|
+
const next = _sshSem.queue.shift();
|
|
51
|
+
if (next) next();
|
|
52
|
+
}
|
|
53
|
+
|
|
19
54
|
/**
|
|
20
55
|
* 执行日志查询
|
|
21
56
|
* @param {Object} service - 服务配置
|
|
@@ -26,7 +61,9 @@ import { JUMP_HOST, K8S_SERVER, DEFAULTS } from './config.js';
|
|
|
26
61
|
export async function queryLog(service, command, options = {}) {
|
|
27
62
|
const timeout = options.timeout || DEFAULTS.timeout;
|
|
28
63
|
|
|
29
|
-
|
|
64
|
+
await sshAcquire();
|
|
65
|
+
try {
|
|
66
|
+
return await new Promise((resolve, reject) => {
|
|
30
67
|
const conn = new Client();
|
|
31
68
|
let buffer = ''; // 累积所有输出
|
|
32
69
|
let timeoutId;
|
|
@@ -115,6 +152,9 @@ export async function queryLog(service, command, options = {}) {
|
|
|
115
152
|
readyTimeout: DEFAULTS.connectTimeout
|
|
116
153
|
});
|
|
117
154
|
});
|
|
155
|
+
} finally {
|
|
156
|
+
sshRelease();
|
|
157
|
+
}
|
|
118
158
|
}
|
|
119
159
|
|
|
120
160
|
/**
|
|
@@ -201,7 +241,9 @@ export async function testConnection() {
|
|
|
201
241
|
export async function executeKubectl(kubectlCommand, options = {}) {
|
|
202
242
|
const timeout = options.timeout || DEFAULTS.timeout;
|
|
203
243
|
|
|
204
|
-
|
|
244
|
+
await sshAcquire();
|
|
245
|
+
try {
|
|
246
|
+
return await new Promise((resolve, reject) => {
|
|
205
247
|
const conn = new Client();
|
|
206
248
|
let buffer = '';
|
|
207
249
|
let timeoutId;
|
|
@@ -289,4 +331,7 @@ export async function executeKubectl(kubectlCommand, options = {}) {
|
|
|
289
331
|
readyTimeout: DEFAULTS.connectTimeout
|
|
290
332
|
});
|
|
291
333
|
});
|
|
334
|
+
} finally {
|
|
335
|
+
sshRelease();
|
|
336
|
+
}
|
|
292
337
|
}
|