@@ -35,7 +35,9 @@ use async_channel::Sender;
3535use codex_async_utils:: CancelErr ;
3636use codex_async_utils:: OrCancelExt ;
3737use codex_config:: Constrained ;
38+ use codex_config:: McpServerEnvironment ;
3839use codex_config:: types:: OAuthCredentialsStoreMode ;
40+ use codex_exec_server:: Environment ;
3941use codex_protocol:: ToolName ;
4042use codex_protocol:: approvals:: ElicitationRequest ;
4143use codex_protocol:: approvals:: ElicitationRequestEvent ;
@@ -50,8 +52,11 @@ use codex_protocol::protocol::McpStartupStatus;
5052use codex_protocol:: protocol:: McpStartupUpdateEvent ;
5153use codex_protocol:: protocol:: SandboxPolicy ;
5254use codex_rmcp_client:: ElicitationResponse ;
55+ use codex_rmcp_client:: ExecutorStdioTransportRuntime ;
56+ use codex_rmcp_client:: LocalStdioTransportRuntime ;
5357use codex_rmcp_client:: RmcpClient ;
5458use codex_rmcp_client:: SendElicitation ;
59+ use codex_rmcp_client:: StdioTransportRuntime ;
5560use futures:: future:: BoxFuture ;
5661use futures:: future:: FutureExt ;
5762use futures:: future:: Shared ;
@@ -491,6 +496,8 @@ impl AsyncManagedClient {
491496 elicitation_requests : ElicitationRequestManager ,
492497 codex_apps_tools_cache_context : Option < CodexAppsToolsCacheContext > ,
493498 tool_plugin_provenance : Arc < ToolPluginProvenance > ,
499+ environment : Option < Arc < Environment > > ,
500+ remote_stdio_cwd : PathBuf ,
494501 ) -> Self {
495502 let tool_filter = ToolFilter :: from_config ( & config) ;
496503 let startup_snapshot = load_startup_cached_codex_apps_tools_snapshot (
@@ -507,8 +514,16 @@ impl AsyncManagedClient {
507514 return Err ( error. into ( ) ) ;
508515 }
509516
510- let client =
511- Arc :: new ( make_rmcp_client ( & server_name, config. transport , store_mode) . await ?) ;
517+ let client = Arc :: new (
518+ make_rmcp_client (
519+ & server_name,
520+ config. clone ( ) ,
521+ store_mode,
522+ environment,
523+ remote_stdio_cwd,
524+ )
525+ . await ?,
526+ ) ;
512527 match start_server_task (
513528 server_name,
514529 client,
@@ -708,6 +723,8 @@ impl McpConnectionManager {
708723 submit_id : String ,
709724 tx_event : Sender < Event > ,
710725 initial_sandbox_policy : SandboxPolicy ,
726+ environment : Option < Arc < Environment > > ,
727+ remote_stdio_cwd : PathBuf ,
711728 codex_home : PathBuf ,
712729 codex_apps_tools_cache_key : CodexAppsToolsCacheKey ,
713730 tool_plugin_provenance : ToolPluginProvenance ,
@@ -752,6 +769,8 @@ impl McpConnectionManager {
752769 elicitation_requests. clone ( ) ,
753770 codex_apps_tools_cache_context,
754771 Arc :: clone ( & tool_plugin_provenance) ,
772+ environment. clone ( ) ,
773+ remote_stdio_cwd. clone ( ) ,
755774 ) ;
756775 clients. insert ( server_name. clone ( ) , async_managed_client. clone ( ) ) ;
757776 let tx_event = tx_event. clone ( ) ;
@@ -1481,9 +1500,17 @@ struct StartServerTaskParams {
14811500
14821501async fn make_rmcp_client (
14831502 server_name : & str ,
1484- transport : McpServerTransportConfig ,
1503+ config : McpServerConfig ,
14851504 store_mode : OAuthCredentialsStoreMode ,
1505+ exec_environment : Option < Arc < Environment > > ,
1506+ remote_stdio_cwd : PathBuf ,
14861507) -> Result < RmcpClient , StartupOutcomeError > {
1508+ let McpServerConfig {
1509+ transport,
1510+ environment,
1511+ ..
1512+ } = config;
1513+
14871514 match transport {
14881515 McpServerTransportConfig :: Stdio {
14891516 command,
@@ -1499,7 +1526,28 @@ async fn make_rmcp_client(
14991526 . map ( |( key, value) | ( key. into ( ) , value. into ( ) ) )
15001527 . collect :: < HashMap < _ , _ > > ( )
15011528 } ) ;
1502- RmcpClient :: new_stdio_client ( command_os, args_os, env_os, & env_vars, cwd)
1529+ let runtime = match environment {
1530+ McpServerEnvironment :: Local => {
1531+ Arc :: new ( LocalStdioTransportRuntime ) as Arc < dyn StdioTransportRuntime >
1532+ }
1533+ McpServerEnvironment :: Remote => {
1534+ let exec_environment = exec_environment. ok_or_else ( || {
1535+ StartupOutcomeError :: from ( anyhow ! (
1536+ "remote MCP server `{server_name}` requires an executor environment"
1537+ ) )
1538+ } ) ?;
1539+ Arc :: new ( ExecutorStdioTransportRuntime :: new (
1540+ exec_environment. get_exec_backend ( ) ,
1541+ remote_stdio_cwd,
1542+ ) )
1543+ }
1544+ } ;
1545+
1546+ // `RmcpClient` always sees an MCP stdio transport. The runtime
1547+ // trait hides whether that transport was created by spawning a
1548+ // local child process or by asking the executor to start the child
1549+ // and stream its stdin/stdout bytes over the process API.
1550+ RmcpClient :: new_stdio_client ( command_os, args_os, env_os, & env_vars, cwd, runtime)
15031551 . await
15041552 . map_err ( |err| StartupOutcomeError :: from ( anyhow ! ( err) ) )
15051553 }
@@ -1508,23 +1556,34 @@ async fn make_rmcp_client(
15081556 http_headers,
15091557 env_http_headers,
15101558 bearer_token_env_var,
1511- } => {
1512- let resolved_bearer_token =
1513- match resolve_bearer_token ( server_name, bearer_token_env_var. as_deref ( ) ) {
1514- Ok ( token) => token,
1515- Err ( error) => return Err ( error. into ( ) ) ,
1516- } ;
1517- RmcpClient :: new_streamable_http_client (
1518- server_name,
1519- & url,
1520- resolved_bearer_token,
1521- http_headers,
1522- env_http_headers,
1523- store_mode,
1524- )
1525- . await
1526- . map_err ( StartupOutcomeError :: from)
1527- }
1559+ } => match environment {
1560+ McpServerEnvironment :: Local => {
1561+ // Local streamable HTTP remains the existing reqwest path from
1562+ // the orchestrator process.
1563+ let resolved_bearer_token =
1564+ match resolve_bearer_token ( server_name, bearer_token_env_var. as_deref ( ) ) {
1565+ Ok ( token) => token,
1566+ Err ( error) => return Err ( error. into ( ) ) ,
1567+ } ;
1568+ RmcpClient :: new_streamable_http_client (
1569+ server_name,
1570+ & url,
1571+ resolved_bearer_token,
1572+ http_headers,
1573+ env_http_headers,
1574+ store_mode,
1575+ )
1576+ . await
1577+ . map_err ( StartupOutcomeError :: from)
1578+ }
1579+ McpServerEnvironment :: Remote => Err ( StartupOutcomeError :: from ( anyhow ! (
1580+ // Remote HTTP needs the future low-level executor
1581+ // `network/request` API so reqwest runs on the executor side.
1582+ // Do not fall back to local HTTP here; the config explicitly
1583+ // asked for remote placement.
1584+ "remote streamable HTTP MCP server `{server_name}` is not implemented yet"
1585+ ) ) ) ,
1586+ } ,
15281587 }
15291588}
15301589
0 commit comments