From 2e67d7717ff89a918eead0b6c6822b44aeb76abb Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 17 Feb 2026 18:47:30 -0500 Subject: [PATCH 1/2] fix: await MCP server initialization before returning McpHub instance MCP tools were unavailable on the first task turn when started via IPC because McpHub's constructor fired initializeGlobalMcpServers() and initializeProjectMcpServers() without awaiting them. getInstance() returned a hub with servers still in "connecting" state. Store the combined initialization promise and expose waitUntilReady(), then await it in McpServerManager.getInstance() so the hub is only returned after all servers have connected or timed out. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/services/mcp/McpHub.ts | 15 +++++++++++++-- src/services/mcp/McpServerManager.ts | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 3d02ce25f19..ea38ee02d6d 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -161,14 +161,25 @@ export class McpHub { private isProgrammaticUpdate: boolean = false private flagResetTimer?: NodeJS.Timeout private sanitizedNameRegistry: Map = new Map() + private initializationPromise: Promise constructor(provider: ClineProvider) { this.providerRef = new WeakRef(provider) this.watchMcpSettingsFile() this.watchProjectMcpFile().catch(console.error) this.setupWorkspaceFoldersWatcher() - this.initializeGlobalMcpServers() - this.initializeProjectMcpServers() + this.initializationPromise = Promise.all([ + this.initializeGlobalMcpServers(), + this.initializeProjectMcpServers(), + ]).then(() => {}) + } + + /** + * Waits until all MCP servers have finished their initial connection attempts. + * Each server individually handles its own timeout, so this will not block indefinitely. + */ + async waitUntilReady(): Promise { + await this.initializationPromise } /** * Registers a client (e.g., ClineProvider) using this hub. diff --git a/src/services/mcp/McpServerManager.ts b/src/services/mcp/McpServerManager.ts index e15f9db0a7a..1f25916eda4 100644 --- a/src/services/mcp/McpServerManager.ts +++ b/src/services/mcp/McpServerManager.ts @@ -37,6 +37,8 @@ export class McpServerManager { // Double-check instance in case it was created while we were waiting if (!this.instance) { this.instance = new McpHub(provider) + // Wait for all MCP servers to finish connecting (or timing out) + await this.instance.waitUntilReady() // Store a unique identifier in global state to track the primary instance await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString()) } From 1e84db9402a5191aaf8b4ac4e29133d505cb2eed Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 17 Feb 2026 23:56:25 +0000 Subject: [PATCH 2/2] fix: assign McpHub instance only after waitUntilReady() resolves Closes race condition where concurrent callers of getInstance() could receive a hub that has not finished initialization. The hub is now created in a local variable and only assigned to this.instance after waitUntilReady() completes. --- src/services/mcp/McpServerManager.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/mcp/McpServerManager.ts b/src/services/mcp/McpServerManager.ts index 1f25916eda4..3fd7146d9f9 100644 --- a/src/services/mcp/McpServerManager.ts +++ b/src/services/mcp/McpServerManager.ts @@ -36,9 +36,10 @@ export class McpServerManager { try { // Double-check instance in case it was created while we were waiting if (!this.instance) { - this.instance = new McpHub(provider) + const hub = new McpHub(provider) // Wait for all MCP servers to finish connecting (or timing out) - await this.instance.waitUntilReady() + await hub.waitUntilReady() + this.instance = hub // Store a unique identifier in global state to track the primary instance await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString()) }