( desiredConfigs: Record<string, McpServerConfigForProcessTransport>, currentState: DynamicMcpState, setAppState: (f: (prev: AppState) => AppState) => void, )
| 5448 | * Handles additions, removals, and config changes. |
| 5449 | */ |
| 5450 | export async function reconcileMcpServers( |
| 5451 | desiredConfigs: Record<string, McpServerConfigForProcessTransport>, |
| 5452 | currentState: DynamicMcpState, |
| 5453 | setAppState: (f: (prev: AppState) => AppState) => void, |
| 5454 | ): Promise<{ |
| 5455 | response: SDKControlMcpSetServersResponse |
| 5456 | newState: DynamicMcpState |
| 5457 | }> { |
| 5458 | const currentNames = new Set(Object.keys(currentState.configs)) |
| 5459 | const desiredNames = new Set(Object.keys(desiredConfigs)) |
| 5460 | |
| 5461 | const toRemove = [...currentNames].filter(n => !desiredNames.has(n)) |
| 5462 | const toAdd = [...desiredNames].filter(n => !currentNames.has(n)) |
| 5463 | |
| 5464 | // Check for config changes (same name, different config) |
| 5465 | const toCheck = [...currentNames].filter(n => desiredNames.has(n)) |
| 5466 | const toReplace = toCheck.filter(name => { |
| 5467 | const currentConfig = currentState.configs[name] |
| 5468 | const desiredConfigRaw = desiredConfigs[name] |
| 5469 | if (!currentConfig || !desiredConfigRaw) return true |
| 5470 | const desiredConfig = toScopedConfig(desiredConfigRaw) |
| 5471 | return !areMcpConfigsEqual(currentConfig, desiredConfig) |
| 5472 | }) |
| 5473 | |
| 5474 | const removed: string[] = [] |
| 5475 | const added: string[] = [] |
| 5476 | const errors: Record<string, string> = {} |
| 5477 | |
| 5478 | let newClients = [...currentState.clients] |
| 5479 | let newTools = [...currentState.tools] |
| 5480 | |
| 5481 | // Remove old servers (including ones being replaced) |
| 5482 | for (const name of [...toRemove, ...toReplace]) { |
| 5483 | const client = newClients.find(c => c.name === name) |
| 5484 | const config = currentState.configs[name] |
| 5485 | if (client && config) { |
| 5486 | if (client.type === 'connected') { |
| 5487 | try { |
| 5488 | await client.cleanup() |
| 5489 | } catch (e) { |
| 5490 | logError(e) |
| 5491 | } |
| 5492 | } |
| 5493 | // Clear the memoization cache |
| 5494 | await clearServerCache(name, config) |
| 5495 | } |
| 5496 | |
| 5497 | // Remove tools from this server |
| 5498 | const prefix = `mcp__${name}__` |
| 5499 | newTools = newTools.filter(t => !t.name.startsWith(prefix)) |
| 5500 | |
| 5501 | // Remove from clients list |
| 5502 | newClients = newClients.filter(c => c.name !== name) |
| 5503 | |
| 5504 | // Track removal (only for actually removed, not replaced) |
| 5505 | if (toRemove.includes(name)) { |
| 5506 | removed.push(name) |
| 5507 | } |
no test coverage detected