mcp-log-query-server 3.4.2 → 3.5.1
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 +66 -12
- package/index.js +11 -10
- package/package.json +1 -1
- package/ssh-client.js +73 -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,59 @@ export const SERVICES = {
|
|
|
81
81
|
logFile: 'normal.log',
|
|
82
82
|
aliases: ['core', '核心']
|
|
83
83
|
},
|
|
84
|
-
'clife-senior-
|
|
85
|
-
name: 'clife-senior-
|
|
86
|
-
description: '
|
|
84
|
+
'clife-senior-crm': {
|
|
85
|
+
name: 'clife-senior-crm',
|
|
86
|
+
description: '养老CRM客户关系管理服务',
|
|
87
87
|
namespace: 'saas-itest',
|
|
88
|
-
podPattern: 'clife-senior-
|
|
89
|
-
logPath: '/www/logs/clife-senior-
|
|
88
|
+
podPattern: 'clife-senior-crm-app',
|
|
89
|
+
logPath: '/www/logs/clife-senior-crm-app/normal_logs',
|
|
90
90
|
logFile: 'normal.log',
|
|
91
|
-
aliases: ['
|
|
91
|
+
aliases: ['crm', '客户关系', '客户管理']
|
|
92
|
+
},
|
|
93
|
+
'clife-senior-device-scene-base': {
|
|
94
|
+
name: 'clife-senior-device-scene-base',
|
|
95
|
+
description: '养老设备场景基础服务',
|
|
96
|
+
namespace: 'saas-itest',
|
|
97
|
+
podPattern: 'clife-senior-device-scene-base-service',
|
|
98
|
+
logPath: '/www/logs/clife-senior-device-scene-base-service/normal_logs',
|
|
99
|
+
logFile: 'normal.log',
|
|
100
|
+
aliases: ['device-scene-base', 'scene-base', '设备场景基础']
|
|
101
|
+
},
|
|
102
|
+
'clife-senior-device-manage': {
|
|
103
|
+
name: 'clife-senior-device-manage',
|
|
104
|
+
description: '养老设备管理服务',
|
|
105
|
+
namespace: 'saas-itest',
|
|
106
|
+
podPattern: 'clife-senior-device-manage-service',
|
|
107
|
+
logPath: '/www/logs/clife-senior-device-manage-service/normal_logs',
|
|
108
|
+
logFile: 'normal.log',
|
|
109
|
+
aliases: ['device-manage', '设备管理']
|
|
110
|
+
},
|
|
111
|
+
'clife-senior-device-iot5-source': {
|
|
112
|
+
name: 'clife-senior-device-iot5-source',
|
|
113
|
+
description: '养老设备IoT5数据源服务',
|
|
114
|
+
namespace: 'saas-itest',
|
|
115
|
+
podPattern: 'clife-senior-device-iot5-source-service',
|
|
116
|
+
logPath: '/www/logs/clife-senior-device-iot5-source-service/normal_logs',
|
|
117
|
+
logFile: 'normal.log',
|
|
118
|
+
aliases: ['device-iot5-source', 'device-iot5', 'iot5', 'iot5-source', '设备IoT5', '设备IoT5数据源']
|
|
119
|
+
},
|
|
120
|
+
'clife-senior-device-simulation-source': {
|
|
121
|
+
name: 'clife-senior-device-simulation-source',
|
|
122
|
+
description: '养老设备模拟数据源服务',
|
|
123
|
+
namespace: 'saas-itest',
|
|
124
|
+
podPattern: 'clife-senior-device-simulation-source-service',
|
|
125
|
+
logPath: '/www/logs/clife-senior-device-simulation-source-service/normal_logs',
|
|
126
|
+
logFile: 'normal.log',
|
|
127
|
+
aliases: ['device-simulation-source', 'device-simulation', 'simulation-source', 'simulation', '设备模拟', '模拟数据源']
|
|
128
|
+
},
|
|
129
|
+
'clife-senior-device-third-source': {
|
|
130
|
+
name: 'clife-senior-device-third-source',
|
|
131
|
+
description: '养老设备第三方数据源服务',
|
|
132
|
+
namespace: 'saas-itest',
|
|
133
|
+
podPattern: 'clife-senior-device-third-source-app',
|
|
134
|
+
logPath: '/www/logs/clife-senior-device-third-source-app/normal_logs',
|
|
135
|
+
logFile: 'normal.log',
|
|
136
|
+
aliases: ['device-third-source', 'third-source', '设备第三方数据源', '第三方数据源']
|
|
92
137
|
},
|
|
93
138
|
'clife-senior-device-warn': {
|
|
94
139
|
name: 'clife-senior-device-warn',
|
|
@@ -220,10 +265,10 @@ export const SERVICES = {
|
|
|
220
265
|
name: 'clife-senior-open',
|
|
221
266
|
description: '养老开放平台服务',
|
|
222
267
|
namespace: 'saas-itest',
|
|
223
|
-
podPattern: 'clife-senior-open-
|
|
224
|
-
logPath: '/www/logs/clife-senior-open-
|
|
268
|
+
podPattern: 'clife-senior-open-service',
|
|
269
|
+
logPath: '/www/logs/clife-senior-open-service/normal_logs',
|
|
225
270
|
logFile: 'normal.log',
|
|
226
|
-
aliases: ['open', '开放平台']
|
|
271
|
+
aliases: ['open', 'open-service', '开放平台']
|
|
227
272
|
},
|
|
228
273
|
'clife-senior-optimal-aging': {
|
|
229
274
|
name: 'clife-senior-optimal-aging',
|
|
@@ -324,6 +369,15 @@ export const SERVICES = {
|
|
|
324
369
|
logFile: 'normal.log',
|
|
325
370
|
aliases: ['station-assistant-bff', 'station-assistant', '驿站助手', '驿站BFF']
|
|
326
371
|
},
|
|
372
|
+
'clife-senior-station-cmini-bff': {
|
|
373
|
+
name: 'clife-senior-station-cmini-bff',
|
|
374
|
+
description: '养老驿站小程序BFF服务',
|
|
375
|
+
namespace: 'saas-itest',
|
|
376
|
+
podPattern: 'clife-senior-station-cmini-bff-app',
|
|
377
|
+
logPath: '/www/logs/clife-senior-station-cmini-bff-app/normal_logs',
|
|
378
|
+
logFile: 'normal.log',
|
|
379
|
+
aliases: ['station-cmini-bff', 'station-cmini', 'cmini-bff', '驿站小程序', '驿站小程序BFF']
|
|
380
|
+
},
|
|
327
381
|
'clife-senior-third-bff': {
|
|
328
382
|
name: 'clife-senior-third-bff',
|
|
329
383
|
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.1
|
|
59
|
+
version: '3.5.1',
|
|
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 {
|
|
@@ -843,7 +844,7 @@ async function main() {
|
|
|
843
844
|
|
|
844
845
|
const transport = new StdioServerTransport();
|
|
845
846
|
await server.connect(transport);
|
|
846
|
-
console.error('[MCP] Log Query Server v3.
|
|
847
|
+
console.error('[MCP] Log Query Server v3.5.1 已启动 (超时保护 + SSH 并发限制 + 排队超时 + 进程不自杀 + stdin 优雅关闭)');
|
|
847
848
|
}
|
|
848
849
|
|
|
849
850
|
main().catch((error) => {
|
package/package.json
CHANGED
package/ssh-client.js
CHANGED
|
@@ -16,6 +16,67 @@
|
|
|
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 SSH_ACQUIRE_TIMEOUT = parseInt(process.env.SSH_ACQUIRE_TIMEOUT || '60000', 10);
|
|
26
|
+
|
|
27
|
+
const _sshSem = {
|
|
28
|
+
active: 0,
|
|
29
|
+
queue: [],
|
|
30
|
+
max: SSH_MAX_CONCURRENT,
|
|
31
|
+
queueMax: SSH_QUEUE_MAX,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 获取 SSH 信号量槽位
|
|
36
|
+
* @param {number} [timeoutMs] - 排队超时(默认 60s),防止无限等
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
function sshAcquire(timeoutMs = SSH_ACQUIRE_TIMEOUT) {
|
|
40
|
+
if (_sshSem.active < _sshSem.max) {
|
|
41
|
+
_sshSem.active++;
|
|
42
|
+
console.error(`[SSH-Sem] acquire 直接通过 (active=${_sshSem.active}/${_sshSem.max}, queue=${_sshSem.queue.length})`);
|
|
43
|
+
return Promise.resolve();
|
|
44
|
+
}
|
|
45
|
+
if (_sshSem.queue.length >= _sshSem.queueMax) {
|
|
46
|
+
return Promise.reject(new Error(`SSH 并发队列已满(>${_sshSem.queueMax}),请稍后重试`));
|
|
47
|
+
}
|
|
48
|
+
console.error(`[SSH-Sem] acquire 进入排队 (active=${_sshSem.active}/${_sshSem.max}, queue=${_sshSem.queue.length + 1}, timeout=${timeoutMs}ms)`);
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
let settled = false;
|
|
51
|
+
const enterQueue = () => {
|
|
52
|
+
if (settled) return; // 已超时,放弃并立刻腾位给下一个
|
|
53
|
+
settled = true;
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
_sshSem.active++;
|
|
56
|
+
console.error(`[SSH-Sem] acquire 出队获得槽位 (active=${_sshSem.active}/${_sshSem.max}, queue=${_sshSem.queue.length})`);
|
|
57
|
+
resolve();
|
|
58
|
+
};
|
|
59
|
+
_sshSem.queue.push(enterQueue);
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
if (settled) return;
|
|
62
|
+
settled = true;
|
|
63
|
+
// 从队列里移除自己的 entry
|
|
64
|
+
const idx = _sshSem.queue.indexOf(enterQueue);
|
|
65
|
+
if (idx >= 0) _sshSem.queue.splice(idx, 1);
|
|
66
|
+
console.error(`[SSH-Sem] acquire 排队超时 (${timeoutMs}ms, active=${_sshSem.active}/${_sshSem.max}, queue=${_sshSem.queue.length})`);
|
|
67
|
+
reject(new Error(`SSH 排队等待超时 (${timeoutMs}ms):前面请求卡住,或并发过高。可调整 SSH_MAX_CONCURRENT / SSH_ACQUIRE_TIMEOUT`));
|
|
68
|
+
}, timeoutMs);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sshRelease() {
|
|
73
|
+
_sshSem.active = Math.max(0, _sshSem.active - 1);
|
|
74
|
+
// 超时的 entry 已在 timer 里从 queue 移除,这里 shift 到的都是活的
|
|
75
|
+
const next = _sshSem.queue.shift();
|
|
76
|
+
if (next) next();
|
|
77
|
+
console.error(`[SSH-Sem] release (active=${_sshSem.active}/${_sshSem.max}, queue=${_sshSem.queue.length})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
19
80
|
/**
|
|
20
81
|
* 执行日志查询
|
|
21
82
|
* @param {Object} service - 服务配置
|
|
@@ -26,7 +87,9 @@ import { JUMP_HOST, K8S_SERVER, DEFAULTS } from './config.js';
|
|
|
26
87
|
export async function queryLog(service, command, options = {}) {
|
|
27
88
|
const timeout = options.timeout || DEFAULTS.timeout;
|
|
28
89
|
|
|
29
|
-
|
|
90
|
+
await sshAcquire();
|
|
91
|
+
try {
|
|
92
|
+
return await new Promise((resolve, reject) => {
|
|
30
93
|
const conn = new Client();
|
|
31
94
|
let buffer = ''; // 累积所有输出
|
|
32
95
|
let timeoutId;
|
|
@@ -115,6 +178,9 @@ export async function queryLog(service, command, options = {}) {
|
|
|
115
178
|
readyTimeout: DEFAULTS.connectTimeout
|
|
116
179
|
});
|
|
117
180
|
});
|
|
181
|
+
} finally {
|
|
182
|
+
sshRelease();
|
|
183
|
+
}
|
|
118
184
|
}
|
|
119
185
|
|
|
120
186
|
/**
|
|
@@ -201,7 +267,9 @@ export async function testConnection() {
|
|
|
201
267
|
export async function executeKubectl(kubectlCommand, options = {}) {
|
|
202
268
|
const timeout = options.timeout || DEFAULTS.timeout;
|
|
203
269
|
|
|
204
|
-
|
|
270
|
+
await sshAcquire();
|
|
271
|
+
try {
|
|
272
|
+
return await new Promise((resolve, reject) => {
|
|
205
273
|
const conn = new Client();
|
|
206
274
|
let buffer = '';
|
|
207
275
|
let timeoutId;
|
|
@@ -289,4 +357,7 @@ export async function executeKubectl(kubectlCommand, options = {}) {
|
|
|
289
357
|
readyTimeout: DEFAULTS.connectTimeout
|
|
290
358
|
});
|
|
291
359
|
});
|
|
360
|
+
} finally {
|
|
361
|
+
sshRelease();
|
|
362
|
+
}
|
|
292
363
|
}
|