mcp-log-query-server 3.4.0 → 3.4.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/ssh-client.js +35 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-log-query-server",
3
- "version": "3.4.0",
3
+ "version": "3.4.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
@@ -34,32 +34,35 @@ export async function queryLog(service, command, options = {}) {
34
34
  let kubectlOutput = '';
35
35
  let collectingOutput = false;
36
36
 
37
- // 设置超时
37
+ // 设置超时 - 使用 destroy() 强制关闭
38
+ let settled = false;
38
39
  timeoutId = setTimeout(() => {
39
- conn.end();
40
- reject(new Error(`命令执行超时 (${timeout}ms)`));
40
+ if (!settled) {
41
+ settled = true;
42
+ try { conn.destroy(); } catch {};
43
+ reject(new Error(`命令执行超时 (${timeout}ms)`));
44
+ }
41
45
  }, timeout);
42
46
 
43
47
  conn.on('ready', () => {
44
- console.error('[SSH] 已连接到堡垒机');
45
-
46
48
  conn.shell({ term: 'xterm', rows: 24, cols: 500 }, (err, stream) => {
47
49
  if (err) {
48
50
  clearTimeout(timeoutId);
49
- reject(err);
51
+ if (!settled) { settled = true; reject(err); }
50
52
  return;
51
53
  }
52
54
 
53
55
  stream.on('close', () => {
54
56
  clearTimeout(timeoutId);
55
- conn.end();
56
-
57
- // 清理输出
58
- const cleanOutput = cleanTerminalOutput(kubectlOutput || buffer);
59
- resolve(cleanOutput);
57
+ try { conn.destroy(); } catch {}
58
+ if (!settled) {
59
+ settled = true;
60
+ resolve(cleanTerminalOutput(kubectlOutput || buffer));
61
+ }
60
62
  });
61
63
 
62
64
  stream.on('data', (data) => {
65
+ if (settled) return; // 已超时,忽略后续数据
63
66
  const text = data.toString();
64
67
  buffer += text;
65
68
 
@@ -67,64 +70,43 @@ export async function queryLog(service, command, options = {}) {
67
70
  kubectlOutput += text;
68
71
  }
69
72
 
70
- // JumpServer 状态机 - 检查累积的 buffer
71
-
72
- // 阶段1: 等待 Opt> 提示符
73
+ // JumpServer 状态机
73
74
  if (stage === 'init' && buffer.includes('Opt>')) {
74
75
  stage = 'opt';
75
- console.error('[SSH] 输入目标服务器 IP');
76
76
  stream.write(K8S_SERVER.host + '\r');
77
77
  }
78
- // 阶段2: 等待 [Host]> 提示符(搜索结果列表后)
79
78
  else if (stage === 'opt' && buffer.includes('[Host]>')) {
80
79
  stage = 'host';
81
- console.error('[SSH] 选择服务器 ID: ' + K8S_SERVER.selectOption);
82
80
  stream.write(K8S_SERVER.selectOption + '\r');
83
81
  }
84
- // 阶段3: 等待进入服务器 shell(检测 ~]$ 或 ~]# 提示符)
85
82
  else if (stage === 'host' && (buffer.includes('~]$') || buffer.includes('~]#'))) {
86
83
  stage = 'server';
87
- console.error('[SSH] 已进入服务器,执行 kubectl 命令');
88
-
89
- // 构建并执行 kubectl 命令
90
84
  const kubectlCmd = buildKubectlCommand(service, command);
91
- console.error(`[SSH] 命令: ${kubectlCmd.substring(0, 80)}...`);
92
-
93
- // 重置 buffer,开始收集 kubectl 输出
94
85
  kubectlOutput = '';
95
86
  collectingOutput = true;
96
-
97
87
  stream.write(kubectlCmd + '\r');
98
88
  stage = 'kubectl';
99
89
  }
100
- // 阶段4: kubectl 执行完成
101
90
  else if (stage === 'kubectl' && collectingOutput && kubectlOutput.length > 50) {
102
- // 检测命令执行完成(返回到 shell 提示符)
103
91
  if (kubectlOutput.includes('~]$') || kubectlOutput.includes('~]#')) {
104
92
  stage = 'done';
105
93
  collectingOutput = false;
106
- console.error('[SSH] 命令执行完成,退出');
107
-
108
- // 立即退出
109
94
  stream.write('exit\r');
110
95
  stream.write('exit\r');
111
- setTimeout(() => stream.end(), 500);
96
+ setTimeout(() => { try { stream.end(); } catch {} }, 300);
112
97
  }
113
98
  }
114
99
  });
115
100
 
116
- stream.stderr.on('data', (data) => {
117
- console.error('[SSH stderr]', data.toString());
118
- });
101
+ stream.stderr.on('data', () => {});
119
102
  });
120
103
  });
121
104
 
122
105
  conn.on('error', (err) => {
123
106
  clearTimeout(timeoutId);
124
- reject(new Error(`SSH 连接错误: ${err.message}`));
107
+ if (!settled) { settled = true; reject(new Error(`SSH 连接错误: ${err.message}`)); }
125
108
  });
126
109
 
127
- // 连接堡垒机
128
110
  conn.connect({
129
111
  host: JUMP_HOST.host,
130
112
  port: JUMP_HOST.port,
@@ -227,32 +209,35 @@ export async function executeKubectl(kubectlCommand, options = {}) {
227
209
  let kubectlOutput = '';
228
210
  let collectingOutput = false;
229
211
 
230
- // 设置超时
212
+ // 设置超时 - 使用 destroy() 强制关闭
213
+ let settled = false;
231
214
  timeoutId = setTimeout(() => {
232
- conn.end();
233
- reject(new Error(`命令执行超时 (${timeout}ms)`));
215
+ if (!settled) {
216
+ settled = true;
217
+ try { conn.destroy(); } catch {}
218
+ reject(new Error(`命令执行超时 (${timeout}ms)`));
219
+ }
234
220
  }, timeout);
235
221
 
236
222
  conn.on('ready', () => {
237
- console.error('[SSH] 已连接到堡垒机');
238
-
239
223
  conn.shell({ term: 'xterm', rows: 24, cols: 500 }, (err, stream) => {
240
224
  if (err) {
241
225
  clearTimeout(timeoutId);
242
- reject(err);
226
+ if (!settled) { settled = true; reject(err); }
243
227
  return;
244
228
  }
245
229
 
246
230
  stream.on('close', () => {
247
231
  clearTimeout(timeoutId);
248
- conn.end();
249
-
250
- // 清理输出
251
- const cleanOutput = cleanTerminalOutput(kubectlOutput || buffer);
252
- resolve(cleanOutput);
232
+ try { conn.destroy(); } catch {}
233
+ if (!settled) {
234
+ settled = true;
235
+ resolve(cleanTerminalOutput(kubectlOutput || buffer));
236
+ }
253
237
  });
254
238
 
255
239
  stream.on('data', (data) => {
240
+ if (settled) return;
256
241
  const text = data.toString();
257
242
  buffer += text;
258
243
 
@@ -261,60 +246,41 @@ export async function executeKubectl(kubectlCommand, options = {}) {
261
246
  }
262
247
 
263
248
  // JumpServer 状态机
264
-
265
- // 阶段1: 等待 Opt> 提示符
266
249
  if (stage === 'init' && buffer.includes('Opt>')) {
267
250
  stage = 'opt';
268
- console.error('[SSH] 输入目标服务器 IP');
269
251
  stream.write(K8S_SERVER.host + '\r');
270
252
  }
271
- // 阶段2: 等待 [Host]> 提示符
272
253
  else if (stage === 'opt' && buffer.includes('[Host]>')) {
273
254
  stage = 'host';
274
- console.error('[SSH] 选择服务器 ID: ' + K8S_SERVER.selectOption);
275
255
  stream.write(K8S_SERVER.selectOption + '\r');
276
256
  }
277
- // 阶段3: 等待进入服务器 shell
278
257
  else if (stage === 'host' && (buffer.includes('~]$') || buffer.includes('~]#'))) {
279
258
  stage = 'server';
280
- console.error('[SSH] 已进入服务器,执行 kubectl 命令');
281
- console.error(`[SSH] 命令: ${kubectlCommand.substring(0, 100)}...`);
282
-
283
- // 重置 buffer,开始收集 kubectl 输出
284
259
  kubectlOutput = '';
285
260
  collectingOutput = true;
286
-
287
261
  stream.write(kubectlCommand + '\r');
288
262
  stage = 'kubectl';
289
263
  }
290
- // 阶段4: kubectl 执行完成
291
264
  else if (stage === 'kubectl' && collectingOutput && kubectlOutput.length > 50) {
292
- // 检测命令执行完成(返回到 shell 提示符)
293
265
  if (kubectlOutput.includes('~]$') || kubectlOutput.includes('~]#')) {
294
266
  stage = 'done';
295
267
  collectingOutput = false;
296
- console.error('[SSH] 命令执行完成,退出');
297
-
298
- // 立即退出
299
268
  stream.write('exit\r');
300
269
  stream.write('exit\r');
301
- setTimeout(() => stream.end(), 500);
270
+ setTimeout(() => { try { stream.end(); } catch {} }, 300);
302
271
  }
303
272
  }
304
273
  });
305
274
 
306
- stream.stderr.on('data', (data) => {
307
- console.error('[SSH stderr]', data.toString());
308
- });
275
+ stream.stderr.on('data', () => {});
309
276
  });
310
277
  });
311
278
 
312
279
  conn.on('error', (err) => {
313
280
  clearTimeout(timeoutId);
314
- reject(new Error(`SSH 连接错误: ${err.message}`));
281
+ if (!settled) { settled = true; reject(new Error(`SSH 连接错误: ${err.message}`)); }
315
282
  });
316
283
 
317
- // 连接堡垒机
318
284
  conn.connect({
319
285
  host: JUMP_HOST.host,
320
286
  port: JUMP_HOST.port,