-
Notifications
You must be signed in to change notification settings - Fork 151
Expand file tree
/
Copy pathlifecycle.ts
More file actions
93 lines (79 loc) · 3 KB
/
lifecycle.ts
File metadata and controls
93 lines (79 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import type { ServerDefinition } from "./types.ts";
import type { McpServerManager } from "./server-manager.ts";
import { logger } from "./logger.ts";
export type ReconnectCallback = (serverName: string) => void;
export class McpLifecycleManager {
private manager: McpServerManager;
private keepAliveServers = new Map<string, ServerDefinition>();
private allServers = new Map<string, ServerDefinition>();
private serverSettings = new Map<string, { idleTimeout?: number }>();
private globalIdleTimeout: number = 10 * 60 * 1000;
private healthCheckInterval?: NodeJS.Timeout;
private onReconnect?: ReconnectCallback;
private onIdleShutdown?: (serverName: string) => void;
constructor(manager: McpServerManager) {
this.manager = manager;
}
/**
* Set callback to be invoked after a successful auto-reconnect.
* Use this to update tool metadata when a server reconnects.
*/
setReconnectCallback(callback: ReconnectCallback): void {
this.onReconnect = callback;
}
markKeepAlive(name: string, definition: ServerDefinition): void {
this.keepAliveServers.set(name, definition);
}
registerServer(name: string, definition: ServerDefinition, settings?: { idleTimeout?: number }): void {
this.allServers.set(name, definition);
if (settings?.idleTimeout !== undefined) {
this.serverSettings.set(name, settings);
}
}
setGlobalIdleTimeout(minutes: number): void {
this.globalIdleTimeout = minutes * 60 * 1000;
}
setIdleShutdownCallback(callback: (serverName: string) => void): void {
this.onIdleShutdown = callback;
}
startHealthChecks(intervalMs = 30000): void {
this.healthCheckInterval = setInterval(() => {
this.checkConnections();
}, intervalMs);
this.healthCheckInterval.unref();
}
private async checkConnections(): Promise<void> {
for (const [name, definition] of this.keepAliveServers) {
const connection = this.manager.getConnection(name);
if (!connection || connection.status !== "connected") {
try {
await this.manager.connect(name, definition);
logger.debug(`Reconnected to ${name}`);
// Notify extension to update metadata
this.onReconnect?.(name);
} catch (error) {
console.error(`MCP: Failed to reconnect to ${name}:`, error);
}
}
}
for (const [name] of this.allServers) {
if (this.keepAliveServers.has(name)) continue;
const timeout = this.getIdleTimeout(name);
if (timeout > 0 && this.manager.isIdle(name, timeout)) {
await this.manager.close(name);
this.onIdleShutdown?.(name);
}
}
}
private getIdleTimeout(name: string): number {
const perServer = this.serverSettings.get(name)?.idleTimeout;
if (perServer !== undefined) return perServer * 60 * 1000;
return this.globalIdleTimeout;
}
async gracefulShutdown(): Promise<void> {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
await this.manager.closeAll();
}
}