Keep executor plugin MCP paths URI-native (#29628)

## Why

Executor-owned plugin roots are `PathUri`, but MCP config normalization
still converts them into a native `Path` using the app-server host's
rules. Relative `cwd` values can therefore resolve against the wrong
filesystem when host and executor path conventions differ.

This PR keeps executor MCP paths URI-native until the selected
environment launches the server, while retaining the existing host
parser behavior.

## What changed

- Keep one shared MCP normalization path with narrow host-`Path` and
executor-`PathUri` entrypoints.
- Preserve native host resolution for locally installed plugin MCP
configs.
- For executor configs, default `cwd` to the plugin root and resolve
relative working directories with the root URI's path convention.
- Accept explicit executor `file:` URIs only when they remain within the
selected plugin root.
- Preserve the selected environment id and existing remote
environment-variable ownership rules.
- Route the executor plugin provider through the URI-native entrypoint
without converting the root on the host.
- Ensure `codex doctor` does not probe executor-owned stdio commands or
foreign working directories on the host.
- Cover foreign Windows roots, relative and absolute executor working
directories, traversal rejection, runtime resolution, and doctor
behavior.

```text
plugin root:    file:///C:/plugins/demo
configured cwd: scripts
                  |
                  v
resolved cwd:  file:///C:/plugins/demo/scripts
                  |
                  v
launch through the selected executor
```

No new provider or filesystem abstraction is introduced.

## Stack

1. #29614 — add lexical `PathUri` containment.
2. #29620 — share URI-native manifest path resolution.
3. #28918 — keep selected plugin roots and resources URI-native.
4. #29626 — load executor skills without host path conversion.
5. **This PR** — resolve executor MCP working directories without host
path conversion.
This commit is contained in:
jif
2026-06-24 09:46:07 +01:00
committed by GitHub
Unverified
parent 2a320fedb5
commit 3e39e92f03
7 changed files with 213 additions and 116 deletions
+30 -1
View File
@@ -13,7 +13,6 @@ use codex_exec_server::Environment;
use codex_exec_server::EnvironmentManager;
use codex_protocol::models::PermissionProfile;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
use serde::Serialize;
@@ -225,6 +224,36 @@ mod tests {
}
}
#[tokio::test]
async fn remote_stdio_accepts_foreign_absolute_cwd() {
let runtime_context = McpRuntimeContext::new(
Arc::new(
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
/*local_runtime_paths*/ None,
)
.await,
),
PathBuf::from("/tmp"),
);
let mut remote_stdio = stdio_server("remote");
let McpServerTransportConfig::Stdio { cwd, .. } = &mut remote_stdio.transport else {
unreachable!("stdio helper should build stdio transport");
};
*cwd = Some(
PathUri::parse("file:///C:/plugins/demo")
.expect("foreign cwd URI")
.into(),
);
let resolved_runtime =
match runtime_context.resolve_server_environment("stdio", &remote_stdio) {
Ok(resolved_runtime) => resolved_runtime,
Err(error) => panic!("foreign cwd should resolve: {error}"),
};
assert!(resolved_runtime.is_some());
}
#[tokio::test]
async fn local_stdio_accepts_local_environment_when_available() {
let runtime_context = McpRuntimeContext::new(