Skip to content

Commit 8044335

Browse files
aibrahim-oaicodex
andcommitted
Prototype remote MCP stdio transport
Add MCP server environment selection and executor-backed stdio transport for remote MCP servers. Co-authored-by: Codex <noreply@openai.com>
1 parent 66533dd commit 8044335

42 files changed

Lines changed: 899 additions & 170 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codex-rs/Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/cli/src/mcp_cmd.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
297297

298298
let new_entry = McpServerConfig {
299299
transport: transport.clone(),
300+
environment: Default::default(),
300301
enabled: true,
301302
required: false,
302303
supports_parallel_tool_calls: false,

codex-rs/codex-mcp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ anyhow = { workspace = true }
1616
async-channel = { workspace = true }
1717
codex-async-utils = { workspace = true }
1818
codex-config = { workspace = true }
19+
codex-exec-server = { workspace = true }
1920
codex-login = { workspace = true }
2021
codex-otel = { workspace = true }
2122
codex-plugin = { workspace = true }

codex-rs/codex-mcp/src/mcp/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ fn codex_apps_mcp_server_config(config: &McpConfig, auth: Option<&CodexAuth>) ->
269269
http_headers,
270270
env_http_headers: None,
271271
},
272+
environment: Default::default(),
272273
enabled: true,
273274
required: false,
274275
supports_parallel_tool_calls: false,
@@ -354,6 +355,8 @@ pub async fn collect_mcp_snapshot_with_detail(
354355
submit_id,
355356
tx_event,
356357
SandboxPolicy::new_read_only_policy(),
358+
None,
359+
config.codex_home.clone(),
357360
config.codex_home.clone(),
358361
codex_apps_tools_cache_key(auth),
359362
tool_plugin_provenance,
@@ -420,6 +423,8 @@ pub async fn collect_mcp_server_status_snapshot_with_detail(
420423
submit_id,
421424
tx_event,
422425
SandboxPolicy::new_read_only_policy(),
426+
None,
427+
config.codex_home.clone(),
423428
config.codex_home.clone(),
424429
codex_apps_tools_cache_key(auth),
425430
tool_plugin_provenance,

codex-rs/codex-mcp/src/mcp/mod_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
193193
http_headers: None,
194194
env_http_headers: None,
195195
},
196+
environment: Default::default(),
196197
enabled: true,
197198
required: false,
198199
supports_parallel_tool_calls: false,
@@ -215,6 +216,7 @@ async fn effective_mcp_servers_preserve_user_servers_and_add_codex_apps() {
215216
http_headers: None,
216217
env_http_headers: None,
217218
},
219+
environment: Default::default(),
218220
enabled: true,
219221
required: false,
220222
supports_parallel_tool_calls: false,

codex-rs/codex-mcp/src/mcp/skill_dependencies.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ fn mcp_dependency_to_server_config(
119119
http_headers: None,
120120
env_http_headers: None,
121121
},
122+
environment: Default::default(),
122123
enabled: true,
123124
required: false,
124125
supports_parallel_tool_calls: false,
@@ -146,6 +147,7 @@ fn mcp_dependency_to_server_config(
146147
env_vars: Vec::new(),
147148
cwd: None,
148149
},
150+
environment: Default::default(),
149151
enabled: true,
150152
required: false,
151153
supports_parallel_tool_calls: false,

codex-rs/codex-mcp/src/mcp/skill_dependencies_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ fn collect_missing_respects_canonical_installed_key() {
3939
http_headers: None,
4040
env_http_headers: None,
4141
},
42+
environment: Default::default(),
4243
enabled: true,
4344
required: false,
4445
supports_parallel_tool_calls: false,
@@ -90,6 +91,7 @@ fn collect_missing_dedupes_by_canonical_key_but_preserves_original_name() {
9091
http_headers: None,
9192
env_http_headers: None,
9293
},
94+
environment: Default::default(),
9395
enabled: true,
9496
required: false,
9597
supports_parallel_tool_calls: false,

codex-rs/codex-mcp/src/mcp_connection_manager.rs

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ use async_channel::Sender;
3535
use codex_async_utils::CancelErr;
3636
use codex_async_utils::OrCancelExt;
3737
use codex_config::Constrained;
38+
use codex_config::McpServerEnvironment;
3839
use codex_config::types::OAuthCredentialsStoreMode;
40+
use codex_exec_server::Environment;
3941
use codex_protocol::ToolName;
4042
use codex_protocol::approvals::ElicitationRequest;
4143
use codex_protocol::approvals::ElicitationRequestEvent;
@@ -50,8 +52,11 @@ use codex_protocol::protocol::McpStartupStatus;
5052
use codex_protocol::protocol::McpStartupUpdateEvent;
5153
use codex_protocol::protocol::SandboxPolicy;
5254
use codex_rmcp_client::ElicitationResponse;
55+
use codex_rmcp_client::ExecutorStdioTransportRuntime;
56+
use codex_rmcp_client::LocalStdioTransportRuntime;
5357
use codex_rmcp_client::RmcpClient;
5458
use codex_rmcp_client::SendElicitation;
59+
use codex_rmcp_client::StdioTransportRuntime;
5560
use futures::future::BoxFuture;
5661
use futures::future::FutureExt;
5762
use 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

14821501
async 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

codex-rs/codex-mcp/src/mcp_connection_manager_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ fn mcp_init_error_display_prompts_for_github_pat() {
792792
http_headers: None,
793793
env_http_headers: None,
794794
},
795+
environment: Default::default(),
795796
enabled: true,
796797
required: false,
797798
supports_parallel_tool_calls: false,
@@ -842,6 +843,7 @@ fn mcp_init_error_display_reports_generic_errors() {
842843
http_headers: None,
843844
env_http_headers: None,
844845
},
846+
environment: Default::default(),
845847
enabled: true,
846848
required: false,
847849
supports_parallel_tool_calls: false,

codex-rs/config/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub use mcp_edit::load_global_mcp_servers;
6666
pub use mcp_types::AppToolApproval;
6767
pub use mcp_types::McpServerConfig;
6868
pub use mcp_types::McpServerDisabledReason;
69+
pub use mcp_types::McpServerEnvironment;
6970
pub use mcp_types::McpServerToolConfig;
7071
pub use mcp_types::McpServerTransportConfig;
7172
pub use mcp_types::RawMcpServerConfig;

0 commit comments

Comments
 (0)