Skip to content

Commit 3f6e07a

Browse files
refactor(mcp): Implement reference counting for McpHub lifecycle (#2310)
Problem: Closing auxiliary windows (e.g., from "open in editor") could prematurely dispose the shared singleton McpHub instance, shutting down MCP servers unexpectedly while other panels (like the sidebar) might still be active. Solution: Implemented reference counting directly within the McpHub class to manage its own lifecycle based on active clients (ClineProvider instances). - Added `refCount`, `registerClient()`, and `unregisterClient()` methods to McpHub. - McpHub now disposes itself only when the last registered client unregisters (`refCount` reaches 0). - ClineProvider instances now call `mcpHub.registerClient()` upon initialization and `mcpHub.unregisterClient()` upon disposal. - Removed the direct `mcpHub.dispose()` call from ClineProvider.dispose. Benefit: This ensures the shared McpHub instance remains active as long as at least one ClineProvider instance is using it. Cleanup now correctly occurs only when the last provider is closed or during full extension deactivation. This centralizes the resource's lifecycle logic within the resource class itself. Files Changed: - src/services/mcp/McpHub.ts - src/core/webview/ClineProvider.ts
1 parent 6ece39e commit 3f6e07a

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
115115
McpServerManager.getInstance(this.context, this)
116116
.then((hub) => {
117117
this.mcpHub = hub
118+
this.mcpHub.registerClient()
118119
})
119120
.catch((error) => {
120121
this.outputChannel.appendLine(`Failed to initialize MCP Hub: ${error}`)
@@ -221,7 +222,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
221222

222223
this.workspaceTracker?.dispose()
223224
this.workspaceTracker = undefined
224-
this.mcpHub?.dispose()
225+
await this.mcpHub?.unregisterClient()
225226
this.mcpHub = undefined
226227
this.customModesManager?.dispose()
227228
this.outputChannel.appendLine("Disposed all disposables")

src/services/mcp/McpHub.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export class McpHub {
109109
private isDisposed: boolean = false
110110
connections: McpConnection[] = []
111111
isConnecting: boolean = false
112+
private refCount: number = 0 // Reference counter for active clients
112113

113114
constructor(provider: ClineProvider) {
114115
this.providerRef = new WeakRef(provider)
@@ -118,6 +119,27 @@ export class McpHub {
118119
this.initializeGlobalMcpServers()
119120
this.initializeProjectMcpServers()
120121
}
122+
/**
123+
* Registers a client (e.g., ClineProvider) using this hub.
124+
* Increments the reference count.
125+
*/
126+
public registerClient(): void {
127+
this.refCount++
128+
console.log(`McpHub: Client registered. Ref count: ${this.refCount}`)
129+
}
130+
131+
/**
132+
* Unregisters a client. Decrements the reference count.
133+
* If the count reaches zero, disposes the hub.
134+
*/
135+
public async unregisterClient(): Promise<void> {
136+
this.refCount--
137+
console.log(`McpHub: Client unregistered. Ref count: ${this.refCount}`)
138+
if (this.refCount <= 0) {
139+
console.log("McpHub: Last client unregistered. Disposing hub.")
140+
await this.dispose()
141+
}
142+
}
121143

122144
/**
123145
* Validates and normalizes server configuration
@@ -1247,6 +1269,12 @@ export class McpHub {
12471269
}
12481270

12491271
async dispose(): Promise<void> {
1272+
// Prevent multiple disposals
1273+
if (this.isDisposed) {
1274+
console.log("McpHub: Already disposed.")
1275+
return
1276+
}
1277+
console.log("McpHub: Disposing...")
12501278
this.isDisposed = true
12511279
this.removeAllFileWatchers()
12521280
for (const connection of this.connections) {

0 commit comments

Comments
 (0)