mcp-log-query-server 3.3.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 (3) hide show
  1. package/index.js +23 -5
  2. package/package.json +1 -1
  3. package/ssh-client.js +35 -69
package/index.js CHANGED
@@ -670,25 +670,43 @@ async function handleToolCall(name, args) {
670
670
 
671
671
  console.error(`[MCP] 追踪日志: traceId=${traceId}, namespace=${targetNamespace || 'default'}, 服务数=${servicesToSearch.length}`);
672
672
 
673
+ const TRACE_TOTAL_TIMEOUT = 50000; // 总耗时上限 50s
674
+ const TRACE_PER_SERVICE = 10000; // 单服务超时 10s
675
+ const traceStart = Date.now();
673
676
  const results = [];
677
+ let searched = 0;
678
+ let skipped = 0;
679
+
674
680
  for (const serviceName of servicesToSearch) {
681
+ // 总耗时检查
682
+ if (Date.now() - traceStart > TRACE_TOTAL_TIMEOUT) {
683
+ skipped = servicesToSearch.length - searched;
684
+ console.error(`[MCP] trace_log 总耗时超过 ${TRACE_TOTAL_TIMEOUT}ms,跳过剩余 ${skipped} 个服务`);
685
+ break;
686
+ }
687
+
675
688
  const service = findService(serviceName, targetNamespace);
676
- if (!service) continue;
689
+ if (!service) { searched++; continue; }
677
690
 
678
691
  try {
679
692
  const command = `grep -i -C ${contextLines} "${traceId}"`;
680
- const result = await queryLog(service, command);
693
+ const result = await queryLog(service, command, { timeout: TRACE_PER_SERVICE });
681
694
 
682
695
  if (result && result.trim() && !result.includes('未找到')) {
683
696
  results.push({ service: serviceName, namespace: service.namespace, logs: result });
684
697
  }
685
698
  } catch (err) {
686
- console.error(`[MCP] 搜索 ${serviceName} 失败: ${err.message}`);
699
+ // 快速跳过失败/超时的服务
700
+ console.error(`[MCP] ${serviceName} 跳过: ${err.message.substring(0, 80)}`);
687
701
  }
702
+ searched++;
688
703
  }
689
704
 
705
+ const elapsed = Date.now() - traceStart;
706
+ const timeNote = skipped > 0 ? `\n**注意**: 已搜索 ${searched}/${servicesToSearch.length} 个服务(耗时 ${elapsed}ms,跳过 ${skipped} 个)` : '';
707
+
690
708
  if (results.length === 0) {
691
- return { content: [{ type: 'text', text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n\n❌ 未在任何服务中找到匹配的日志` }] };
709
+ return { content: [{ type: 'text', text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n\n❌ 未在已搜索的 ${searched} 个服务中找到匹配的日志${timeNote}` }] };
692
710
  }
693
711
 
694
712
  const output = results.map(r => `### ${r.service} (${r.namespace})\n\`\`\`\n${r.logs}\n\`\`\``).join('\n\n');
@@ -696,7 +714,7 @@ async function handleToolCall(name, args) {
696
714
  return {
697
715
  content: [{
698
716
  type: 'text',
699
- text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n**匹配服务数**: ${results.length}\n\n${output}`
717
+ text: `## TraceId 追踪结果\n\n**traceId**: ${traceId}\n**namespace**: ${targetNamespace || '默认'}\n**匹配服务数**: ${results.length}${timeNote}\n\n${output}`
700
718
  }]
701
719
  };
702
720
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-log-query-server",
3
- "version": "3.3.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,