From 620164e5114e7706ee1e4ca32dc89e8d541ada4b Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Tue, 20 Jan 2026 18:12:25 -0800 Subject: [PATCH] Covert to use absolutePathBuf for mcp cwd --- codex-rs/core/config.schema.json | 6 ++- codex-rs/core/src/config/mod.rs | 7 +++- codex-rs/core/src/config/types.rs | 44 ++++++--------------- codex-rs/core/src/mcp_connection_manager.rs | 1 + 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 596fe6d2f0..45f6896298 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1104,7 +1104,11 @@ }, "cwd": { "default": null, - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ] }, "disabled_tools": { "default": null, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 934c542638..a33776f616 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -2864,7 +2864,7 @@ ZIG_VAR = "3" async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> { let codex_home = TempDir::new()?; - let cwd_path = PathBuf::from("/tmp/codex-mcp"); + let cwd_path = AbsolutePathBuf::from_absolute_path("/tmp/codex-mcp").expect("expected cwd"); let servers = BTreeMap::from([( "docs".to_string(), McpServerConfig { @@ -2901,7 +2901,10 @@ ZIG_VAR = "3" let docs = loaded.get("docs").expect("docs entry"); match &docs.transport { McpServerTransportConfig::Stdio { cwd, .. } => { - assert_eq!(cwd.as_deref(), Some(Path::new("/tmp/codex-mcp"))); + assert_eq!( + cwd.as_ref().map(AbsolutePathBuf::as_path), + Some(Path::new("/tmp/codex-mcp")) + ); } other => panic!("unexpected transport {other:?}"), } diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 731e536c61..d32e9bc64e 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -10,7 +10,6 @@ use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; -use std::path::PathBuf; use std::time::Duration; use wildmatch::WildMatchPattern; @@ -88,7 +87,7 @@ pub(crate) struct RawMcpServerConfig { #[serde(default)] pub env_vars: Option>, #[serde(default)] - pub cwd: Option, + pub cwd: Option, pub http_headers: Option>, #[serde(default)] pub env_http_headers: Option>, @@ -161,7 +160,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { args: raw.args.clone().unwrap_or_default(), env: raw.env.clone(), env_vars: raw.env_vars.clone().unwrap_or_default(), - cwd: raw.cwd.take().map(expand_tilde_pathbuf), + cwd: raw.cwd.take(), } } else if let Some(url) = raw.url.clone() { throw_if_set("streamable_http", "args", raw.args.as_ref())?; @@ -195,30 +194,6 @@ const fn default_enabled() -> bool { true } -fn expand_tilde_pathbuf(path: PathBuf) -> PathBuf { - let Some(path_str) = path.to_str() else { - return path; - }; - - if cfg!(target_os = "windows") { - return path; - } - - let Some(home) = home_dir() else { - return path; - }; - - if path_str == "~" { - return home; - } - - if let Some(rest) = path_str.strip_prefix("~/") { - return home.join(rest); - } - - path -} - fn expand_tilde_string(value: String) -> String { if cfg!(target_os = "windows") { return value; @@ -252,7 +227,7 @@ pub enum McpServerTransportConfig { #[serde(default, skip_serializing_if = "Vec::is_empty")] env_vars: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] - cwd: Option, + cwd: Option, }, /// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http StreamableHttp { @@ -804,6 +779,7 @@ pub enum Personality { #[cfg(test)] mod tests { use super::*; + use codex_utils_absolute_path::AbsolutePathBufGuard; use pretty_assertions::assert_eq; #[test] @@ -916,13 +892,17 @@ mod tests { args: vec![], env: None, env_vars: Vec::new(), - cwd: Some(PathBuf::from("/tmp")), + cwd: Some( + AbsolutePathBuf::from_absolute_path("/tmp").expect("expected absolute mcp cwd"), + ), } ); } #[test] fn deserialize_stdio_command_server_config_expands_tilde_cwd() { + let base_dir = std::env::temp_dir(); + let _guard = AbsolutePathBufGuard::new(&base_dir); let cfg: McpServerConfig = toml::from_str( r#" command = "echo" @@ -932,12 +912,14 @@ mod tests { .expect("should deserialize command config with tilde cwd"); let expected_cwd = if cfg!(target_os = "windows") { - PathBuf::from("~/tmp") + AbsolutePathBuf::resolve_path_against_base("~/tmp", &base_dir) + .expect("expected absolute mcp cwd") } else { let Some(home) = home_dir() else { return; }; - home.join("tmp") + AbsolutePathBuf::from_absolute_path(home.join("tmp")) + .expect("expected absolute mcp cwd") }; assert_eq!( diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs index 0d638760f5..01a3b7d234 100644 --- a/codex-rs/core/src/mcp_connection_manager.rs +++ b/codex-rs/core/src/mcp_connection_manager.rs @@ -865,6 +865,7 @@ async fn make_rmcp_client( } => { let command_os: OsString = command.into(); let args_os: Vec = args.into_iter().map(Into::into).collect(); + let cwd = cwd.map(codex_utils_absolute_path::AbsolutePathBuf::into_path_buf); RmcpClient::new_stdio_client(command_os, args_os, env, &env_vars, cwd) .await .map_err(|err| StartupOutcomeError::from(anyhow!(err)))