Commit Graph

22 Commits

  • skills: make backend plugin skills invocable without an executor (#27387)
    ## Why
    
    #27198 made the extension-owned `codex_apps` MCP connection the hosted
    plugin runtime, but its `mcp/skill` resources still bypassed the skills
    extension. App-server could list and read those resources through
    generic MCP APIs, but a thread with no selected environment did not
    expose them in the model's skills catalog or load their `SKILL.md`
    through `$skill`.
    
    Hosted skills should stay remote while using the same typed catalog,
    source authority, deduplication, bounded contextual catalog, and
    selected-skill prompt injection as host and executor skills. They should
    not be downloaded or exposed as ambient filesystem paths.
    
    ## What changed
    
    - Add a session-scoped `McpResourceClient` over the replaceable MCP
    connection manager so resource list/read calls follow startup and
    refresh replacements.
    - Add a `BackendSkillProvider` that pages `codex_apps` resources,
    accepts bounded and validated `mcp/skill` entries, and reads a selected
    skill's `SKILL.md` through the same MCP connection.
    - Register the remote provider in app-server and include it in the
    skills catalog even when a thread has no selected capability roots or
    executor.
    - Contribute hosted skill metadata through the bounded
    `AvailableSkillsInstructions` developer-context path, exclude remote
    entries from per-turn catalog injection, and classify `<skills>`
    messages as contextual developer content so rollback can trim and
    rebuild them correctly.
    
    ## Testing
    
    - Extend the app-server MCP resource integration test with
    `environments: []` to exercise two-page discovery, filter a
    non-`mcp/skill` resource, verify the escaped developer catalog entry and
    user-role `<skill>` fragment containing the fetched `SKILL.md`, and
    preserve generic MCP resource reads.
    - Add core event-mapping coverage that classifies `<skills>` developer
    messages as contextual history.
  • Use latest-wins MCP manager replacement (#27259)
    ## Summary
    
    We originally addressed startup prewarming holding the read side of
    `RwLock<McpConnectionManager>` by snapshotting tool-list state. Review
    feedback identified the broader ownership problem: the outer
    synchronization should only publish or retrieve the current manager,
    while MCP operations rely on the manager's internal synchronization. A
    follow-up preserved operation retirement with a separate gate, but
    further review questioned whether that synchronization was actually
    required and whether we could support latest-wins replacement instead.
    
    This PR now stores the current MCP manager in `ArcSwap`. Each operation
    uses `load_full()` to obtain an owned `Arc<McpConnectionManager>`, then
    performs MCP I/O without retaining the publication mechanism. Refresh
    cancels obsolete startup work, constructs a replacement, and atomically
    publishes it. New operations see the latest manager, while operations
    that already loaded the previous manager retain a valid handle. Refresh
    happens at a turn boundary, so there should be no active user tool calls
    to drain.
    
    Git history supports dropping the outer `RwLock`. It was introduced in
    `03ffe4d595` on November 17, 2025 for non-blocking MCP startup: the
    session published an empty manager, startup initialized that same object
    while holding the write lock, and readers waited for initialization.
    `7cd2e84026` on February 19, 2026 removed that two-phase initialization
    in favor of constructing a fresh manager and swapping it in, explicitly
    noting that `Option` or `OnceCell` could replace the placeholder design.
    Hot reload later reused the existing lock to publish a replacement, but
    I found no indication that the lock was introduced to guarantee
    in-flight tool calls finish before refresh or shutdown.
    
    Terminal shutdown remains separate from refresh: it aborts startup
    prewarming and active tasks before shutting down the current manager, so
    tool calls may be interrupted and no model WebSocket work continues
    after shutdown. Focused regression coverage exercises pending tool-list
    cancellation, deferred refresh, and startup-prewarm shutdown.
  • [codex] Make MCP connection startup fallible (#27261)
    ## Why
    
    Required MCP server startup was enforced in `Session::new` after
    `McpConnectionManager` had already created the clients. That split let
    other manager construction paths bypass the same requirement and exposed
    manager internals solely so the session could validate them. Keeping
    required-server readiness in the constructor gives every caller one
    consistent startup contract.
    
    ## What changed
    
    - make `McpConnectionManager::new` return `anyhow::Result<Self>` and
    fail when an enabled, required server cannot initialize
    - pass the startup cancellation token into the constructor so
    required-server waits remain cancellable
    - propagate constructor failures through resource reads, connector
    discovery, and MCP status collection
    - preserve the active manager and cancellation token when a refreshed
    replacement fails
    - keep required-startup failure collection private and cover the
    constructor error contract directly
    
    ## Validation
    
    - updated the focused connection-manager test to assert the complete
    required-server startup error
    - local tests not run; relying on CI
  • [codex] Tighten MCP connection manager API visibility and order (#27257)
    ## Summary
    
    - order `McpConnectionManager` methods by visibility, with the primary
    constructor and public API first
    - restrict `list_available_server_infos` to `codex-mcp`
    - make `new_uninitialized` a private test-only helper
    
    ## Why
    
    The manager exposed methods that are only used inside `codex-mcp` or its
    unit tests. Tightening those methods keeps the exported API intentional,
    while the new ordering makes the supported surface easier to scan.
    
    ## Validation
    
    - `just fmt`
    - `git diff --check`
    - local tests not run; relying on CI
  • Expose MCP server info as part of server status (#24698)
    # Summary
    
    Expose MCP server info via App Server (when available) so apps can
    render a richer MCP experience
  • Update rmcp to 1.7.0 (#24763)
    WIll make it easier to uprev when the new draft spec is supported.
    
    Also updates reqwest where needed for compatibility but doesn't update
    it everywhere since this is already a large diff.
    
    The new version of rmcp handles certain kinds of authentication failures
    differently, this patch includes support for identifying the failing scope
    in a WWW-Authenticate header.
  • fix(core): instrument stalled tool-listing handoff (#24667)
    ## Why
    
    When a turn needs a follow-up request after tool output is recorded,
    Codex can still appear stuck in `Thinking` before the next `/responses`
    request is opened. The existing local trace showed the last completed
    response and the absence of a new backend request, but it did not show
    whether the stall was in tool-router preparation or later request setup.
    
    Issue: N/A (internal incident investigation)
    
    ## What Changed
    
    Added trace spans around the pre-stream tool-router handoff in
    `core/src/session/turn.rs`, including the `built_tools` phase and the
    MCP manager read lock.
    
    Added per-server MCP tool-listing spans and trace breadcrumbs in
    `codex-mcp/src/connection_manager.rs` with startup snapshot /
    startup-complete state so a pending MCP client is visible in feedback
    logs instead of looking like a silent hang.
    
    ## Verification
    
    - `just fmt`
    - `just test -p codex-mcp`
    - `just test -p codex-core` (prior full rerun fails in this workspace on
    unrelated integration tests: code-mode output length expectations, one
    shell timeout formatting assertion, and shell snapshot timeouts; latest
    review-fix rerun compiled and passed 1160 tests before I stopped the
    abnormally slow unrelated suite)
  • Move MCP tool naming mode into manager (#21576)
    ## Why
    
    The `non_prefixed_mcp_tool_names` feature should be applied where MCP
    tools become model-visible, not by remapping names later in core.
    Keeping the decision in `McpConnectionManager` construction makes
    `ToolInfo` the single shaped view that spec building, deferred tool
    search, routing, and unavailable-tool placeholders can consume directly.
    
    This also preserves the existing external behavior while the feature is
    off, and keeps the feature-on behavior for code mode and hooks explicit
    at the manager boundary.
    
    ## What Changed
    
    - Add `McpToolNameMode` to `codex-mcp` and flow it through `McpConfig`
    into `McpConnectionManager::new`.
    - Normalize MCP `ToolInfo` names in the manager using either
    legacy-prefixed namespaces or non-prefixed namespaces; the legacy path
    adds `mcp__` without restoring the old trailing namespace suffix.
    - Remove the core-side MCP name remapping path so specs, tool search,
    session resolution, and unavailable-tool placeholder construction use
    the manager-provided `ToolName` values directly.
    - Keep code mode flattening on the `__` namespace separator.
    - Preserve hook compatibility by giving non-prefixed MCP hook names
    legacy `mcp__...` matcher aliases.
    - Add/adjust integration and unit coverage for non-prefixed code-mode
    behavior, hook matching with the feature on and off, and manager-level
    legacy prefixing.
    
    ## Testing
    
    - `cargo test -p codex-mcp --lib`
    - `cargo test -p codex-core --lib tools::spec::tests -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tools -- --nocapture`
    - `cargo test -p codex-core --lib mcp_tool_exposure -- --nocapture`
    - `cargo test -p codex-core --test all mcp_tool -- --nocapture`
    - `cargo test -p codex-core --test all search_tool -- --nocapture`
    - `cargo test -p codex-core --test all hooks_mcp -- --nocapture`
    - `cargo test -p codex-core --test all
    code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled --
    --nocapture`
    - `cargo test -p codex-tools`
    - `cargo test -p codex-features`
  • Route MCP servers through explicit environments (#23583)
    ## Summary
    - route each configured MCP server through an explicit per-server
    `environment_id` instead of a manager-wide remote toggle
    - default omitted `environment_id` to `local`, resolve named ids through
    `EnvironmentManager`, and fail only the affected MCP server when an
    explicit id is unknown
    - keep local stdio on the existing local launcher path for now, while
    named-environment stdio uses the selected environment backend and
    requires an absolute `cwd`
    - allow local HTTP MCP servers to keep using the ambient HTTP client
    when no local `Environment` is configured; named-environment HTTP MCPs
    use that environment's HTTP client
    
    ## Validation
    - devbox Bazel build: `bazel build --bes_backend= --bes_results_url=
    //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server
    //codex-rs/rmcp-client:test_streamable_http_server`
    - devbox app-server config matrix with real `config.toml` /
    `environments.toml` files covering omitted local, explicit local,
    omitted local under remote default, explicit remote stdio, local HTTP
    without local env, explicit remote HTTP, local stdio without local env,
    unknown explicit env, and remote stdio without `cwd`
  • Include plugin id in plugin MCP tool metadata (#23353)
    Adding the id of the plugin that contains the MCP (if any) so we can
    apply filters at plugin level.
    
    ## Summary
    - carry the plugin owner into MCP runtime provenance
    - attach `plugin_id` to outbound plugin-backed MCP tool-call `_meta`
    - avoid misattributing user-configured MCP servers that shadow plugin
    server names
    
    ## Testing
    - `just fmt`
    - `just fix -p codex-mcp`
    - `just fix -p codex-core`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core
    plugin_mcp_tool_call_request_meta_includes_plugin_id`
    - `cargo test -p codex-core
    to_mcp_config_omits_plugin_id_when_user_server_shadows_plugin_mcp`
    - `cargo test -p codex-core
    rebuild_preserving_session_layers_refreshes_plugin_derived_mcp_config`
    - `git diff --check`
    
    ## Notes
    - Attempted `cargo test -p codex-core`; it aborted in
    `agent::control::tests::resume_agent_from_rollout_skips_descendants_when_parent_resume_fails`
    with a stack overflow before the full suite completed.
  • permissions: resolve profile identity with constraints (#22683)
    ## Why
    
    This PR is the invariant-cleanup layer that follows the workspace-roots
    base merged in [#22610](https://github.com/openai/codex/pull/22610).
    
    #22610 adds `[permissions.<id>.workspace_roots]` and keeps runtime
    workspace roots separate from the raw permission profile, but its
    in-memory representation is intentionally transitional: `Permissions`
    still carries the selected profile identity next to a constrained
    `PermissionProfile`. That makes APIs such as
    `set_constrained_permission_profile_with_active_profile()` fragile
    because the id and value only mean the right thing when every caller
    keeps them in sync.
    
    This PR introduces a single resolved profile state so profile identity,
    `extends`, the profile value, and profile-declared workspace roots
    travel together. The next PR,
    [#22611](https://github.com/openai/codex/pull/22611), builds on this by
    changing the app-server turn API to select permission profiles by id
    plus runtime workspace roots.
    
    ## Stack Context
    
    - #22610, now merged: adds profile-declared `workspace_roots`, runtime
    workspace roots, and `:workspace_roots` materialization.
    - This PR: replaces the parallel active-profile/profile-value fields
    with `PermissionProfileState`.
    - #22611: switches app-server turn updates toward profile ids plus
    runtime workspace roots.
    - #22612: updates TUI/exec summaries to show the effective workspace
    roots.
    
    Keeping this separate from #22611 is deliberate: reviewers can validate
    the internal state invariant before reviewing the app-server protocol
    migration.
    
    ## What Changed
    
    - Added `ResolvedPermissionProfile::{Legacy, BuiltIn, Named}` and
    `PermissionProfileState`.
    - Typed built-in profile ids with `BuiltInPermissionProfileId`.
    - Moved selected profile identity and profile-declared workspace roots
    into the resolved state.
    - Replaced `Permissions` parallel profile fields with one
    `permission_profile_state`.
    - Removed `set_constrained_permission_profile_with_active_profile()`
    from session sync paths.
    - Kept trusted session replay/`SessionConfigured` compatibility through
    explicit session snapshot helpers.
    - Updated session configuration, MCP initialization, app-server, exec,
    TUI, and guardian call sites to consume `&PermissionProfile` directly.
    
    ## Review Guide
    
    Start with `codex-rs/core/src/config/resolved_permission_profile.rs`; it
    is the new invariant boundary. Then review
    `codex-rs/core/src/config/mod.rs` to see how config loading records
    active profile identity and profile workspace roots. The remaining
    call-site changes are mostly mechanical fallout from
    `Permissions::permission_profile()` returning `&PermissionProfile`
    instead of `&Constrained<PermissionProfile>`.
    
    ## Verification
    
    The existing config/session coverage now constructs and asserts through
    `PermissionProfileState`. The workspace-root config test also asserts
    that profile-declared roots are preserved in the resolved state, which
    is the behavior #22611 relies on when runtime roots become mutable
    through the app-server API.
    
    ---
    [//]: # (BEGIN SAPLING FOOTER)
    Stack created with [Sapling](https://sapling-scm.com). Best reviewed
    with [ReviewStack](https://reviewstack.dev/openai/codex/pull/22683).
    * #22612
    * #22611
    * __->__ #22683
  • Simplify MCP tool handler plumbing (#21595)
    ## Why
    The MCP tool path had accumulated a few core-owned special cases: a
    dedicated payload variant, resolver plumbing, a legacy `AfterToolUse`
    translation path, and a side channel for parallel-call metadata. That
    made `ToolRegistry` and the spec builder know more about MCP than they
    needed to.
    
    This change moves MCP-specific execution details back onto `ToolInfo`
    and `McpHandler` so `codex-core` can treat MCP calls like normal
    function calls while still preserving MCP-specific dispatch and
    telemetry behavior where it belongs.
    
    ## What changed
    - removed `resolve_mcp_tool_info`, `ToolPayload::Mcp`, `ToolKind`, and
    the remaining registry-side MCP resolver path
    - stored MCP routing metadata directly on `McpHandler` and `ToolInfo`,
    including `supports_parallel_tool_calls`
    - deleted the legacy `AfterToolUse` consumer in `core`, which removes
    the need for handler-specific `after_tool_use_payload` implementations
    - switched tool-result telemetry to handler-provided tags and kept
    MCP-specific dispatch payload construction inside the handler
    - simplified tool spec planning/building by passing `ToolInfo` directly
    and dropping the direct/deferred MCP wrapper structs and the
    parallel-server side table
    
    ## Testing
    - `cargo check -p codex-core -p codex-mcp -p codex-otel`
    - `cargo test -p codex-core
    mcp_parallel_support_uses_exact_payload_server`
    - `cargo test -p codex-core
    direct_mcp_tools_register_namespaced_handlers`
    - `cargo test -p codex-core
    search_tool_description_lists_each_mcp_source_once`
    - `cargo test -p codex-mcp
    list_all_tools_uses_startup_snapshot_while_client_is_pending`
    - `just fix -p codex-core -p codex-mcp -p codex-otel`
  • [elicitation] Advertise new url elicitation capability when auth_elicitation is enabled. (#22188)
    ## Why
    
    We've added support for auth elicitation behind the auth_elicitation
    flag, but servers need to explicitly check the capability before it
    decides to send elicitations in order to be backward compatible. This PR
    adds the capability advertising conditioned on the flag.
    
    ## What changed
    
    - Build `client_elicitation_capability` from the `AuthElicitation`
    feature state.
    - Thread that capability through MCP config, session startup, and
    `McpConnectionManager` so RMCP initialization advertises the correct
    elicitation support.
    - Advertise both `form` and `url` elicitation when the feature is
    enabled, and preserve the empty default capability when it is disabled.
    - Add coverage for the feature-derived config shape and the advertised
    initialization payload.
    
    ## Testing
    
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core
    to_mcp_config_preserves_auth_elicitation_feature_from_config`
    - `cargo test -p codex-core` *(currently fails outside this change in
    `tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`
    with a stack overflow after unrelated tests have started running)*
  • chore: drop built-in MCPs (#22173)
    Drop something that was never used
  • [codex] Remove string-keyed MCP tool maps (#21454)
    ## Summary
    
    This PR removes the synthetic `HashMap<String, ToolInfo>` keys from MCP
    tool discovery. `McpConnectionManager::list_all_tools()` now returns
    normalized `Vec<ToolInfo>`, and downstream code derives identity from
    `ToolInfo::canonical_tool_name()`.
    
    The motivation is to keep model-visible tool identity on
    `ToolName`/`ToolInfo` instead of parallel string map keys, so future
    namespace changes do not have to preserve otherwise-unused lookup keys.
    
    ## Changes
    
    - Rename the MCP normalization path from `qualify_tools` to
    `normalize_tools_for_model` and return tool values directly.
    - Flow MCP tool lists through connectors, plugin injection, router/spec
    building, code mode, and tool search as vectors/slices.
    - Keep direct/deferred subtraction local to `mcp_tool_exposure`, using
    `ToolName` values.
    - Update tests to compare `ToolName` instances where MCP identity
    matters.
    
    ## Validation
    
    - `cargo test -p codex-mcp test_normalize_tools`
    - `cargo test -p codex-core mcp_tool_exposure`
    - `cargo test -p codex-core
    direct_mcp_tools_register_namespaced_handlers`
    - `cargo test -p codex-core
    search_tool_registers_namespaced_mcp_tool_aliases`
    - `just fix -p codex-mcp`
    - `just fix -p codex-core`
  • feat: make built-in MCPs first-class runtime servers (#21356)
    ## DISCLAIMER
    This is experimental and no production service must rely on this
    
    ## Why
    
    Built-in MCPs are product-owned runtime capabilities, but they were
    previously flattened into the same config-backed stdio path as
    user-configured servers. That made them depend on a hidden `codex
    builtin-mcp` re-exec path, exposed them through config-oriented CLI
    flows, and erased distinctions the runtime needs to preserve—most
    notably whether an MCP call should count as external context for
    memory-mode pollution.
    
    ## What changed
    
    - Model product-owned built-ins separately from config-backed MCP
    servers via `BuiltinMcpServer` and `EffectiveMcpServer`.
    - Launch built-ins in process through a reusable async transport instead
    of the hidden `builtin-mcp` stdio subcommand.
    - Keep config-oriented CLI operations such as `codex mcp
    list/get/login/logout` scoped to configured servers, while merging
    built-ins only into the effective runtime server set.
    - Retain server metadata after launch so parallel-tool support and
    context classification come from the live server set; built-in
    `memories` is now classified as local Codex state rather than external
    context.
    
    ## Test plan
    
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-core --test suite
    builtin_memories_mcp_call_does_not_mark_thread_memory_mode_polluted_when_configured`
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>
  • Route opted-in MCP elicitations through Guardian (#19431)
    # Motivation
    
    Browser Use origin-access prompts are MCP elicitations, not direct
    tool-call approval prompts, so they were bypassing the Guardian approval
    path. We need a generic opt-in that lets eligible MCP elicitations use
    Guardian when the current turn already routes approvals there.
    
    # Description
    
    Add a generic elicitation reviewer hook in codex-mcp and wire codex-core
    to pass a Guardian reviewer callback when creating the MCP connection
    manager. The reviewer validates explicit mcp_tool_call opt-in metadata,
    builds a Guardian MCP tool-call review request from
    server/tool/connector metadata and tool params, and maps Guardian
    approval, denial, timeout, and cancellation decisions back to MCP
    elicitation responses.
    
    The new option to trigger this in the `_meta` object is:
    ```
    "codex_request_type": "approval_request",
    ```
    
    # Testing
    
    - RUST_MIN_STACK=8388608 NEXTEST_STATUS_LEVEL=leak cargo nextest run
    --no-fail-fast --cargo-profile ci-test --test-threads 2
    - cargo clippy --tests -- -D warnings
    - cargo fmt -- --config imports_granularity=Item --check
    - cargo shear
    - pnpm run format
    - python3 .github/scripts/verify_cargo_workspace_manifests.py
    - python3 .github/scripts/verify_tui_core_boundary.py
    - python3 .github/scripts/verify_bazel_clippy_lints.py
    - git diff --check
  • Support Codex Apps auth elicitations (#19193)
    ## Summary
    
    - request URL-mode MCP elicitations when Codex Apps tool calls fail with
    connector auth metadata
    - route Codex Apps auth URL elicitations into the TUI app-link flow
    
    ## Test plan
    
    - `just fmt`
    - `cargo test -p codex-core mcp_tool_call::tests`
    - `cargo test -p codex-mcp`
    - `cargo test -p codex-tui bottom_pane::app_link_view::tests`
    - `just fix -p codex-core`
    - `just fix -p codex-mcp`
    - `just fix -p codex-tui`
    
    Also attempted broader local runs:
    
    - `cargo test -p codex-core` fails in unrelated
    config/request-permission/proxy-sensitive tests under the current Codex
    Desktop environment.
    - `cargo test -p codex-tui` fails in unrelated status
    snapshots/trust-default tests because the ambient environment renders
    workspace-write/network permission defaults.
  • Auto-deny MCP elicitations for Xcode 26.4 clients (#21113)
    ## Summary
    
    Xcode 26.4 was built against app-server behavior from before MCP
    elicitation requests became client-visible in CLI 0.120.0 via #17043.
    That client line does not expect the new events/messages, so this PR
    restores the old behavior for exactly that client/version combination.
    
    The compatibility handling stays in the app-server layer: when the
    initialized client is `Xcode` and its version starts with `26.4`, the
    app server marks the live Codex thread so MCP elicitations are
    auto-denied. The flag is applied on thread start/resume/fork/turn
    attachment, carried through `Codex`/`CodexThread`, and stored on
    `McpConnectionManager` so refreshed MCP managers preserve the behavior.
    
    ## Notes
    
    This is intentionally narrow and includes a TODO to remove the
    compatibility path once Xcode 26.4 ages out.
  • Terminate stdio MCP servers on shutdown to avoid process leaks (#19753)
    ## Why
    
    Several bug reports describe thread shutdown (including subagent
    threads) leaving stdio MCP server processes behind. These reports all
    point at the same lifecycle gap: Codex launches stdio MCP servers, but
    the session-level shutdown path does not explicitly close MCP clients or
    terminate the server process tree.
    
    Fixes #12491
    Fixes #12976
    Fixes #18881
    Fixes #19469
    
    ## History
    
    This is best understood as a regression/coverage gap in MCP session
    lifecycle management, not as stdio MCP cleanup being absent all along.
    #10710 added process-group cleanup for stdio MCP servers, but that
    cleanup only runs when the `RmcpClient`/transport is dropped. The older
    reports (#12491 and #12976) came after that cleanup existed, which
    suggests the remaining problem was that some higher-level shutdown paths
    kept the MCP manager alive or replaced it without explicitly draining
    clients. The newer reports (#18881 and #19469) exposed the same family
    around manager replacement and shutdown.
    
    ## What changed
    
    - Added an explicit stdio MCP process handle in `codex-rmcp-client` so
    local MCP servers terminate their process group and executor-backed MCP
    servers call the executor process terminator.
    - Added `RmcpClient::shutdown()` and manager-level MCP shutdown draining
    so session shutdown, channel-close fallback, MCP refresh, and connector
    probing stop owned MCP clients.
    - Added regression coverage that starts a stdio MCP server, begins an
    in-flight blocking tool call, shuts down the client, and asserts the
    server process exits.
    
    ## Verification
    
    - `cargo test -p codex-rmcp-client`
    - `cargo test -p codex-mcp`
    - `just fix -p codex-rmcp-client`
    - `just fix -p codex-mcp`
    - `just fix -p codex-core`
    
    - Manual before/after validation with a temporary repro script:
    - Pre-fix binary from `HEAD^` (`fed0a8f4fa`): reproduced the leak with
    surviving MCP server and child PIDs, `survivors=[77583, 77592]`,
    `leaked=true`.
    - Post-fix binary from this branch (`67e318148b`): verified both MCP
    processes were gone after interrupting `codex exec`, `survivors=[]`,
    `leaked=false`.
  • Split MCP connection modules (#19725)
    ## Why
    
    The MCP connection manager module had grown to mix orchestration, RMCP
    client startup, elicitation handling, Codex Apps cache and naming
    behavior, tool qualification and filtering, and runtime data. The
    previous stacked PRs split these responsibilities incrementally; this PR
    collapses that work into one self-contained refactor on latest main.
    
    ## What changed
    
    - Move McpConnectionManager into connection_manager.rs.
    - Move RMCP client lifecycle, startup, and uncached tool listing into
    rmcp_client.rs.
    - Move elicitation request tracking and policy handling into
    elicitation.rs.
    - Move Codex Apps cache, key, filtering, and naming helpers into
    codex_apps.rs.
    - Rename the tool-name helper module to tools.rs and move ToolInfo, tool
    filtering, schema masking, and qualification there.
    - Move runtime and sandbox shared types into runtime.rs.
    - Preserve latest main PermissionProfile-based MCP elicitation
    auto-approval behavior.
    
    ## Verification
    
    - just fmt
    - cargo check -p codex-mcp
    - cargo check -p codex-mcp --tests
    - cargo check -p codex-core
    
    ---------
    
    Co-authored-by: Codex <noreply@openai.com>