mcp: accept foreign absolute cwd for remote stdio (#29493)

## Why

Remote stdio MCP servers can run in an environment whose path convention
differs from the Codex host. A Windows cwd such as
`C:\Users\openai\share` is absolute for the executor but was rejected by
a POSIX orchestrator.

Built on #29501, now merged, which only clarifies the host-native
`PathUri` constructor name.

## What changed

- Deserialize MCP cwd values as `LegacyAppPathString` so config does not
apply host path rules.
- Interpret that spelling as host-native for local launches and convert
it to `PathUri` at executor launch.
- Skip host filesystem and command resolution checks for remote stdio in
`codex doctor`.
- Add host-independent config and executor-boundary coverage using the
foreign path convention for each test platform.

## Validation

- `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
codex-rmcp-client` (408 passed)
- `just test -p codex-cli -p codex-rmcp-client` (372 passed)
- `cargo check --workspace --tests`
- `just test` (11,311 passed; 43 unrelated environment/timing failures)
- `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
codex-mcp-extension -p codex-rmcp-client -p codex-tui`
This commit is contained in:
Adam Perry @ OpenAI
2026-06-22 18:33:51 -07:00
committed by GitHub
Unverified
parent 3310fc8ae5
commit 67009bc53f
20 changed files with 250 additions and 162 deletions
+98 -16
View File
@@ -1520,19 +1520,35 @@ async fn mcp_check_from_servers(servers: &HashMap<String, McpServerConfig>) -> D
if disabled_server {
continue;
}
if let Some(cwd) = cwd
&& !cwd.exists()
{
missing_env.push(format!("{name}: cwd does not exist ({})", cwd.display()));
}
if command.trim().is_empty() {
let command_is_empty = command.trim().is_empty();
if command_is_empty {
missing_env.push(format!("{name}: stdio command is empty"));
} else if let Err(err) =
stdio_command_resolves(command, cwd.as_deref(), env.as_ref())
{
missing_env.push(format!(
"{name}: stdio command {command:?} is not resolvable ({err})"
));
}
if server.is_local_environment() {
let host_native_cwd = cwd.as_ref().map(|cwd| Path::new(cwd.as_str()));
if let Some(cwd) = host_native_cwd
&& !cwd.exists()
{
missing_env.push(format!("{name}: cwd does not exist ({})", cwd.display()));
}
if !command_is_empty
&& let Err(err) =
stdio_command_resolves(command, host_native_cwd, env.as_ref())
{
missing_env.push(format!(
"{name}: stdio command {command:?} is not resolvable ({err})"
));
}
} else {
match cwd {
Some(cwd) if cwd.to_inferred_path_uri().is_none() => {
missing_env
.push(format!("{name}: remote stdio cwd is not absolute ({cwd})"));
}
None => missing_env
.push(format!("{name}: remote stdio requires an explicit cwd")),
Some(_) => {}
}
}
if let Some(env) = env {
for key in env.keys().filter(|key| key.trim().is_empty()) {
@@ -1541,10 +1557,12 @@ async fn mcp_check_from_servers(servers: &HashMap<String, McpServerConfig>) -> D
}
for env_var in env_vars {
if env_var.is_remote_source() {
missing_env.push(format!(
"{name}: env_vars entry `{}` uses source `remote`, which requires remote MCP stdio",
env_var.name()
));
if server.is_local_environment() {
missing_env.push(format!(
"{name}: env_vars entry `{}` uses source `remote`, which requires remote MCP stdio",
env_var.name()
));
}
} else if !env_var_present(env_var.name()) {
missing_env.push(format!("{name}: env var {} is not set", env_var.name()));
}
@@ -3863,6 +3881,70 @@ mod tests {
}));
}
#[tokio::test]
async fn mcp_check_skips_host_path_checks_for_remote_stdio() {
#[cfg(not(windows))]
let cwd = r"C:\Users\openai\share";
#[cfg(windows)]
let cwd = "/home/openai/share";
let cwd = toml::Value::String(cwd.to_string());
let remote_server: McpServerConfig = toml::from_str(&format!(
r#"
command = "definitely-missing-codex-doctor-mcp"
environment_id = "remote"
cwd = {cwd}
required = true
env_vars = [{{ name = "REMOTE_ONLY_TOKEN", source = "remote" }}]
"#,
))
.expect("should deserialize remote MCP config");
let servers = HashMap::from([("remote".to_string(), remote_server)]);
let check = mcp_check_from_servers(&servers).await;
assert_eq!(check.status, CheckStatus::Ok);
assert_eq!(check.summary, "MCP configuration is locally consistent");
}
#[tokio::test]
async fn mcp_check_validates_remote_stdio_cwd() {
let missing_cwd: McpServerConfig = toml::from_str(
r#"
command = "echo"
environment_id = "remote"
required = true
"#,
)
.expect("should deserialize remote MCP config without cwd");
let relative_cwd: McpServerConfig = toml::from_str(
r#"
command = "echo"
environment_id = "remote"
cwd = "relative"
required = true
"#,
)
.expect("should deserialize remote MCP config with relative cwd");
let servers = HashMap::from([
("missing".to_string(), missing_cwd),
("relative".to_string(), relative_cwd),
]);
let check = mcp_check_from_servers(&servers).await;
assert_eq!(check.status, CheckStatus::Fail);
assert!(
check
.details
.contains(&"missing: remote stdio requires an explicit cwd".to_string())
);
assert!(
check
.details
.contains(&"relative: remote stdio cwd is not absolute (relative)".to_string())
);
}
#[cfg(unix)]
#[test]
fn read_probe_file_rejects_unreadable_file() {
+2 -2
View File
@@ -640,7 +640,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
let env_display = format_env_display(env.as_ref(), env_vars);
let cwd_display = cwd
.as_ref()
.map(|path| path.display().to_string())
.map(ToString::to_string)
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "-".to_string());
let status = format_mcp_status(cfg);
@@ -897,7 +897,7 @@ async fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Re
println!(" args: {args_display}");
let cwd_display = cwd
.as_ref()
.map(|path| path.display().to_string())
.map(ToString::to_string)
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "-".to_string());
println!(" cwd: {cwd_display}");