@@ -36,6 +36,7 @@ use codex_async_utils::CancelErr;
3636use codex_async_utils:: OrCancelExt ;
3737use codex_config:: Constrained ;
3838use codex_config:: types:: OAuthCredentialsStoreMode ;
39+ use codex_exec_server:: Environment ;
3940use codex_protocol:: ToolName ;
4041use codex_protocol:: approvals:: ElicitationRequest ;
4142use codex_protocol:: approvals:: ElicitationRequestEvent ;
@@ -50,6 +51,7 @@ use codex_protocol::protocol::McpStartupStatus;
5051use codex_protocol:: protocol:: McpStartupUpdateEvent ;
5152use codex_protocol:: protocol:: SandboxPolicy ;
5253use codex_rmcp_client:: ElicitationResponse ;
54+ use codex_rmcp_client:: ExecutorStdioServerLauncher ;
5355use codex_rmcp_client:: LocalStdioServerLauncher ;
5456use codex_rmcp_client:: RmcpClient ;
5557use codex_rmcp_client:: SendElicitation ;
@@ -493,6 +495,8 @@ impl AsyncManagedClient {
493495 elicitation_requests : ElicitationRequestManager ,
494496 codex_apps_tools_cache_context : Option < CodexAppsToolsCacheContext > ,
495497 tool_plugin_provenance : Arc < ToolPluginProvenance > ,
498+ environment : Option < Arc < Environment > > ,
499+ remote_stdio_cwd : PathBuf ,
496500 ) -> Self {
497501 let tool_filter = ToolFilter :: from_config ( & config) ;
498502 let startup_snapshot = load_startup_cached_codex_apps_tools_snapshot (
@@ -509,8 +513,16 @@ impl AsyncManagedClient {
509513 return Err ( error. into ( ) ) ;
510514 }
511515
512- let client =
513- Arc :: new ( make_rmcp_client ( & server_name, config. transport , store_mode) . await ?) ;
516+ let client = Arc :: new (
517+ make_rmcp_client (
518+ & server_name,
519+ config. clone ( ) ,
520+ store_mode,
521+ environment,
522+ remote_stdio_cwd,
523+ )
524+ . await ?,
525+ ) ;
514526 match start_server_task (
515527 server_name,
516528 client,
@@ -710,6 +722,8 @@ impl McpConnectionManager {
710722 submit_id : String ,
711723 tx_event : Sender < Event > ,
712724 initial_sandbox_policy : SandboxPolicy ,
725+ environment : Option < Arc < Environment > > ,
726+ remote_stdio_cwd : PathBuf ,
713727 codex_home : PathBuf ,
714728 codex_apps_tools_cache_key : CodexAppsToolsCacheKey ,
715729 tool_plugin_provenance : ToolPluginProvenance ,
@@ -754,6 +768,8 @@ impl McpConnectionManager {
754768 elicitation_requests. clone ( ) ,
755769 codex_apps_tools_cache_context,
756770 Arc :: clone ( & tool_plugin_provenance) ,
771+ environment. clone ( ) ,
772+ remote_stdio_cwd. clone ( ) ,
757773 ) ;
758774 clients. insert ( server_name. clone ( ) , async_managed_client. clone ( ) ) ;
759775 let tx_event = tx_event. clone ( ) ;
@@ -1484,9 +1500,26 @@ struct StartServerTaskParams {
14841500
14851501async fn make_rmcp_client (
14861502 server_name : & str ,
1487- transport : McpServerTransportConfig ,
1503+ config : McpServerConfig ,
14881504 store_mode : OAuthCredentialsStoreMode ,
1505+ exec_environment : Option < Arc < Environment > > ,
1506+ remote_stdio_cwd : PathBuf ,
14891507) -> Result < RmcpClient , StartupOutcomeError > {
1508+ let McpServerConfig {
1509+ transport,
1510+ experimental_environment,
1511+ ..
1512+ } = config;
1513+ let remote_environment = match experimental_environment. as_deref ( ) {
1514+ None | Some ( "local" ) => false ,
1515+ Some ( "remote" ) => true ,
1516+ Some ( environment) => {
1517+ return Err ( StartupOutcomeError :: from ( anyhow ! (
1518+ "unsupported experimental_environment `{environment}` for MCP server `{server_name}`"
1519+ ) ) ) ;
1520+ }
1521+ } ;
1522+
14901523 match transport {
14911524 McpServerTransportConfig :: Stdio {
14921525 command,
@@ -1502,7 +1535,23 @@ async fn make_rmcp_client(
15021535 . map ( |( key, value) | ( key. into ( ) , value. into ( ) ) )
15031536 . collect :: < HashMap < _ , _ > > ( )
15041537 } ) ;
1505- let launcher = Arc :: new ( LocalStdioServerLauncher ) as Arc < dyn StdioServerLauncher > ;
1538+ let launcher = if remote_environment {
1539+ let exec_environment = exec_environment. ok_or_else ( || {
1540+ StartupOutcomeError :: from ( anyhow ! (
1541+ "remote MCP server `{server_name}` requires an executor environment"
1542+ ) )
1543+ } ) ?;
1544+ Arc :: new ( ExecutorStdioServerLauncher :: new (
1545+ exec_environment. get_exec_backend ( ) ,
1546+ remote_stdio_cwd,
1547+ ) )
1548+ } else {
1549+ Arc :: new ( LocalStdioServerLauncher ) as Arc < dyn StdioServerLauncher >
1550+ } ;
1551+
1552+ // `RmcpClient` always sees a launched MCP stdio server. The
1553+ // launcher hides whether that means a local child process or an
1554+ // executor process whose stdin/stdout bytes cross the process API.
15061555 RmcpClient :: new_stdio_client ( command_os, args_os, env_os, & env_vars, cwd, launcher)
15071556 . await
15081557 . map_err ( |err| StartupOutcomeError :: from ( anyhow ! ( err) ) )
@@ -1513,6 +1562,18 @@ async fn make_rmcp_client(
15131562 env_http_headers,
15141563 bearer_token_env_var,
15151564 } => {
1565+ if remote_environment {
1566+ return Err ( StartupOutcomeError :: from ( anyhow ! (
1567+ // Remote HTTP needs the future low-level executor
1568+ // `network/request` API so reqwest runs on the executor side.
1569+ // Do not fall back to local HTTP here; the config explicitly
1570+ // asked for remote placement.
1571+ "remote streamable HTTP MCP server `{server_name}` is not implemented yet"
1572+ ) ) ) ;
1573+ }
1574+
1575+ // Local streamable HTTP remains the existing reqwest path from
1576+ // the orchestrator process.
15161577 let resolved_bearer_token =
15171578 match resolve_bearer_token ( server_name, bearer_token_env_var. as_deref ( ) ) {
15181579 Ok ( token) => token,
0 commit comments