mirror of
https://github.com/openai/codex.git
synced 2026-11-03 00:21:31 +00:00
[5/6] Wire executor-backed MCP stdio (#18212)
## Summary - Add the executor-backed RMCP stdio transport. - Wire MCP stdio placement through the executor environment config. - Cover local and executor-backed stdio paths with the existing MCP test helpers. ## Stack ```text o #18027 [6/6] Fail exec client operations after disconnect │ @ #18212 [5/6] Wire executor-backed MCP stdio │ o #18087 [4/6] Abstract MCP stdio server launching │ o #18020 [3/6] Add pushed exec process events │ o #18086 [2/6] Support piped stdin in exec process API │ o #18085 [1/6] Add MCP server environment config │ o main ``` --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
1 parent
e3f44ca3b3
commit
996aa23e4c
31 files changed
+1815
-76
No files matched your search
@@ -32,6 +32,7 @@ use codex_config::types::ApprovalsReviewer;
|
||||
use codex_config::types::BundledSkillsConfig;
|
||||
use codex_config::types::FeedbackConfigToml;
|
||||
use codex_config::types::HistoryPersistence;
|
||||
use codex_config::types::McpServerEnvVar;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_config::types::MemoriesConfig;
|
||||
@@ -2490,7 +2491,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
env_vars: vec!["ALPHA".to_string(), "BETA".to_string()],
|
||||
env_vars: vec!["ALPHA".into(), "BETA".into()],
|
||||
cwd: None,
|
||||
},
|
||||
experimental_environment: None,
|
||||
@@ -2526,7 +2527,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
let docs = loaded.get("docs").expect("docs entry");
|
||||
match &docs.transport {
|
||||
McpServerTransportConfig::Stdio { env_vars, .. } => {
|
||||
assert_eq!(env_vars, &vec!["ALPHA".to_string(), "BETA".to_string()]);
|
||||
assert_eq!(env_vars, &vec!["ALPHA".into(), "BETA".into()]);
|
||||
}
|
||||
other => panic!("unexpected transport {other:?}"),
|
||||
}
|
||||
@@ -2534,6 +2535,62 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replace_mcp_servers_serializes_sourced_env_vars() -> anyhow::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
env: None,
|
||||
env_vars: vec![
|
||||
"LEGACY".into(),
|
||||
McpServerEnvVar::Config {
|
||||
name: "REMOTE_TOKEN".to_string(),
|
||||
source: Some("remote".to_string()),
|
||||
},
|
||||
],
|
||||
cwd: None,
|
||||
},
|
||||
experimental_environment: None,
|
||||
enabled: true,
|
||||
required: false,
|
||||
supports_parallel_tool_calls: false,
|
||||
disabled_reason: None,
|
||||
startup_timeout_sec: None,
|
||||
tool_timeout_sec: None,
|
||||
default_tools_approval_mode: None,
|
||||
enabled_tools: None,
|
||||
disabled_tools: None,
|
||||
scopes: None,
|
||||
oauth_resource: None,
|
||||
tools: HashMap::new(),
|
||||
},
|
||||
)]);
|
||||
|
||||
apply_blocking(
|
||||
codex_home.path(),
|
||||
/*profile*/ None,
|
||||
&[ConfigEdit::ReplaceMcpServers(servers.clone())],
|
||||
)?;
|
||||
|
||||
let config_path = codex_home.path().join(CONFIG_TOML_FILE);
|
||||
let serialized = std::fs::read_to_string(&config_path)?;
|
||||
assert!(
|
||||
serialized
|
||||
.contains(r#"env_vars = ["LEGACY", { name = "REMOTE_TOKEN", source = "remote" }]"#),
|
||||
"serialized config missing sourced env_vars field:\n{serialized}"
|
||||
);
|
||||
|
||||
let loaded = load_global_mcp_servers(codex_home.path()).await?;
|
||||
assert_eq!(loaded, servers);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -136,6 +136,7 @@ pub fn model_availability_nux_count_edits(shown_count: &HashMap<String, u32>) ->
|
||||
mod document_helpers {
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerEnvVar;
|
||||
use codex_config::types::McpServerToolConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use toml_edit::Array as TomlArray;
|
||||
@@ -198,7 +199,7 @@ mod document_helpers {
|
||||
entry["env"] = table_from_pairs(env.iter());
|
||||
}
|
||||
if !env_vars.is_empty() {
|
||||
entry["env_vars"] = array_from_iter(env_vars.iter().cloned());
|
||||
entry["env_vars"] = array_from_env_vars(env_vars);
|
||||
}
|
||||
if let Some(cwd) = cwd {
|
||||
entry["cwd"] = value(cwd.to_string_lossy().to_string());
|
||||
@@ -348,6 +349,24 @@ mod document_helpers {
|
||||
TomlItem::Value(array.into())
|
||||
}
|
||||
|
||||
fn array_from_env_vars(env_vars: &[McpServerEnvVar]) -> TomlItem {
|
||||
let mut array = TomlArray::new();
|
||||
for env_var in env_vars {
|
||||
match env_var {
|
||||
McpServerEnvVar::Name(name) => array.push(name.clone()),
|
||||
McpServerEnvVar::Config { name, source } => {
|
||||
let mut table = InlineTable::new();
|
||||
table.insert("name", name.clone().into());
|
||||
if let Some(source) = source {
|
||||
table.insert("source", source.clone().into());
|
||||
}
|
||||
array.push(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
TomlItem::Value(array.into())
|
||||
}
|
||||
|
||||
fn table_from_pairs<'a, I>(pairs: I) -> TomlItem
|
||||
where
|
||||
I: IntoIterator<Item = (&'a String, &'a String)>,
|
||||
|
||||
@@ -696,7 +696,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
env_vars: vec!["FOO".to_string()],
|
||||
env_vars: vec!["FOO".into()],
|
||||
cwd: None,
|
||||
},
|
||||
experimental_environment: None,
|
||||
|
||||
Reference in new issue
Block a user