( servers: Record<string, McpServerConfigForProcessTransport>, sdkState: SdkMcpState, dynamicState: DynamicMcpState, setAppState: (f: (prev: AppState) => AppState) => void, )
| 5391 | * are reported in response.errors so the SDK consumer knows why they weren't added. |
| 5392 | */ |
| 5393 | export async function handleMcpSetServers( |
| 5394 | servers: Record<string, McpServerConfigForProcessTransport>, |
| 5395 | sdkState: SdkMcpState, |
| 5396 | dynamicState: DynamicMcpState, |
| 5397 | setAppState: (f: (prev: AppState) => AppState) => void, |
| 5398 | ): Promise<McpSetServersResult> { |
| 5399 | // Enforce enterprise MCP policy on process-based servers (stdio/http/sse). |
| 5400 | // Mirrors the --mcp-config filter in main.tsx — both user-controlled injection |
| 5401 | // paths must have the same gate. type:'sdk' servers are exempt (SDK-managed, |
| 5402 | // CLI never spawns/connects for them — see filterMcpServersByPolicy jsdoc). |
| 5403 | // Blocked servers go into response.errors so the SDK caller sees why. |
| 5404 | const { allowed: allowedServers, blocked } = filterMcpServersByPolicy(servers) |
| 5405 | const policyErrors: Record<string, string> = {} |
| 5406 | for (const name of blocked) { |
| 5407 | policyErrors[name] = |
| 5408 | 'Blocked by enterprise policy (allowedMcpServers/deniedMcpServers)' |
| 5409 | } |
| 5410 | |
| 5411 | // Separate SDK servers from process-based servers |
| 5412 | const sdkServers: Record<string, McpSdkServerConfig> = {} |
| 5413 | const processServers: Record<string, McpServerConfigForProcessTransport> = {} |
| 5414 | |
| 5415 | for (const [name, config] of Object.entries(allowedServers)) { |
| 5416 | if (config.type === 'sdk') { |
| 5417 | sdkServers[name] = config |
| 5418 | } else { |
| 5419 | processServers[name] = config |
| 5420 | } |
| 5421 | } |
| 5422 | |
| 5423 | // Handle SDK servers |
| 5424 | const currentSdkNames = new Set(Object.keys(sdkState.configs)) |
| 5425 | const newSdkNames = new Set(Object.keys(sdkServers)) |
| 5426 | const sdkAdded: string[] = [] |
| 5427 | const sdkRemoved: string[] = [] |
| 5428 | |
| 5429 | const newSdkConfigs = { ...sdkState.configs } |
| 5430 | let newSdkClients = [...sdkState.clients] |
| 5431 | let newSdkTools = [...sdkState.tools] |
| 5432 | |
| 5433 | // Remove SDK servers no longer in desired state |
| 5434 | for (const name of currentSdkNames) { |
| 5435 | if (!newSdkNames.has(name)) { |
| 5436 | const client = newSdkClients.find(c => c.name === name) |
| 5437 | if (client && client.type === 'connected') { |
| 5438 | await client.cleanup() |
| 5439 | } |
| 5440 | newSdkClients = newSdkClients.filter(c => c.name !== name) |
| 5441 | const prefix = `mcp__${name}__` |
| 5442 | newSdkTools = newSdkTools.filter(t => !t.name.startsWith(prefix)) |
| 5443 | delete newSdkConfigs[name] |
| 5444 | sdkRemoved.push(name) |
| 5445 | } |
| 5446 | } |
| 5447 | |
| 5448 | // Add new SDK servers as pending - they'll be upgraded to connected |
| 5449 | // when updateSdkMcp() runs on the next query |
| 5450 | for (const [name, config] of Object.entries(sdkServers)) { |
no test coverage detected