( servers: Record<string, McpServerConfigForProcessTransport>, sdkState: SdkMcpState, dynamicState: DynamicMcpState, setAppState: (f: (prev: AppState) => AppState) => void, )
| 5351 | * are reported in response.errors so the SDK consumer knows why they weren't added. |
| 5352 | */ |
| 5353 | export async function handleMcpSetServers( |
| 5354 | servers: Record<string, McpServerConfigForProcessTransport>, |
| 5355 | sdkState: SdkMcpState, |
| 5356 | dynamicState: DynamicMcpState, |
| 5357 | setAppState: (f: (prev: AppState) => AppState) => void, |
| 5358 | ): Promise<McpSetServersResult> { |
| 5359 | // Enforce enterprise MCP policy on process-based servers (stdio/http/sse). |
| 5360 | // Mirrors the --mcp-config filter in main.tsx — both user-controlled injection |
| 5361 | // paths must have the same gate. type:'sdk' servers are exempt (SDK-managed, |
| 5362 | // CLI never spawns/connects for them — see filterMcpServersByPolicy jsdoc). |
| 5363 | // Blocked servers go into response.errors so the SDK caller sees why. |
| 5364 | const { allowed: allowedServers, blocked } = filterMcpServersByPolicy(servers) |
| 5365 | const policyErrors: Record<string, string> = {} |
| 5366 | for (const name of blocked) { |
| 5367 | policyErrors[name] = |
| 5368 | 'Blocked by enterprise policy (allowedMcpServers/deniedMcpServers)' |
| 5369 | } |
| 5370 | |
| 5371 | // Separate SDK servers from process-based servers |
| 5372 | const sdkServers: Record<string, McpSdkServerConfig> = {} |
| 5373 | const processServers: Record<string, McpServerConfigForProcessTransport> = {} |
| 5374 | |
| 5375 | for (const [name, config] of Object.entries(allowedServers)) { |
| 5376 | if (config.type === 'sdk') { |
| 5377 | sdkServers[name] = config |
| 5378 | } else { |
| 5379 | processServers[name] = config |
| 5380 | } |
| 5381 | } |
| 5382 | |
| 5383 | // Handle SDK servers |
| 5384 | const currentSdkNames = new Set(Object.keys(sdkState.configs)) |
| 5385 | const newSdkNames = new Set(Object.keys(sdkServers)) |
| 5386 | const sdkAdded: string[] = [] |
| 5387 | const sdkRemoved: string[] = [] |
| 5388 | |
| 5389 | const newSdkConfigs = { ...sdkState.configs } |
| 5390 | let newSdkClients = [...sdkState.clients] |
| 5391 | let newSdkTools = [...sdkState.tools] |
| 5392 | |
| 5393 | // Remove SDK servers no longer in desired state |
| 5394 | for (const name of currentSdkNames) { |
| 5395 | if (!newSdkNames.has(name)) { |
| 5396 | const client = newSdkClients.find(c => c.name === name) |
| 5397 | if (client && client.type === 'connected') { |
| 5398 | await client.cleanup() |
| 5399 | } |
| 5400 | newSdkClients = newSdkClients.filter(c => c.name !== name) |
| 5401 | const prefix = `mcp__${name}__` |
| 5402 | newSdkTools = newSdkTools.filter(t => !t.name.startsWith(prefix)) |
| 5403 | delete newSdkConfigs[name] |
| 5404 | sdkRemoved.push(name) |
| 5405 | } |
| 5406 | } |
| 5407 | |
| 5408 | // Add new SDK servers as pending - they'll be upgraded to connected |
| 5409 | // when updateSdkMcp() runs on the next query |
| 5410 | for (const [name, config] of Object.entries(sdkServers)) { |
no test coverage detected