@@ -8,20 +8,30 @@ import type {McpServerStdio} from "@agentclientprotocol/sdk";
88import { startCodexConnection } from "../../CodexJsonRpcConnection" ;
99import { createBaseTestFixture , removeDirectoryWithRetry , type TestFixture } from "../acp-test-utils" ;
1010
11- describe ( 'MCP config merge across global config and ACP request' , { timeout : 40_000 } , ( ) => {
11+ describe ( 'MCP config merge across configured MCP servers and ACP request' , { timeout : 40_000 } , ( ) => {
1212
1313 let codexHome : string ;
14+ let projectPath : string ;
1415 let fixture : TestFixture ;
1516
1617 beforeEach ( ( ) => {
1718 vi . clearAllMocks ( ) ;
1819
19- const configToml = `
20+ const globalConfig = `
2021[mcp_servers.shared-mcp]
2122url = "https://example.com/mcp"
2223` ;
24+
25+ const projectConfig = `
26+ [mcp_servers.project-mcp]
27+ url = "https://example.com/mcp"
28+ ` ;
29+
2330 codexHome = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "codex-acp-mcp-merge-" ) ) ;
24- fs . writeFileSync ( path . join ( codexHome , "config.toml" ) , configToml , "utf8" ) ;
31+ fs . writeFileSync ( path . join ( codexHome , "config.toml" ) , globalConfig , "utf8" ) ;
32+ projectPath = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "codex-acp-mcp-project-" ) ) ;
33+ fs . mkdirSync ( path . join ( projectPath , ".codex" ) ) ;
34+ fs . writeFileSync ( path . join ( projectPath , ".codex" , "config.toml" ) , projectConfig , "utf8" ) ;
2535
2636 const codexConnection = startCodexConnection ( undefined , {
2737 ...process . env ,
@@ -37,6 +47,7 @@ url = "https://example.com/mcp"
3747 afterEach ( ( ) => {
3848 vi . unstubAllEnvs ( ) ;
3949 removeDirectoryWithRetry ( codexHome ) ;
50+ removeDirectoryWithRetry ( projectPath ) ;
4051 } ) ;
4152
4253 it ( 'should preserve the global url-based MCP when ACP passes a command-type MCP with the same name' , async ( ) => {
@@ -68,6 +79,24 @@ url = "https://example.com/mcp"
6879 expect ( transportDump ) . contain ( "- shared-mcp" ) ;
6980 } ) ;
7081
82+ it ( 'should preserve a project url-based MCP when ACP passes a command-type MCP with the same name' , async ( ) => {
83+ const codexAcpAgent = fixture . getCodexAcpAgent ( ) ;
84+ await codexAcpAgent . initialize ( { protocolVersion : 1 } ) ;
85+ fixture . getCodexAcpClient ( ) . authRequired = vi . fn ( ) . mockResolvedValue ( false ) ;
86+
87+ const conflictingMcp = {
88+ name : "project-mcp" ,
89+ command : "./node_modules/.bin/mcp-hello-world" ,
90+ args : [ "example" ] ,
91+ env : [ { name : "example" , value : "example" } ] ,
92+ } ;
93+
94+ await expect ( codexAcpAgent . newSession ( {
95+ cwd : projectPath ,
96+ mcpServers : [ conflictingMcp ] ,
97+ } ) ) . resolves . toBeDefined ( ) ;
98+ } ) ;
99+
71100 it ( 'should not filter the conflicting ACP MCP when config filtering is disabled' , async ( ) => {
72101 vi . stubEnv ( "DISABLE_MCP_CONFIG_FILTERING" , "true" ) ;
73102 const codexAcpAgent = fixture . getCodexAcpAgent ( ) ;
0 commit comments