@@ -207,7 +207,7 @@ export class CodexAcpClient {
207207 approvalPolicy : null ,
208208 sandbox : null ,
209209 baseInstructions : null ,
210- config : this . createSessionConfig ( request . cwd , request . mcpServers ?? [ ] ) ,
210+ config : await this . createSessionConfig ( request . cwd , request . mcpServers ?? [ ] ) ,
211211 cwd : request . cwd ,
212212 developerInstructions : null ,
213213 model : null ,
@@ -229,7 +229,7 @@ export class CodexAcpClient {
229229 approvalPolicy : null ,
230230 sandbox : null ,
231231 baseInstructions : null ,
232- config : this . createSessionConfig ( request . cwd , request . mcpServers ?? [ ] ) ,
232+ config : await this . createSessionConfig ( request . cwd , request . mcpServers ?? [ ] ) ,
233233 cwd : request . cwd ,
234234 developerInstructions : null ,
235235 model : null ,
@@ -251,7 +251,7 @@ export class CodexAcpClient {
251251 await this . refreshSkills ( request . cwd , request . _meta ) ;
252252
253253 const response = await this . codexClient . threadStart ( {
254- config : this . createSessionConfig ( request . cwd , request . mcpServers ) ,
254+ config : await this . createSessionConfig ( request . cwd , request . mcpServers ) ,
255255 modelProvider : this . getModelProvider ( ) ,
256256 model : null ,
257257 cwd : request . cwd ,
@@ -283,7 +283,7 @@ export class CodexAcpClient {
283283 return this . codexClient . getMcpServerStartupVersion ( ) ;
284284 }
285285
286- private createSessionConfig ( projectPath : string , mcpServers : Array < McpServer > ) : JsonObject {
286+ private async createSessionConfig ( projectPath : string , mcpServers : Array < McpServer > ) : Promise < JsonObject > {
287287 const mergedConfig = {
288288 ...mergeGatewayConfig ( this . config , this . gatewayConfig ) ,
289289 projects : {
@@ -295,10 +295,28 @@ export class CodexAcpClient {
295295 if ( mcpServers . length === 0 ) {
296296 return mergedConfig ;
297297 }
298+
299+ // Deduplicates new servers against existing config to prevent Codex from deep-merging
300+ // incompatible field types (e.g., mixing url and stdio schemas).
301+ const existingNames = await this . getConfigMcpServerNames ( projectPath ) ;
302+ const uniqueServers = mcpServers . filter ( mcp => ! existingNames . has ( mcp . name ) ) ;
303+ if ( uniqueServers . length === 0 ) {
304+ return mergedConfig ;
305+ }
306+
298307 return {
299308 ...mergedConfig ,
300- "mcp_servers" : Object . fromEntries ( mcpServers . map ( mcp => [ mcp . name , this . createMcpSeverConfig ( mcp ) ] ) )
309+ "mcp_servers" : Object . fromEntries ( uniqueServers . map ( mcp => [ mcp . name , this . createMcpSeverConfig ( mcp ) ] ) ) ,
310+ } ;
311+ }
312+
313+ private async getConfigMcpServerNames ( projectPath : string ) : Promise < Set < string > > {
314+ const response = await this . codexClient . configRead ( { includeLayers : true , cwd : projectPath } ) ;
315+ const mcpServers = response ?. config ?. [ "mcp_servers" ] ;
316+ if ( ! mcpServers || typeof mcpServers !== "object" || Array . isArray ( mcpServers ) ) {
317+ return new Set ( ) ;
301318 }
319+ return new Set ( Object . keys ( mcpServers ) ) ;
302320 }
303321
304322 getModelProvider ( ) : string | null {
0 commit comments