fix: await MCP server initialization before returning McpHub instance (#11518)

* 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) <noreply@anthropic.com>

* 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.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
Daniel 2026-02-17 19:32:15 -05:00 committed by GitHub
parent 44df43063f
commit bfbfaf6d46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 3 deletions

View file

@ -161,14 +161,25 @@ export class McpHub {
private isProgrammaticUpdate: boolean = false
private flagResetTimer?: NodeJS.Timeout
private sanitizedNameRegistry: Map<string, string> = new Map()
private initializationPromise: Promise<void>
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<void> {
await this.initializationPromise
}
/**
* Registers a client (e.g., ClineProvider) using this hub.

View file

@ -36,7 +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 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())
}