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 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-archives-app',
53
- logPath: '/www/logs/clife-senior-archives-app/normal_logs',
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-center': {
85
- name: 'clife-senior-device-center',
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-center-app',
89
- logPath: '/www/logs/clife-senior-device-center-app/normal_logs',
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-center', '设备中心']
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-app',
224
- logPath: '/www/logs/clife-senior-open-app/normal_logs',
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; // MCP 请求兜底超时 60s
39
- const WATCHDOG_TIMEOUT = 120000; // 进程看门狗 120s,卡死则强制退出
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) => { console.error('[uncaughtException]', err); process.exit(1); });
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.0',
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} 卡死超过 ${WATCHDOG_TIMEOUT}ms,强制退出进程`);
345
- process.exit(1);
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.2.0 已启动 (支持超时保护 + 进程看门狗)');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-log-query-server",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "MCP Server for querying server logs via SSH jump host and Grafana Loki API",
5
5
  "main": "index.js",
6
6
  "type": "module",
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
- return new Promise((resolve, reject) => {
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
- return new Promise((resolve, reject) => {
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
  }