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 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,59 @@ 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-crm': {
85
+ name: 'clife-senior-crm',
86
+ description: '养老CRM客户关系管理服务',
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-crm-app',
89
+ logPath: '/www/logs/clife-senior-crm-app/normal_logs',
90
90
  logFile: 'normal.log',
91
- aliases: ['device-center', '设备中心']
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-app',
224
- logPath: '/www/logs/clife-senior-open-app/normal_logs',
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; // 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.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} 卡死超过 ${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 {
@@ -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.2.0 已启动 (支持超时保护 + 进程看门狗 + stdin 优雅关闭)');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-log-query-server",
3
- "version": "3.4.2",
3
+ "version": "3.5.1",
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,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
- return new Promise((resolve, reject) => {
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
- return new Promise((resolve, reject) => {
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
  }