Activate selected executor plugin MCPs in app-server (#27893)

## Why

#27870 teaches the MCP extension how to discover stdio MCP servers
declared by a selected executor plugin, but app-server does not yet
install that contributor or initialize its per-thread state. As a
result, `thread/start.selectedCapabilityRoots` can select the plugin
while its MCP servers remain inactive.

This PR closes that app-server wiring gap:

```text
thread/start(selectedCapabilityRoots)
    -> initialize the thread's selected-plugin MCP snapshot
    -> read the selected plugin's .mcp.json through its environment
    -> start declared stdio servers in that environment
    -> expose their tools only on the selected thread
```

## What changed

- Install the selected-executor-plugin MCP contributor in app-server
using the existing shared `EnvironmentManager`.
- Initialize its frozen thread snapshot when `thread/start` includes
selected capability roots.
- Document that selected plugin stdio MCPs are activated in their owning
environment.
- Add an app-server E2E covering the complete selection-to-tool-call
path.

The E2E verifies that:

- the selected MCP process receives an executor-only environment value,
proving the tool runs through the selected environment;
- the MCP tool is advertised to the model and can be called;
- a normal MCP config reload does not discard the thread's frozen
selected-plugin registration;
- another thread without the selected root does not see the MCP server.

## Scope

- Existing sessions without `selectedCapabilityRoots` are unchanged.
- Only stdio MCP declarations are activated. HTTP declarations remain
inactive.
- This does not change selected-root persistence across resume/fork or
add hosted-plugin behavior.

## Verification

- Focused app-server E2E:
`selected_executor_plugin_exposes_its_stdio_mcp_only_to_that_thread`

## Stack

Stacked on #27870.
This commit is contained in:
jif
2026-06-15 15:23:37 +01:00
committed by GitHub
parent e5253b97fb
commit c8c78b63a7
10 changed files with 321 additions and 20 deletions

View File

@@ -187,7 +187,8 @@ def codex_rust_crate(
test_shard_counts = {},
test_tags = [],
unit_test_timeout = None,
extra_binaries = []):
extra_binaries = [],
extra_binaries_non_windows = []):
"""Defines a Rust crate with library, binaries, and tests wired for Bazel + Cargo parity.
The macro mirrors Cargo conventions: it builds a library when `src/` exists,
@@ -232,6 +233,9 @@ def codex_rust_crate(
generated from `src/**/*.rs`.
extra_binaries: Additional binary labels to surface as test data and
`CARGO_BIN_EXE_*` environment variables. These are only needed for binaries from a different crate.
extra_binaries_non_windows: Like `extra_binaries`, but omitted from
Windows test data and environment variables. Tests using these
binaries must be excluded when targeting Windows.
"""
test_env = {
# The launcher resolves an absolute workspace root at runtime so
@@ -370,6 +374,32 @@ def codex_rust_crate(
cargo_env_runfiles[binary_label] = "CARGO_BIN_EXE_" + binary
cargo_env["CARGO_BIN_EXE_" + binary] = "$(rlocationpath %s)" % binary_label
integration_test_binaries = sanitized_binaries
integration_test_cargo_env = cargo_env
integration_test_cargo_env_runfiles = cargo_env_runfiles
if extra_binaries_non_windows:
non_windows_sanitized_binaries = []
non_windows_cargo_env = {}
non_windows_cargo_env_runfiles = {}
for binary_label in extra_binaries_non_windows:
non_windows_sanitized_binaries.append(binary_label)
binary = Label(binary_label).name
non_windows_cargo_env_runfiles[binary_label] = "CARGO_BIN_EXE_" + binary
non_windows_cargo_env["CARGO_BIN_EXE_" + binary] = "$(rlocationpath %s)" % binary_label
integration_test_binaries = sanitized_binaries + select({
"@platforms//os:windows": [],
"//conditions:default": non_windows_sanitized_binaries,
})
integration_test_cargo_env = select({
"@platforms//os:windows": cargo_env,
"//conditions:default": cargo_env | non_windows_cargo_env,
})
integration_test_cargo_env_runfiles = select({
"@platforms//os:windows": cargo_env_runfiles,
"//conditions:default": cargo_env_runfiles | non_windows_cargo_env_runfiles,
})
integration_test_kwargs = {}
if integration_test_args:
integration_test_kwargs["args"] = integration_test_args
@@ -418,7 +448,7 @@ def codex_rust_crate(
crate_name = test_crate_name,
crate_root = test,
srcs = [test],
data = native.glob(["tests/**"], allow_empty = True) + sanitized_binaries + test_data_extra,
data = native.glob(["tests/**"], allow_empty = True) + integration_test_binaries + test_data_extra,
compile_data = native.glob(["tests/**"], allow_empty = True) + integration_compile_data_extra,
deps = all_crate_deps(normal = True, normal_dev = True) + maybe_deps + deps_extra,
# Bazel has emitted both `codex-rs/<crate>/...` and
@@ -440,7 +470,7 @@ def codex_rust_crate(
# The launcher rewrites them to absolute paths at execution
# time so tests keep working after chdir_workspace_root and on
# manifest-only platforms.
runfile_env = cargo_env_runfiles,
runfile_env = integration_test_cargo_env_runfiles,
test_bin = ":" + integration_test_binary,
workspace_root_marker = "//codex-rs/utils/cargo-bin:repo_root.marker",
target_compatible_with = WINDOWS_GNULLVM_INCOMPATIBLE,
@@ -457,7 +487,7 @@ def codex_rust_crate(
crate_name = test_crate_name,
crate_root = test,
srcs = [test],
data = native.glob(["tests/**"], allow_empty = True) + sanitized_binaries + test_data_extra,
data = native.glob(["tests/**"], allow_empty = True) + integration_test_binaries + test_data_extra,
compile_data = native.glob(["tests/**"], allow_empty = True) + integration_compile_data_extra,
deps = all_crate_deps(normal = True, normal_dev = True) + maybe_deps + deps_extra,
# Bazel has emitted both `codex-rs/<crate>/...` and
@@ -468,7 +498,7 @@ def codex_rust_crate(
"--remap-path-prefix=codex-rs=",
],
rustc_env = rustc_env,
env = cargo_env,
env = integration_test_cargo_env,
target_compatible_with = WINDOWS_GNULLVM_INCOMPATIBLE,
tags = test_tags,
**test_kwargs
@@ -485,7 +515,7 @@ def codex_rust_crate(
crate_name = test_crate_name,
crate_root = test,
srcs = [test],
data = native.glob(["tests/**"], allow_empty = True) + sanitized_binaries + test_data_extra,
data = native.glob(["tests/**"], allow_empty = True) + integration_test_binaries + test_data_extra,
compile_data = native.glob(["tests/**"], allow_empty = True) + integration_compile_data_extra,
deps = all_crate_deps(normal = True, normal_dev = True) + maybe_deps + deps_extra,
rustc_flags = rustc_flags_extra + WINDOWS_RUSTC_LINK_FLAGS + [
@@ -493,7 +523,7 @@ def codex_rust_crate(
"--remap-path-prefix=codex-rs=",
],
rustc_env = rustc_env,
env = cargo_env,
env = integration_test_cargo_env,
target_compatible_with = WINDOWS_GNULLVM_ONLY,
tags = test_tags + ["manual"],
)
@@ -501,8 +531,8 @@ def codex_rust_crate(
workspace_root_test(
name = test_name + "-windows-cross",
chdir_workspace_root = False,
env = cargo_env,
runfile_env = cargo_env_runfiles,
env = integration_test_cargo_env,
runfile_env = integration_test_cargo_env_runfiles,
test_bin = ":" + windows_cross_test_binary,
workspace_root_marker = "//codex-rs/utils/cargo-bin:repo_root.marker",
target_compatible_with = WINDOWS_GNULLVM_ONLY,