refactor: Reuse MCP connections for health checks

This commit is contained in:
Aleksander Grygier 2026-01-27 17:13:09 +01:00
parent 0779dff7ca
commit bdae58ceb8
1 changed files with 85 additions and 4 deletions

View File

@ -475,7 +475,12 @@ class MCPStore {
console.log(`[MCPStore] Connection acquired (active flows: ${this.activeFlowCount})`);
}
async releaseConnection(shutdownIfUnused = true): Promise<void> {
/**
* Release a connection reference.
* By default, keeps connections alive for reuse (shutdownIfUnused=false).
* MCP spec encourages long-lived sessions to avoid reconnection overhead.
*/
async releaseConnection(shutdownIfUnused = false): Promise<void> {
this.activeFlowCount = Math.max(0, this.activeFlowCount - 1);
console.log(`[MCPStore] Connection released (active flows: ${this.activeFlowCount})`);
if (shutdownIfUnused && this.activeFlowCount === 0) {
@ -698,7 +703,8 @@ class MCPStore {
requestTimeoutSeconds: number;
headers?: string;
}[],
skipIfChecked = true
skipIfChecked = true,
promoteToActive = false
): Promise<void> {
const serversToCheck = skipIfChecked
? servers.filter((s) => !this.hasHealthCheck(s.id) && s.url.trim())
@ -707,11 +713,61 @@ class MCPStore {
const BATCH_SIZE = 5;
for (let i = 0; i < serversToCheck.length; i += BATCH_SIZE) {
const batch = serversToCheck.slice(i, i + BATCH_SIZE);
await Promise.all(batch.map((server) => this.runHealthCheck(server)));
await Promise.all(batch.map((server) => this.runHealthCheck(server, promoteToActive)));
}
}
async runHealthCheck(server: HealthCheckParams): Promise<void> {
/**
* Check if a server already has an active connection that can be reused.
* Returns the existing connection if available.
*/
getExistingConnection(serverId: string): MCPConnection | undefined {
return this.connections.get(serverId);
}
/**
* Run a health check for a server.
* If the server already has an active connection, reuses it instead of creating a new one.
* If promoteToActive is true and server is enabled, the connection will be kept
* and promoted to an active connection instead of being disconnected.
*/
async runHealthCheck(server: HealthCheckParams, promoteToActive = false): Promise<void> {
// Check if we already have an active connection for this server
const existingConnection = this.connections.get(server.id);
if (existingConnection) {
// Reuse existing connection - just refresh tools list
try {
const tools = await MCPService.listTools(existingConnection);
const capabilities = buildCapabilitiesInfo(
existingConnection.serverCapabilities,
existingConnection.clientCapabilities
);
this.updateHealthCheck(server.id, {
status: HealthCheckStatus.SUCCESS,
tools: tools.map((tool) => ({
name: tool.name,
description: tool.description,
title: tool.title
})),
serverInfo: existingConnection.serverInfo,
capabilities,
transportType: existingConnection.transportType,
protocolVersion: existingConnection.protocolVersion,
instructions: existingConnection.instructions,
connectionTimeMs: existingConnection.connectionTimeMs,
logs: []
});
console.log(`[MCPStore] Reused existing connection for health check: ${server.id}`);
return;
} catch (error) {
console.warn(
`[MCPStore] Failed to reuse connection for ${server.id}, creating new one:`,
error
);
// Connection may be stale, remove it and create new one
this.connections.delete(server.id);
}
}
const trimmedUrl = server.url.trim();
const logs: MCPConnectionLog[] = [];
let currentPhase: MCPConnectionPhase = MCPConnectionPhase.IDLE;
@ -790,6 +846,31 @@ class MCPStore {
}
}
/**
* Promote a health check connection to an active connection.
* This avoids the need to reconnect when the server is needed for agentic flows.
*/
private promoteHealthCheckToConnection(serverId: string, connection: MCPConnection): void {
// Register tools from the connection
for (const tool of connection.tools) {
if (this.toolsIndex.has(tool.name)) {
console.warn(
`[MCPStore] Tool name conflict during promotion: "${tool.name}" exists in "${this.toolsIndex.get(tool.name)}" and "${serverId}". Using tool from "${serverId}".`
);
}
this.toolsIndex.set(tool.name, serverId);
}
// Add to active connections
this.connections.set(serverId, connection);
// Update state
this.updateState({
toolCount: this.toolsIndex.size,
connectedServers: Array.from(this.connections.keys())
});
}
getServersStatus(): ServerStatus[] {
const statuses: ServerStatus[] = [];
for (const [name, connection] of this.connections) {