Skip to content

Commit bfbfaf6

Browse files
daniel-lxsclauderoomote
authored
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>
1 parent 44df430 commit bfbfaf6

File tree

2 files changed

+17
-3
lines changed

2 files changed

+17
-3
lines changed

src/services/mcp/McpHub.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,25 @@ export class McpHub {
161161
private isProgrammaticUpdate: boolean = false
162162
private flagResetTimer?: NodeJS.Timeout
163163
private sanitizedNameRegistry: Map<string, string> = new Map()
164+
private initializationPromise: Promise<void>
164165

165166
constructor(provider: ClineProvider) {
166167
this.providerRef = new WeakRef(provider)
167168
this.watchMcpSettingsFile()
168169
this.watchProjectMcpFile().catch(console.error)
169170
this.setupWorkspaceFoldersWatcher()
170-
this.initializeGlobalMcpServers()
171-
this.initializeProjectMcpServers()
171+
this.initializationPromise = Promise.all([
172+
this.initializeGlobalMcpServers(),
173+
this.initializeProjectMcpServers(),
174+
]).then(() => {})
175+
}
176+
177+
/**
178+
* Waits until all MCP servers have finished their initial connection attempts.
179+
* Each server individually handles its own timeout, so this will not block indefinitely.
180+
*/
181+
async waitUntilReady(): Promise<void> {
182+
await this.initializationPromise
172183
}
173184
/**
174185
* Registers a client (e.g., ClineProvider) using this hub.

src/services/mcp/McpServerManager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ export class McpServerManager {
3636
try {
3737
// Double-check instance in case it was created while we were waiting
3838
if (!this.instance) {
39-
this.instance = new McpHub(provider)
39+
const hub = new McpHub(provider)
40+
// Wait for all MCP servers to finish connecting (or timing out)
41+
await hub.waitUntilReady()
42+
this.instance = hub
4043
// Store a unique identifier in global state to track the primary instance
4144
await context.globalState.update(this.GLOBAL_STATE_KEY, Date.now().toString())
4245
}

0 commit comments

Comments
 (0)