agentdog 1.1.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/LICENSE +22 -0
- package/README.md +244 -0
- package/dist/adapters/adapter-factory.d.ts +21 -0
- package/dist/adapters/adapter-factory.d.ts.map +1 -0
- package/dist/adapters/adapter-factory.js +157 -0
- package/dist/adapters/adapter-factory.js.map +1 -0
- package/dist/adapters/http-sse-adapter.d.ts +49 -0
- package/dist/adapters/http-sse-adapter.d.ts.map +1 -0
- package/dist/adapters/http-sse-adapter.js +485 -0
- package/dist/adapters/http-sse-adapter.js.map +1 -0
- package/dist/adapters/stdio-adapter.d.ts +69 -0
- package/dist/adapters/stdio-adapter.d.ts.map +1 -0
- package/dist/adapters/stdio-adapter.js +664 -0
- package/dist/adapters/stdio-adapter.js.map +1 -0
- package/dist/adapters/streamable-http-adapter.d.ts +38 -0
- package/dist/adapters/streamable-http-adapter.d.ts.map +1 -0
- package/dist/adapters/streamable-http-adapter.js +321 -0
- package/dist/adapters/streamable-http-adapter.js.map +1 -0
- package/dist/ai-gateway/anthropic-errors.d.ts +4 -0
- package/dist/ai-gateway/anthropic-errors.d.ts.map +1 -0
- package/dist/ai-gateway/anthropic-errors.js +17 -0
- package/dist/ai-gateway/anthropic-errors.js.map +1 -0
- package/dist/ai-gateway/auth.d.ts +3 -0
- package/dist/ai-gateway/auth.d.ts.map +1 -0
- package/dist/ai-gateway/auth.js +14 -0
- package/dist/ai-gateway/auth.js.map +1 -0
- package/dist/ai-gateway/auth.test.d.ts +2 -0
- package/dist/ai-gateway/auth.test.d.ts.map +1 -0
- package/dist/ai-gateway/auth.test.js +57 -0
- package/dist/ai-gateway/auth.test.js.map +1 -0
- package/dist/ai-gateway/count-tokens.d.ts +5 -0
- package/dist/ai-gateway/count-tokens.d.ts.map +1 -0
- package/dist/ai-gateway/count-tokens.js +37 -0
- package/dist/ai-gateway/count-tokens.js.map +1 -0
- package/dist/ai-gateway/count-tokens.test.d.ts +2 -0
- package/dist/ai-gateway/count-tokens.test.d.ts.map +1 -0
- package/dist/ai-gateway/count-tokens.test.js +19 -0
- package/dist/ai-gateway/count-tokens.test.js.map +1 -0
- package/dist/ai-gateway/dialects.d.ts +4 -0
- package/dist/ai-gateway/dialects.d.ts.map +1 -0
- package/dist/ai-gateway/dialects.js +15 -0
- package/dist/ai-gateway/dialects.js.map +1 -0
- package/dist/ai-gateway/gateway-server.d.ts +15 -0
- package/dist/ai-gateway/gateway-server.d.ts.map +1 -0
- package/dist/ai-gateway/gateway-server.js +121 -0
- package/dist/ai-gateway/gateway-server.js.map +1 -0
- package/dist/ai-gateway/integration.test.d.ts +2 -0
- package/dist/ai-gateway/integration.test.d.ts.map +1 -0
- package/dist/ai-gateway/integration.test.js +205 -0
- package/dist/ai-gateway/integration.test.js.map +1 -0
- package/dist/ai-gateway/ir/anthropic-in.d.ts +3 -0
- package/dist/ai-gateway/ir/anthropic-in.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-in.js +121 -0
- package/dist/ai-gateway/ir/anthropic-in.js.map +1 -0
- package/dist/ai-gateway/ir/anthropic-in.test.d.ts +2 -0
- package/dist/ai-gateway/ir/anthropic-in.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-in.test.js +93 -0
- package/dist/ai-gateway/ir/anthropic-in.test.js.map +1 -0
- package/dist/ai-gateway/ir/anthropic-sse.d.ts +22 -0
- package/dist/ai-gateway/ir/anthropic-sse.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-sse.js +200 -0
- package/dist/ai-gateway/ir/anthropic-sse.js.map +1 -0
- package/dist/ai-gateway/ir/anthropic-sse.test.d.ts +2 -0
- package/dist/ai-gateway/ir/anthropic-sse.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/anthropic-sse.test.js +101 -0
- package/dist/ai-gateway/ir/anthropic-sse.test.js.map +1 -0
- package/dist/ai-gateway/ir/gemini-out.d.ts +20 -0
- package/dist/ai-gateway/ir/gemini-out.d.ts.map +1 -0
- package/dist/ai-gateway/ir/gemini-out.js +218 -0
- package/dist/ai-gateway/ir/gemini-out.js.map +1 -0
- package/dist/ai-gateway/ir/gemini-out.test.d.ts +2 -0
- package/dist/ai-gateway/ir/gemini-out.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/gemini-out.test.js +125 -0
- package/dist/ai-gateway/ir/gemini-out.test.js.map +1 -0
- package/dist/ai-gateway/ir/openai-out.d.ts +17 -0
- package/dist/ai-gateway/ir/openai-out.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-out.js +226 -0
- package/dist/ai-gateway/ir/openai-out.js.map +1 -0
- package/dist/ai-gateway/ir/openai-out.test.d.ts +2 -0
- package/dist/ai-gateway/ir/openai-out.test.d.ts.map +1 -0
- package/dist/ai-gateway/ir/openai-out.test.js +144 -0
- package/dist/ai-gateway/ir/openai-out.test.js.map +1 -0
- package/dist/ai-gateway/ir/types.d.ts +77 -0
- package/dist/ai-gateway/ir/types.d.ts.map +1 -0
- package/dist/ai-gateway/ir/types.js +4 -0
- package/dist/ai-gateway/ir/types.js.map +1 -0
- package/dist/ai-gateway/ir-channel.d.ts +15 -0
- package/dist/ai-gateway/ir-channel.d.ts.map +1 -0
- package/dist/ai-gateway/ir-channel.js +120 -0
- package/dist/ai-gateway/ir-channel.js.map +1 -0
- package/dist/ai-gateway/model-router.d.ts +11 -0
- package/dist/ai-gateway/model-router.d.ts.map +1 -0
- package/dist/ai-gateway/model-router.js +27 -0
- package/dist/ai-gateway/model-router.js.map +1 -0
- package/dist/ai-gateway/model-router.test.d.ts +2 -0
- package/dist/ai-gateway/model-router.test.d.ts.map +1 -0
- package/dist/ai-gateway/model-router.test.js +43 -0
- package/dist/ai-gateway/model-router.test.js.map +1 -0
- package/dist/ai-gateway/passthrough.d.ts +9 -0
- package/dist/ai-gateway/passthrough.d.ts.map +1 -0
- package/dist/ai-gateway/passthrough.js +81 -0
- package/dist/ai-gateway/passthrough.js.map +1 -0
- package/dist/ai-gateway/passthrough.test.d.ts +2 -0
- package/dist/ai-gateway/passthrough.test.d.ts.map +1 -0
- package/dist/ai-gateway/passthrough.test.js +108 -0
- package/dist/ai-gateway/passthrough.test.js.map +1 -0
- package/dist/ai-gateway/upstream/model-lister.d.ts +13 -0
- package/dist/ai-gateway/upstream/model-lister.d.ts.map +1 -0
- package/dist/ai-gateway/upstream/model-lister.js +61 -0
- package/dist/ai-gateway/upstream/model-lister.js.map +1 -0
- package/dist/ai-gateway/upstream/provider-client.d.ts +20 -0
- package/dist/ai-gateway/upstream/provider-client.d.ts.map +1 -0
- package/dist/ai-gateway/upstream/provider-client.js +76 -0
- package/dist/ai-gateway/upstream/provider-client.js.map +1 -0
- package/dist/ai-gateway/upstream/provider-client.test.d.ts +2 -0
- package/dist/ai-gateway/upstream/provider-client.test.d.ts.map +1 -0
- package/dist/ai-gateway/upstream/provider-client.test.js +69 -0
- package/dist/ai-gateway/upstream/provider-client.test.js.map +1 -0
- package/dist/cli/cli-main.d.ts +7 -0
- package/dist/cli/cli-main.d.ts.map +1 -0
- package/dist/cli/cli-main.js +116 -0
- package/dist/cli/cli-main.js.map +1 -0
- package/dist/cli/cli-router.d.ts +20 -0
- package/dist/cli/cli-router.d.ts.map +1 -0
- package/dist/cli/cli-router.js +271 -0
- package/dist/cli/cli-router.js.map +1 -0
- package/dist/cli/cli-utils.d.ts +30 -0
- package/dist/cli/cli-utils.d.ts.map +1 -0
- package/dist/cli/cli-utils.js +219 -0
- package/dist/cli/cli-utils.js.map +1 -0
- package/dist/cli/commands/audit-commands.d.ts +30 -0
- package/dist/cli/commands/audit-commands.d.ts.map +1 -0
- package/dist/cli/commands/audit-commands.js +417 -0
- package/dist/cli/commands/audit-commands.js.map +1 -0
- package/dist/cli/commands/autostart-command.d.ts +21 -0
- package/dist/cli/commands/autostart-command.d.ts.map +1 -0
- package/dist/cli/commands/autostart-command.js +243 -0
- package/dist/cli/commands/autostart-command.js.map +1 -0
- package/dist/cli/commands/autostart-command.test.d.ts +2 -0
- package/dist/cli/commands/autostart-command.test.d.ts.map +1 -0
- package/dist/cli/commands/autostart-command.test.js +35 -0
- package/dist/cli/commands/autostart-command.test.js.map +1 -0
- package/dist/cli/commands/config-commands.d.ts +30 -0
- package/dist/cli/commands/config-commands.d.ts.map +1 -0
- package/dist/cli/commands/config-commands.js +1013 -0
- package/dist/cli/commands/config-commands.js.map +1 -0
- package/dist/cli/commands/daemon-commands.d.ts +76 -0
- package/dist/cli/commands/daemon-commands.d.ts.map +1 -0
- package/dist/cli/commands/daemon-commands.js +451 -0
- package/dist/cli/commands/daemon-commands.js.map +1 -0
- package/dist/cli/commands/detect-commands.d.ts +17 -0
- package/dist/cli/commands/detect-commands.d.ts.map +1 -0
- package/dist/cli/commands/detect-commands.js +293 -0
- package/dist/cli/commands/detect-commands.js.map +1 -0
- package/dist/cli/commands/diagnose-commands.d.ts +19 -0
- package/dist/cli/commands/diagnose-commands.d.ts.map +1 -0
- package/dist/cli/commands/diagnose-commands.js +326 -0
- package/dist/cli/commands/diagnose-commands.js.map +1 -0
- package/dist/cli/commands/optimize-commands.d.ts +14 -0
- package/dist/cli/commands/optimize-commands.d.ts.map +1 -0
- package/dist/cli/commands/optimize-commands.js +189 -0
- package/dist/cli/commands/optimize-commands.js.map +1 -0
- package/dist/cli/commands/proxy-command.d.ts +27 -0
- package/dist/cli/commands/proxy-command.d.ts.map +1 -0
- package/dist/cli/commands/proxy-command.js +212 -0
- package/dist/cli/commands/proxy-command.js.map +1 -0
- package/dist/cli/commands/start-command.d.ts +22 -0
- package/dist/cli/commands/start-command.d.ts.map +1 -0
- package/dist/cli/commands/start-command.js +275 -0
- package/dist/cli/commands/start-command.js.map +1 -0
- package/dist/config/config-manager.d.ts +125 -0
- package/dist/config/config-manager.d.ts.map +1 -0
- package/dist/config/config-manager.js +569 -0
- package/dist/config/config-manager.js.map +1 -0
- package/dist/config/config-manager.test.d.ts +2 -0
- package/dist/config/config-manager.test.d.ts.map +1 -0
- package/dist/config/config-manager.test.js +209 -0
- package/dist/config/config-manager.test.js.map +1 -0
- package/dist/core/auto-config-generator.d.ts +55 -0
- package/dist/core/auto-config-generator.d.ts.map +1 -0
- package/dist/core/auto-config-generator.js +278 -0
- package/dist/core/auto-config-generator.js.map +1 -0
- package/dist/core/mcpdog-server.d.ts +49 -0
- package/dist/core/mcpdog-server.d.ts.map +1 -0
- package/dist/core/mcpdog-server.js +552 -0
- package/dist/core/mcpdog-server.js.map +1 -0
- package/dist/core/protocol-detector.d.ts +56 -0
- package/dist/core/protocol-detector.d.ts.map +1 -0
- package/dist/core/protocol-detector.js +365 -0
- package/dist/core/protocol-detector.js.map +1 -0
- package/dist/daemon/daemon-client.d.ts +34 -0
- package/dist/daemon/daemon-client.d.ts.map +1 -0
- package/dist/daemon/daemon-client.js +130 -0
- package/dist/daemon/daemon-client.js.map +1 -0
- package/dist/daemon/daemon-client.test.d.ts +2 -0
- package/dist/daemon/daemon-client.test.d.ts.map +1 -0
- package/dist/daemon/daemon-client.test.js +77 -0
- package/dist/daemon/daemon-client.test.js.map +1 -0
- package/dist/daemon/daemon-info.d.ts +7 -0
- package/dist/daemon/daemon-info.d.ts.map +1 -0
- package/dist/daemon/daemon-info.js +34 -0
- package/dist/daemon/daemon-info.js.map +1 -0
- package/dist/daemon/daemon-info.test.d.ts +2 -0
- package/dist/daemon/daemon-info.test.d.ts.map +1 -0
- package/dist/daemon/daemon-info.test.js +57 -0
- package/dist/daemon/daemon-info.test.js.map +1 -0
- package/dist/daemon/daemon-web-server.ai.test.d.ts +2 -0
- package/dist/daemon/daemon-web-server.ai.test.d.ts.map +1 -0
- package/dist/daemon/daemon-web-server.ai.test.js +119 -0
- package/dist/daemon/daemon-web-server.ai.test.js.map +1 -0
- package/dist/daemon/daemon-web-server.d.ts +59 -0
- package/dist/daemon/daemon-web-server.d.ts.map +1 -0
- package/dist/daemon/daemon-web-server.js +1236 -0
- package/dist/daemon/daemon-web-server.js.map +1 -0
- package/dist/daemon/daemon-web-server.test.d.ts +2 -0
- package/dist/daemon/daemon-web-server.test.d.ts.map +1 -0
- package/dist/daemon/daemon-web-server.test.js +93 -0
- package/dist/daemon/daemon-web-server.test.js.map +1 -0
- package/dist/daemon/mcpdog-daemon.d.ts +42 -0
- package/dist/daemon/mcpdog-daemon.d.ts.map +1 -0
- package/dist/daemon/mcpdog-daemon.js +302 -0
- package/dist/daemon/mcpdog-daemon.js.map +1 -0
- package/dist/daemon/stdio-proxy.d.ts +17 -0
- package/dist/daemon/stdio-proxy.d.ts.map +1 -0
- package/dist/daemon/stdio-proxy.js +143 -0
- package/dist/daemon/stdio-proxy.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +198 -0
- package/dist/index.js.map +1 -0
- package/dist/intent-analyzer.d.ts +27 -0
- package/dist/intent-analyzer.d.ts.map +1 -0
- package/dist/intent-analyzer.js +140 -0
- package/dist/intent-analyzer.js.map +1 -0
- package/dist/logging/server-log-manager.d.ts +75 -0
- package/dist/logging/server-log-manager.d.ts.map +1 -0
- package/dist/logging/server-log-manager.js +185 -0
- package/dist/logging/server-log-manager.js.map +1 -0
- package/dist/mcp-registry.d.ts +35 -0
- package/dist/mcp-registry.d.ts.map +1 -0
- package/dist/mcp-registry.js +166 -0
- package/dist/mcp-registry.js.map +1 -0
- package/dist/middleware/auth.d.ts +5 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +81 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/mock-tool-database.d.ts +41 -0
- package/dist/mock-tool-database.d.ts.map +1 -0
- package/dist/mock-tool-database.js +461 -0
- package/dist/mock-tool-database.js.map +1 -0
- package/dist/proxy-server-manager.d.ts +11 -0
- package/dist/proxy-server-manager.d.ts.map +1 -0
- package/dist/proxy-server-manager.js +144 -0
- package/dist/proxy-server-manager.js.map +1 -0
- package/dist/router/tool-router.d.ts +61 -0
- package/dist/router/tool-router.d.ts.map +1 -0
- package/dist/router/tool-router.js +539 -0
- package/dist/router/tool-router.js.map +1 -0
- package/dist/router/tool-router.test.d.ts +2 -0
- package/dist/router/tool-router.test.d.ts.map +1 -0
- package/dist/router/tool-router.test.js +113 -0
- package/dist/router/tool-router.test.js.map +1 -0
- package/dist/streamable-http-server.d.ts +27 -0
- package/dist/streamable-http-server.d.ts.map +1 -0
- package/dist/streamable-http-server.js +302 -0
- package/dist/streamable-http-server.js.map +1 -0
- package/dist/tool-finder.d.ts +21 -0
- package/dist/tool-finder.d.ts.map +1 -0
- package/dist/tool-finder.js +86 -0
- package/dist/tool-finder.js.map +1 -0
- package/dist/tool-recommender.d.ts +23 -0
- package/dist/tool-recommender.d.ts.map +1 -0
- package/dist/tool-recommender.js +200 -0
- package/dist/tool-recommender.js.map +1 -0
- package/dist/types/index.d.ts +145 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/server-name-validator.d.ts +47 -0
- package/dist/utils/server-name-validator.d.ts.map +1 -0
- package/dist/utils/server-name-validator.js +156 -0
- package/dist/utils/server-name-validator.js.map +1 -0
- package/dist/web/web-server.d.ts +44 -0
- package/dist/web/web-server.d.ts.map +1 -0
- package/dist/web/web-server.js +500 -0
- package/dist/web/web-server.js.map +1 -0
- package/package.json +68 -0
- package/web/dist/assets/index-DMVhVtNU.css +1 -0
- package/web/dist/assets/index-VMXIgcOH.js +92 -0
- package/web/dist/assets/index-VMXIgcOH.js.map +1 -0
- package/web/dist/index.html +14 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
import spawn from 'cross-spawn';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { globalLogManager } from '../logging/server-log-manager.js';
|
|
4
|
+
export class StdioAdapter extends EventEmitter {
|
|
5
|
+
name;
|
|
6
|
+
config;
|
|
7
|
+
isConnected = false;
|
|
8
|
+
process;
|
|
9
|
+
requestId = 1;
|
|
10
|
+
pendingRequests = new Map();
|
|
11
|
+
buffer = '';
|
|
12
|
+
stderrBuffer = '';
|
|
13
|
+
// Process stability monitoring
|
|
14
|
+
crashCount = 0;
|
|
15
|
+
lastCrashTime = 0;
|
|
16
|
+
isRecovering = false;
|
|
17
|
+
crashHistory = []; // Crash time history
|
|
18
|
+
isBlacklisted = false; // Whether blacklisted
|
|
19
|
+
blacklistUntil = 0; // Blacklist release time
|
|
20
|
+
isDisabled = false; // Whether disabled, should not auto-reconnect if disabled
|
|
21
|
+
constructor(name, config) {
|
|
22
|
+
super();
|
|
23
|
+
this.name = name;
|
|
24
|
+
this.config = config;
|
|
25
|
+
if (config.transport !== 'stdio') {
|
|
26
|
+
throw new Error(`Invalid transport for StdioAdapter: ${config.transport}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async connect() {
|
|
30
|
+
if (this.isConnected) {
|
|
31
|
+
console.error(`[${this.name}] Already connected, skipping connect() call.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (this.isDisabled) {
|
|
35
|
+
console.error(`[${this.name}] Adapter is disabled, skipping connect() call.`);
|
|
36
|
+
throw new Error(`Adapter ${this.name} is disabled.`);
|
|
37
|
+
}
|
|
38
|
+
if (this.isBlacklisted) {
|
|
39
|
+
const remaining = Math.ceil((this.blacklistUntil - Date.now()) / 1000);
|
|
40
|
+
console.error(`[${this.name}] Adapter is blacklisted for ${remaining}s, skipping connect() call.`);
|
|
41
|
+
throw new Error(`Adapter ${this.name} is blacklisted.`);
|
|
42
|
+
}
|
|
43
|
+
if (!this.config.command) {
|
|
44
|
+
throw new Error('Command is required for stdio transport');
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
console.error(`[${this.name}] Attempting to connect via stdio: ${this.config.command} ${this.config.args?.join(' ') || ''}`);
|
|
48
|
+
// Environment variable debug log
|
|
49
|
+
this.logEnvironmentVariables();
|
|
50
|
+
// Prepare process environment variables
|
|
51
|
+
const processEnv = {
|
|
52
|
+
...process.env,
|
|
53
|
+
...this.config.env
|
|
54
|
+
};
|
|
55
|
+
// 使用 cross-spawn 启动子进程。
|
|
56
|
+
// Windows 上全局安装的命令(如 npx/npm/codegraph)实为 .cmd 批处理 shim,
|
|
57
|
+
// 仅靠 Node 原生 spawn(shell:false) 无法解析,会立即 ENOENT 导致进程起不来、
|
|
58
|
+
// initialize 握手超时。cross-spawn 内部会自动经 cmd.exe 解析并正确处理转义,
|
|
59
|
+
// 与官方 @modelcontextprotocol/sdk 的 StdioClientTransport 行为一致。
|
|
60
|
+
const spawnOptions = {
|
|
61
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
62
|
+
cwd: this.config.cwd,
|
|
63
|
+
env: processEnv,
|
|
64
|
+
// 经 cmd.exe 启动 .cmd shim 时 Windows 默认会弹出可见的 CMD 窗口,
|
|
65
|
+
// 隐藏之;子进程 stdio 均为管道,无需可见控制台。
|
|
66
|
+
windowsHide: true
|
|
67
|
+
};
|
|
68
|
+
// Enhanced stdio handling for high-output processes like playwright
|
|
69
|
+
if (this.name === 'playwright') {
|
|
70
|
+
spawnOptions.detached = false; // Keep attached but in separate session
|
|
71
|
+
// Increase buffer size for high-volume stderr output
|
|
72
|
+
spawnOptions.stdio = ['pipe', 'pipe', 'pipe'];
|
|
73
|
+
console.error(`[${this.name}] Using special spawn options for playwright`);
|
|
74
|
+
// Verify critical environment variables are set
|
|
75
|
+
if (processEnv.DEBUG) {
|
|
76
|
+
console.error(`[${this.name}] ✅ DEBUG environment variable set: ${processEnv.DEBUG}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.error(`[${this.name}] ⚠️ DEBUG environment variable not set`);
|
|
80
|
+
}
|
|
81
|
+
if (processEnv.PWDEBUG) {
|
|
82
|
+
console.error(`[${this.name}] ✅ PWDEBUG environment variable set`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.process = spawn(this.config.command, this.config.args || [], spawnOptions);
|
|
86
|
+
this.setupProcessHandlers();
|
|
87
|
+
// Initialize handshake
|
|
88
|
+
await this.initialize();
|
|
89
|
+
// Mark as connected, let router manage tool list fetching
|
|
90
|
+
this.isConnected = true;
|
|
91
|
+
console.error(`[${this.name}] Connected successfully.`);
|
|
92
|
+
globalLogManager.updateConnectionStatus(this.name, true);
|
|
93
|
+
globalLogManager.addLog(this.name, 'info', `MCP server connected successfully (command: ${this.config.command}, args: ${this.config.args?.join(' ') || 'none'})`, 'system');
|
|
94
|
+
this.emit('connected', { serverName: this.name });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
this.cleanup();
|
|
98
|
+
const errorMsg = `[${this.name}] Failed to connect: ${error.message}`;
|
|
99
|
+
console.error(errorMsg);
|
|
100
|
+
globalLogManager.updateConnectionStatus(this.name, false, errorMsg);
|
|
101
|
+
throw new Error(errorMsg);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async disconnect() {
|
|
105
|
+
if (!this.isConnected) {
|
|
106
|
+
console.error(`[${this.name}] Already disconnected, skipping disconnect() call.`);
|
|
107
|
+
globalLogManager.addLog(this.name, 'warn', 'Disconnect called but server already disconnected', 'system');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
console.error(`[${this.name}] Attempting to disconnect...`);
|
|
111
|
+
globalLogManager.addLog(this.name, 'info', 'Initiating disconnect...', 'system');
|
|
112
|
+
// Cancel all pending requests
|
|
113
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
114
|
+
clearTimeout(pending.timeout);
|
|
115
|
+
pending.reject(new Error('Connection closed'));
|
|
116
|
+
}
|
|
117
|
+
this.pendingRequests.clear();
|
|
118
|
+
this.cleanup();
|
|
119
|
+
this.isConnected = false;
|
|
120
|
+
console.error(`[${this.name}] Disconnected successfully.`);
|
|
121
|
+
globalLogManager.updateConnectionStatus(this.name, false);
|
|
122
|
+
globalLogManager.addLog(this.name, 'info', 'MCP server disconnected successfully', 'system');
|
|
123
|
+
this.emit('disconnected', { serverName: this.name });
|
|
124
|
+
}
|
|
125
|
+
setupProcessHandlers() {
|
|
126
|
+
if (!this.process)
|
|
127
|
+
return;
|
|
128
|
+
this.process.stdout?.on('data', (data) => {
|
|
129
|
+
const stdout = data.toString();
|
|
130
|
+
// Only log non-JSON response messages to log manager, avoid duplication
|
|
131
|
+
const lines = stdout.trim().split('\n');
|
|
132
|
+
const nonJsonLines = lines.filter(line => {
|
|
133
|
+
try {
|
|
134
|
+
JSON.parse(line.trim());
|
|
135
|
+
return false; // This is a JSON message, do not log
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return true; // This is not JSON, log it
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
if (nonJsonLines.length > 0) {
|
|
142
|
+
globalLogManager.addServerOutput(this.name, nonJsonLines.join('\n'), 'stdout');
|
|
143
|
+
this.emit('log', { stream: 'stdout', data: nonJsonLines.join('\n') });
|
|
144
|
+
}
|
|
145
|
+
this.handleStdoutData(stdout);
|
|
146
|
+
});
|
|
147
|
+
this.process.stderr?.on('data', (data) => {
|
|
148
|
+
this.stderrBuffer += data.toString();
|
|
149
|
+
const lines = this.stderrBuffer.split('\n');
|
|
150
|
+
this.stderrBuffer = lines.pop() || ''; // Keep incomplete line
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (line.trim()) {
|
|
153
|
+
const stderr = line.trim();
|
|
154
|
+
// Add debug logging for stderr
|
|
155
|
+
console.error(`[${this.name}] DEBUG: STDERR received - length: ${stderr.length}`);
|
|
156
|
+
console.error(`[${this.name}] DEBUG: STDERR content: ${stderr.substring(0, 200)}`);
|
|
157
|
+
// Log to log manager
|
|
158
|
+
globalLogManager.addServerOutput(this.name, stderr, 'stderr');
|
|
159
|
+
this.emit('log', { stream: 'stderr', data: stderr }); // Ensure all output is sent to frontend
|
|
160
|
+
// Detect browsermcp stack overflow errors
|
|
161
|
+
if (stderr.includes('Maximum call stack size exceeded') ||
|
|
162
|
+
stderr.includes('RangeError')) {
|
|
163
|
+
console.error(`⚠️ ${this.name} detected stack overflow, will auto-restart on next request`);
|
|
164
|
+
globalLogManager.addLog(this.name, 'warn', 'Stack overflow detected, will auto-restart on next request', 'system');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
this.process.on('error', (error) => {
|
|
170
|
+
console.error(`${this.name} process error:`, error);
|
|
171
|
+
globalLogManager.addLog(this.name, 'error', `Process error: ${error.message}`, 'system');
|
|
172
|
+
this.emit('error', { error, context: `${this.name}-process` });
|
|
173
|
+
});
|
|
174
|
+
this.process.on('exit', (code, signal) => {
|
|
175
|
+
console.error(`[${this.name}] DEBUG: Process exited with code ${code}, signal ${signal}.`);
|
|
176
|
+
console.error(`[${this.name}] DEBUG: Pending requests at exit: ${this.pendingRequests.size}`);
|
|
177
|
+
globalLogManager.addLog(this.name, 'error', `Process exited with code ${code}, signal ${signal}`, 'system');
|
|
178
|
+
// Log crash history
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
this.crashCount++;
|
|
181
|
+
this.lastCrashTime = now;
|
|
182
|
+
this.crashHistory.push(now);
|
|
183
|
+
// Only keep crash records for the last 5 minutes
|
|
184
|
+
const fiveMinutesAgo = now - 5 * 60 * 1000;
|
|
185
|
+
this.crashHistory = this.crashHistory.filter(time => time > fiveMinutesAgo);
|
|
186
|
+
if (signal === 'SIGKILL') {
|
|
187
|
+
const killMsg = `⚠️ Process was killed with SIGKILL - likely due to internal errors or resource issues. Crash count: ${this.crashCount} (${this.crashHistory.length} in last 5min)`;
|
|
188
|
+
console.error(`[${this.name}] ${killMsg}`);
|
|
189
|
+
globalLogManager.addLog(this.name, 'error', killMsg, 'system');
|
|
190
|
+
}
|
|
191
|
+
// Check if blacklisting is needed
|
|
192
|
+
this.checkAndUpdateBlacklist();
|
|
193
|
+
this.isConnected = false;
|
|
194
|
+
globalLogManager.updateConnectionStatus(this.name, false, `Process exited: code=${code}, signal=${signal}`);
|
|
195
|
+
this.emit('disconnected', {
|
|
196
|
+
serverName: this.name,
|
|
197
|
+
error: new Error(`Process exited: code=${code}, signal=${signal}`)
|
|
198
|
+
});
|
|
199
|
+
// Smart reconnect strategy
|
|
200
|
+
if (this.shouldAttemptReconnect(signal)) {
|
|
201
|
+
const delay = this.getReconnectDelay();
|
|
202
|
+
const reconnectMsg = `🔄 Will attempt reconnect in ${delay}ms`;
|
|
203
|
+
console.error(`[${this.name}] ${reconnectMsg}`);
|
|
204
|
+
globalLogManager.addLog(this.name, 'info', reconnectMsg, 'system');
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
this.attemptRecovery();
|
|
207
|
+
}, delay);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const noReconnectMsg = '🚫 Not attempting reconnect';
|
|
211
|
+
console.error(`[${this.name}] ${noReconnectMsg}`);
|
|
212
|
+
globalLogManager.addLog(this.name, 'warn', noReconnectMsg, 'system');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
handleStdoutData(data) {
|
|
217
|
+
console.error(`[${this.name}] DEBUG: Received stdout data - length: ${data.length}`);
|
|
218
|
+
this.buffer += data;
|
|
219
|
+
const lines = this.buffer.split('\n');
|
|
220
|
+
this.buffer = lines.pop() || ''; // Keep the last incomplete line
|
|
221
|
+
console.error(`[${this.name}] DEBUG: Split into ${lines.length} lines, buffer remaining: ${this.buffer.length}`);
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
if (line.trim()) {
|
|
224
|
+
console.error(`[${this.name}] DEBUG: Processing line length: ${line.length}`);
|
|
225
|
+
try {
|
|
226
|
+
const message = JSON.parse(line);
|
|
227
|
+
console.error(`[${this.name}] DEBUG: Parsed message - id: ${message.id}, method: ${message.method}`);
|
|
228
|
+
this.handleMessage(message);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error(`Failed to parse message from ${this.name}:`, line);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
handleMessage(message) {
|
|
237
|
+
if (message.id && this.pendingRequests.has(message.id)) {
|
|
238
|
+
// This is a response to a request
|
|
239
|
+
const pending = this.pendingRequests.get(message.id);
|
|
240
|
+
this.pendingRequests.delete(message.id);
|
|
241
|
+
clearTimeout(pending.timeout);
|
|
242
|
+
// Log response details (but not the raw JSON)
|
|
243
|
+
if (message.error) {
|
|
244
|
+
globalLogManager.addLog(this.name, 'error', `Method response error (ID: ${message.id}): ${JSON.stringify(message.error)}`, 'system');
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
globalLogManager.addLog(this.name, 'info', `Method response success (ID: ${message.id}): ${message.result ? 'with result' : 'no result'}`, 'system');
|
|
248
|
+
}
|
|
249
|
+
pending.resolve(message);
|
|
250
|
+
}
|
|
251
|
+
else if (!message.id && message.method) {
|
|
252
|
+
// This is a notification
|
|
253
|
+
this.handleNotification(message);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
handleNotification(notification) {
|
|
257
|
+
console.error(`Notification from ${this.name}:`, notification.method);
|
|
258
|
+
globalLogManager.addLog(this.name, 'info', `Notification received: ${notification.method}`, 'system');
|
|
259
|
+
if (notification.method === 'notifications/tools/list_changed') {
|
|
260
|
+
// Tool list changed, notify router for unified handling
|
|
261
|
+
globalLogManager.addLog(this.name, 'info', 'Tools list changed, notifying router', 'system');
|
|
262
|
+
this.emit('tools-changed', { serverName: this.name });
|
|
263
|
+
}
|
|
264
|
+
// Forward notification to upper layer
|
|
265
|
+
this.emit('notification', { serverName: this.name, notification });
|
|
266
|
+
}
|
|
267
|
+
async initialize() {
|
|
268
|
+
const initRequest = {
|
|
269
|
+
jsonrpc: '2.0',
|
|
270
|
+
id: this.getNextRequestId(),
|
|
271
|
+
method: 'initialize',
|
|
272
|
+
params: {
|
|
273
|
+
protocolVersion: '2024-11-05',
|
|
274
|
+
capabilities: {
|
|
275
|
+
tools: {}
|
|
276
|
+
},
|
|
277
|
+
clientInfo: {
|
|
278
|
+
name: 'mcpdog',
|
|
279
|
+
version: '2.0.0'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const response = await this.sendRequest(initRequest);
|
|
284
|
+
if (response.error) {
|
|
285
|
+
throw new Error(`Initialize failed: ${response.error.message}`);
|
|
286
|
+
}
|
|
287
|
+
// Send initialized notification
|
|
288
|
+
const initializedNotification = {
|
|
289
|
+
jsonrpc: '2.0',
|
|
290
|
+
method: 'notifications/initialized'
|
|
291
|
+
};
|
|
292
|
+
this.sendNotification(initializedNotification);
|
|
293
|
+
}
|
|
294
|
+
async getTools() {
|
|
295
|
+
const request = {
|
|
296
|
+
jsonrpc: '2.0',
|
|
297
|
+
id: this.getNextRequestId(),
|
|
298
|
+
method: 'tools/list',
|
|
299
|
+
params: {}
|
|
300
|
+
};
|
|
301
|
+
const response = await this.sendRequest(request);
|
|
302
|
+
if (response.error) {
|
|
303
|
+
throw new Error(`Failed to get tools: ${response.error.message}`);
|
|
304
|
+
}
|
|
305
|
+
return response.result?.tools || [];
|
|
306
|
+
}
|
|
307
|
+
async callTool(name, args) {
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
globalLogManager.addLog(this.name, 'info', `Calling tool: ${name}`, 'system');
|
|
310
|
+
const request = {
|
|
311
|
+
jsonrpc: '2.0',
|
|
312
|
+
id: this.getNextRequestId(),
|
|
313
|
+
method: 'tools/call',
|
|
314
|
+
params: {
|
|
315
|
+
name,
|
|
316
|
+
arguments: args
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
try {
|
|
320
|
+
const response = await this.sendRequest(request);
|
|
321
|
+
const duration = Date.now() - startTime;
|
|
322
|
+
if (response.error) {
|
|
323
|
+
globalLogManager.addLog(this.name, 'error', `Tool call failed: ${name} (duration: ${duration}ms, error: ${JSON.stringify(response.error)})`, 'system');
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
globalLogManager.addLog(this.name, 'info', `Tool call successful: ${name} (duration: ${duration}ms)`, 'system');
|
|
327
|
+
if (response.result?.content) {
|
|
328
|
+
// Log content summary without serializing large content to avoid blocking
|
|
329
|
+
const contentCount = Array.isArray(response.result.content) ? response.result.content.length : 1;
|
|
330
|
+
globalLogManager.addLog(this.name, 'debug', `Tool result: ${contentCount} content item(s)`, 'system');
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return response;
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
const duration = Date.now() - startTime;
|
|
337
|
+
globalLogManager.addLog(this.name, 'error', `Tool call exception: ${name} (duration: ${duration}ms, error: ${error.message})`, 'system');
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async sendRequest(request) {
|
|
342
|
+
// Add detailed debug logging for troubleshooting
|
|
343
|
+
console.error(`[${this.name}] DEBUG: sendRequest called - method: ${request.method}, id: ${request.id}`);
|
|
344
|
+
globalLogManager.addLog(this.name, 'info', `DEBUG: Sending request ${request.method} (ID: ${request.id})`, 'system');
|
|
345
|
+
// Check process status, try to reconnect if dead
|
|
346
|
+
if (!this.process?.stdin || this.process.killed || this.process.exitCode !== null) {
|
|
347
|
+
console.error(`${this.name} process is dead, attempting reconnection...`);
|
|
348
|
+
globalLogManager.addLog(this.name, 'warn', 'Process is dead, attempting reconnection...', 'system');
|
|
349
|
+
try {
|
|
350
|
+
await this.connect();
|
|
351
|
+
globalLogManager.addLog(this.name, 'info', 'Reconnection successful', 'system');
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
const errorMsg = `Failed to reconnect to ${this.name}: ${error.message}`;
|
|
355
|
+
globalLogManager.addLog(this.name, 'error', errorMsg, 'system');
|
|
356
|
+
throw new Error(errorMsg);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (!this.process?.stdin) {
|
|
360
|
+
const errorMsg = `Not connected to ${this.name}`;
|
|
361
|
+
globalLogManager.addLog(this.name, 'error', errorMsg, 'system');
|
|
362
|
+
throw new Error(errorMsg);
|
|
363
|
+
}
|
|
364
|
+
const startTime = Date.now();
|
|
365
|
+
return new Promise((resolve, reject) => {
|
|
366
|
+
const timeout = setTimeout(() => {
|
|
367
|
+
this.pendingRequests.delete(request.id);
|
|
368
|
+
const timeoutMsg = `Request timeout for ${this.name} (method: ${request.method}, timeout: ${this.config.timeout || 30000}ms)`;
|
|
369
|
+
globalLogManager.addLog(this.name, 'error', timeoutMsg, 'system');
|
|
370
|
+
reject(new Error(timeoutMsg));
|
|
371
|
+
}, this.config.timeout || 30000);
|
|
372
|
+
this.pendingRequests.set(request.id, {
|
|
373
|
+
resolve: (response) => {
|
|
374
|
+
// Skip debug logging in resolve
|
|
375
|
+
resolve(response);
|
|
376
|
+
},
|
|
377
|
+
reject: (error) => {
|
|
378
|
+
const duration = Date.now() - startTime;
|
|
379
|
+
globalLogManager.addLog(this.name, 'error', `Request failed: ${request.method} (ID: ${request.id}, duration: ${duration}ms, error: ${error.message})`, 'system');
|
|
380
|
+
reject(error);
|
|
381
|
+
},
|
|
382
|
+
timeout
|
|
383
|
+
});
|
|
384
|
+
try {
|
|
385
|
+
const requestStr = JSON.stringify(request) + '\n';
|
|
386
|
+
console.error(`[${this.name}] DEBUG: Writing to stdin - length: ${requestStr.length}`);
|
|
387
|
+
this.process.stdin.write(requestStr);
|
|
388
|
+
console.error(`[${this.name}] DEBUG: Successfully wrote to stdin`);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
this.pendingRequests.delete(request.id);
|
|
392
|
+
clearTimeout(timeout);
|
|
393
|
+
const errorMsg = `Failed to send request to ${this.name}: ${error.message}`;
|
|
394
|
+
globalLogManager.addLog(this.name, 'error', errorMsg, 'system');
|
|
395
|
+
reject(new Error(errorMsg));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
sendNotification(notification) {
|
|
400
|
+
if (!this.isConnected || !this.process?.stdin) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const notificationStr = JSON.stringify(notification) + '\n';
|
|
405
|
+
this.process.stdin.write(notificationStr);
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error(`Failed to send notification to ${this.name}:`, error);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
getNextRequestId() {
|
|
412
|
+
return this.requestId++;
|
|
413
|
+
}
|
|
414
|
+
cleanup() {
|
|
415
|
+
if (this.process) {
|
|
416
|
+
try {
|
|
417
|
+
if (!this.process.killed) {
|
|
418
|
+
this.process.kill('SIGTERM');
|
|
419
|
+
}
|
|
420
|
+
// If process does not end within 2 seconds, force kill
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
if (this.process && !this.process.killed) {
|
|
423
|
+
this.process.kill('SIGKILL');
|
|
424
|
+
}
|
|
425
|
+
}, 2000);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
console.error(`Error killing process for ${this.name}:`, error);
|
|
429
|
+
}
|
|
430
|
+
this.process = undefined;
|
|
431
|
+
}
|
|
432
|
+
this.buffer = '';
|
|
433
|
+
}
|
|
434
|
+
// Get current tool list (fetch in real-time from server)
|
|
435
|
+
async getCachedTools() {
|
|
436
|
+
if (!this.isConnected)
|
|
437
|
+
return [];
|
|
438
|
+
return await this.getTools();
|
|
439
|
+
}
|
|
440
|
+
// Check if specific tool is available (check in real-time from server)
|
|
441
|
+
async hasTools(toolName) {
|
|
442
|
+
if (!this.isConnected)
|
|
443
|
+
return false;
|
|
444
|
+
const tools = await this.getTools();
|
|
445
|
+
return tools.some(tool => tool.name === toolName);
|
|
446
|
+
}
|
|
447
|
+
// Get connection status information
|
|
448
|
+
getStatus() {
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
return {
|
|
451
|
+
name: this.name,
|
|
452
|
+
connected: this.isConnected,
|
|
453
|
+
toolCount: 0, // Tool count managed by router
|
|
454
|
+
pendingRequests: this.pendingRequests.size,
|
|
455
|
+
crashCount: this.crashCount,
|
|
456
|
+
recentCrashes: this.crashHistory.length,
|
|
457
|
+
isBlacklisted: this.isBlacklisted,
|
|
458
|
+
blacklistRemaining: this.isBlacklisted ? Math.max(0, this.blacklistUntil - now) : undefined
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
// Manually clear blacklist (admin function)
|
|
462
|
+
clearBlacklist() {
|
|
463
|
+
if (this.isBlacklisted) {
|
|
464
|
+
console.error(`🟢 Manually clearing blacklist for ${this.name}`);
|
|
465
|
+
this.isBlacklisted = false;
|
|
466
|
+
this.blacklistUntil = 0;
|
|
467
|
+
this.crashCount = 0;
|
|
468
|
+
this.crashHistory = [];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Get crash statistics
|
|
472
|
+
getCrashStats() {
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
return {
|
|
475
|
+
totalCrashes: this.crashCount,
|
|
476
|
+
recentCrashes: this.crashHistory.length,
|
|
477
|
+
crashHistory: this.crashHistory.map(time => new Date(time).toISOString()),
|
|
478
|
+
isBlacklisted: this.isBlacklisted,
|
|
479
|
+
nextAttemptIn: this.isBlacklisted ? Math.max(0, this.blacklistUntil - now) : undefined
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
// Check and update blacklist status
|
|
483
|
+
checkAndUpdateBlacklist() {
|
|
484
|
+
const now = Date.now();
|
|
485
|
+
// Check if blacklisted and time not yet reached
|
|
486
|
+
if (this.isBlacklisted && now < this.blacklistUntil) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// If blacklist time has passed, remove from blacklist
|
|
490
|
+
if (this.isBlacklisted && now >= this.blacklistUntil) {
|
|
491
|
+
console.error(`🟢 ${this.name} removed from blacklist, allowing reconnection`);
|
|
492
|
+
this.isBlacklisted = false;
|
|
493
|
+
this.blacklistUntil = 0;
|
|
494
|
+
// Reset some statistics
|
|
495
|
+
this.crashCount = Math.max(0, this.crashCount - 2);
|
|
496
|
+
}
|
|
497
|
+
// Check if blacklisting is needed
|
|
498
|
+
const recentCrashes = this.crashHistory.length;
|
|
499
|
+
if (recentCrashes >= 5) {
|
|
500
|
+
// More than 5 crashes in 5 minutes, blacklist for 30 minutes
|
|
501
|
+
this.isBlacklisted = true;
|
|
502
|
+
this.blacklistUntil = now + 30 * 60 * 1000; // 30 minutes
|
|
503
|
+
console.error(`🔴 ${this.name} blacklisted for 30 minutes due to ${recentCrashes} crashes in 5 minutes`);
|
|
504
|
+
}
|
|
505
|
+
else if (recentCrashes >= 3) {
|
|
506
|
+
// More than 3 crashes in 5 minutes, blacklist for 10 minutes
|
|
507
|
+
this.isBlacklisted = true;
|
|
508
|
+
this.blacklistUntil = now + 10 * 60 * 1000; // 10 minutes
|
|
509
|
+
console.error(`🟡 ${this.name} blacklisted for 10 minutes due to ${recentCrashes} crashes in 5 minutes`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Smart reconnect strategy
|
|
513
|
+
shouldAttemptReconnect(signal) {
|
|
514
|
+
// If server is disabled, should not auto-reconnect
|
|
515
|
+
if (this.isDisabled) {
|
|
516
|
+
console.error(`❌ ${this.name} is disabled, no auto-reconnect`);
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
// If already recovering, do not try again
|
|
520
|
+
if (this.isRecovering) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
// Check blacklist status
|
|
524
|
+
const now = Date.now();
|
|
525
|
+
if (this.isBlacklisted && now < this.blacklistUntil) {
|
|
526
|
+
const remainingMinutes = Math.ceil((this.blacklistUntil - now) / (60 * 1000));
|
|
527
|
+
console.error(`❌ ${this.name} is blacklisted for ${remainingMinutes} more minutes, no auto-reconnect`);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
// If recent crashes are too frequent, be cautious even if not blacklisted
|
|
531
|
+
if (this.crashHistory.length >= 2) {
|
|
532
|
+
const lastTwoCrashes = this.crashHistory.slice(-2);
|
|
533
|
+
const timeBetweenCrashes = lastTwoCrashes[1] - lastTwoCrashes[0];
|
|
534
|
+
// If two crashes are less than 30 seconds apart, pause reconnect
|
|
535
|
+
if (timeBetweenCrashes < 30 * 1000) {
|
|
536
|
+
console.error(`⏸️ ${this.name} crashing too quickly (${Math.round(timeBetweenCrashes / 1000)}s apart), pausing auto-reconnect`);
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Only SIGKILL or other abnormal exits require reconnect
|
|
541
|
+
return signal === 'SIGKILL' || signal === 'SIGTERM' || signal === null;
|
|
542
|
+
}
|
|
543
|
+
getReconnectDelay() {
|
|
544
|
+
// Adjust delay strategy based on crash history
|
|
545
|
+
const recentCrashes = this.crashHistory.length;
|
|
546
|
+
const baseDelay = 2000; // 2 second base delay
|
|
547
|
+
let delay = baseDelay;
|
|
548
|
+
// Adjust delay based on recent crash count
|
|
549
|
+
if (recentCrashes >= 4) {
|
|
550
|
+
delay = 60000; // 1 minute
|
|
551
|
+
}
|
|
552
|
+
else if (recentCrashes >= 3) {
|
|
553
|
+
delay = 30000; // 30 seconds
|
|
554
|
+
}
|
|
555
|
+
else if (recentCrashes >= 2) {
|
|
556
|
+
delay = 10000; // 10 seconds
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
delay = baseDelay; // 2 seconds
|
|
560
|
+
}
|
|
561
|
+
// If frequent crashes, add random delay to avoid thundering herd effect
|
|
562
|
+
if (recentCrashes >= 2) {
|
|
563
|
+
const randomDelay = Math.random() * delay * 0.5; // 0-50% random delay
|
|
564
|
+
delay += randomDelay;
|
|
565
|
+
}
|
|
566
|
+
return Math.round(delay);
|
|
567
|
+
}
|
|
568
|
+
async attemptRecovery() {
|
|
569
|
+
if (this.isRecovering || this.isConnected) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
this.isRecovering = true;
|
|
573
|
+
console.error(`🔧 ${this.name} attempting recovery (attempt ${this.crashCount})`);
|
|
574
|
+
try {
|
|
575
|
+
// Clean up old state
|
|
576
|
+
this.cleanup();
|
|
577
|
+
// Wait a bit for system resources to be released
|
|
578
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
579
|
+
// Try to reconnect
|
|
580
|
+
await this.connect();
|
|
581
|
+
console.error(`✅ ${this.name} recovered successfully`);
|
|
582
|
+
// Reset crash counter (after successful connection)
|
|
583
|
+
if (this.isConnected) {
|
|
584
|
+
this.crashCount = Math.max(0, this.crashCount - 1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
console.error(`❌ ${this.name} recovery failed:`, error.message);
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
this.isRecovering = false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Manually trigger recovery (for external calls)
|
|
595
|
+
async forceReconnect() {
|
|
596
|
+
console.error(`🔄 Force reconnecting ${this.name}...`);
|
|
597
|
+
this.crashCount = 0; // Reset counter
|
|
598
|
+
await this.disconnect();
|
|
599
|
+
await this.attemptRecovery();
|
|
600
|
+
}
|
|
601
|
+
// Disable adapter, prevent auto-reconnect
|
|
602
|
+
disable() {
|
|
603
|
+
console.error(`🚫 Disabling ${this.name} - no auto-reconnect`);
|
|
604
|
+
this.isDisabled = true;
|
|
605
|
+
}
|
|
606
|
+
// Re-enable adapter, allow auto-reconnect
|
|
607
|
+
enable() {
|
|
608
|
+
console.error(`✅ Enabling ${this.name} - auto-reconnect allowed`);
|
|
609
|
+
this.isDisabled = false;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Log environment variable debug information
|
|
613
|
+
*/
|
|
614
|
+
logEnvironmentVariables() {
|
|
615
|
+
const configEnv = this.config.env;
|
|
616
|
+
if (!configEnv || Object.keys(configEnv).length === 0) {
|
|
617
|
+
console.error(`[${this.name}] 🔧 No custom environment variables configured`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
console.error(`[${this.name}] 🔧 Environment Variables Configuration:`);
|
|
621
|
+
// Statistics
|
|
622
|
+
const envKeys = Object.keys(configEnv);
|
|
623
|
+
console.error(`[${this.name}] 📊 Total custom environment variables: ${envKeys.length}`);
|
|
624
|
+
// Detailed log (safely, without showing sensitive values)
|
|
625
|
+
envKeys.forEach(key => {
|
|
626
|
+
const value = configEnv[key];
|
|
627
|
+
const isSensitive = this.isSensitiveEnvVar(key);
|
|
628
|
+
if (isSensitive) {
|
|
629
|
+
console.error(`[${this.name}] 🔐 ${key}=[REDACTED] (${value.length} chars, sensitive)`);
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
// For non-sensitive variables, also limit display length
|
|
633
|
+
const displayValue = value.length > 50 ? `${value.substring(0, 47)}...` : value;
|
|
634
|
+
console.error(`[${this.name}] 🔧 ${key}=${displayValue}`);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
// Environment variable override check
|
|
638
|
+
const systemEnvKeys = Object.keys(process.env);
|
|
639
|
+
const overriddenKeys = envKeys.filter(key => systemEnvKeys.includes(key));
|
|
640
|
+
if (overriddenKeys.length > 0) {
|
|
641
|
+
console.error(`[${this.name}] ⚠️ Overriding ${overriddenKeys.length} system environment variables: ${overriddenKeys.join(', ')}`);
|
|
642
|
+
}
|
|
643
|
+
// Working directory information
|
|
644
|
+
if (this.config.cwd) {
|
|
645
|
+
console.error(`[${this.name}] 📁 Working directory: ${this.config.cwd}`);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
console.error(`[${this.name}] 📁 Working directory: ${process.cwd()} (default)`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Check if environment variable is sensitive
|
|
653
|
+
*/
|
|
654
|
+
isSensitiveEnvVar(key) {
|
|
655
|
+
const sensitiveKeywords = [
|
|
656
|
+
'password', 'secret', 'key', 'token', 'auth', 'credential',
|
|
657
|
+
'pass', 'pwd', 'private', 'sensitive', 'security', 'api_key',
|
|
658
|
+
'access_token', 'refresh_token', 'jwt', 'session'
|
|
659
|
+
];
|
|
660
|
+
const lowerKey = key.toLowerCase();
|
|
661
|
+
return sensitiveKeywords.some(keyword => lowerKey.includes(keyword));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
//# sourceMappingURL=stdio-adapter.js.map
|