From bdae58ceb8367ddf9e95142d798d33dd7d971f92 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 27 Jan 2026 17:13:09 +0100 Subject: [PATCH] refactor: Reuse MCP connections for health checks --- .../server/webui/src/lib/stores/mcp.svelte.ts | 89 ++++++++++++++++++- 1 file changed, 85 insertions(+), 4 deletions(-) diff --git a/tools/server/webui/src/lib/stores/mcp.svelte.ts b/tools/server/webui/src/lib/stores/mcp.svelte.ts index 1b1dd69ec0..2181b59d0f 100644 --- a/tools/server/webui/src/lib/stores/mcp.svelte.ts +++ b/tools/server/webui/src/lib/stores/mcp.svelte.ts @@ -475,7 +475,12 @@ class MCPStore { console.log(`[MCPStore] Connection acquired (active flows: ${this.activeFlowCount})`); } - async releaseConnection(shutdownIfUnused = true): Promise { + /** + * 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 { 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 { 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 { + /** + * 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 { + // 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) {