Commit Graph
99 Commits
Author SHA1 Message Date
jif e19e65317a Reuse MCP connections across runtime refreshes (#34952)
## Why

Refreshing MCP runtime state should not restart an unchanged, ready server or
relist its tools.

## What changed

- Reconcile refreshed MCP configuration against the published connection set
  and reuse connections whose transport, environment, authentication, OAuth
  credentials, and client capabilities are unchanged.
- Keep tool filters, timeouts, metadata, and plugin provenance in the published
  server view so those settings can change without reconnecting.
- Update elicitation authority in place for reused connections, and reconnect
  when connection-defining inputs or live OAuth credentials change.

## Testing

Added coverage for unchanged-server reuse, view-only and elicitation-policy
updates, connection identity changes, OAuth credential changes, and avoiding
redundant tool listing.

GitOrigin-RevId: f04f5db5fbdde127b6a14f6aa9673112c0b557e7
2026-07-23 14:55:31 +00:00
jif e497325a6a Centralize thread MCP state in McpRuntime (#34930)
## What changed

- Make `McpRuntime` own the published MCP configuration, connections, elicitation routing, and selected capability roots for a thread.
- Capture immutable MCP bindings for model steps and tool calls so in-flight work keeps a consistent connection set and approval authority while refreshed state is published atomically.
- Mark MCP state dirty when relevant configuration, plugins, environments, authentication, or elicitation capabilities change, then rebuild it before the next sampling request or out-of-band MCP operation.
- Separate config reloads from server invalidation: `ReloadMcpConfig` applies resolved MCP inputs, while `RefreshMcpServers` requests reinitialization from the thread's latest state.

## Testing

- Cover refreshed state visibility for existing turns, stable step bindings, current approval authority, cancelled refresh retries, resource-client reconciliation, and Apps recovery between sampling requests.

GitOrigin-RevId: 59eabb1aa8dc083426bd18ef4d3630508f376401
2026-07-23 11:27:34 +00:00
jif 516f1e2aff Rename the MCP connection manager to McpConnectionSet (#34708)
## What changed

- Use `McpConnectionSet` as the primary name for the collection of active MCP
  server connections throughout `codex-mcp` and its callers.
- Keep `McpConnectionManager` as a backward-compatible type alias.

GitOrigin-RevId: 9753838b4fd0a0b6429a9c4af0c077137616f388
2026-07-22 09:25:22 +00:00
stevenlee-oai 19940967bd Support threadless MCP connections without event channels (#34408)
## What changed

- Make the MCP connection manager's event sender optional.
- Skip startup notifications when a caller has no session event stream.
- Decline interactive MCP elicitations in that mode while allowing the
  underlying operation to continue.
- Update threadless resource reads, status snapshots, and connector discovery
  to use the channel-free path.

## Testing

- Extend the app-server MCP resource test to verify that a threadless resource
  read declines an elicitation and still returns the resource contents.

GitOrigin-RevId: 65fea152adbc02a78e09a40cae4134b6dccffd34
2026-07-20 20:43:36 +00:00
jif 42c5d3c80d Reuse MCP tool catalogs across sessions (#33184)
## Why

Starting a new session should not have to wait for an unchanged stdio MCP
server to initialize before its tools can be presented to the model.

## What changed

- Cache recent tool catalogs for matching stdio MCP server configurations and
  use them while a new live connection starts in the background.
- Bound the process-wide cache to 32 entries with a 30-minute TTL, and key
  entries by the server configuration, environment, working directory, and
  elicitation capabilities that affect the catalog.
- Keep connection-specific instructions and tool annotations out of cached
  catalogs. Resolve tool calls against the live connection so tools that are
  unavailable or hidden in the new session cannot be invoked from stale data.
- Publish only the newest completed catalog fetch and do not cache HTTP servers
  or stdio configurations that depend on remotely sourced environment values.

## Testing

- Add coverage for cache expiration, environment changes, catalog sanitization,
  and out-of-order refreshes.
- Add an end-to-end test that starts inference from cached definitions while a
  replacement MCP process initializes, then verifies calls and visibility
  against the live process.

GitOrigin-RevId: 2ed0f29ba53ace12532730f951b175c588aa3ae7
2026-07-14 22:25:39 +00:00
jif 2e156cbe31 Avoid blocking thread startup on MCP OAuth discovery (#32825)
## Why

OAuth metadata discovery for an optional HTTP MCP server can stall, delaying
`thread/start` even though that server is not required.

## What changed

- Remove eager MCP authentication-status discovery from session and connection
  manager startup.
- When an MCP connection reports an authentication error, inspect configured and
  stored credentials locally to preserve login and reauthentication failure
  reasons without making another discovery request.
- Continue using server configuration directly for startup error messages and
  timeout hints.

## Testing

Add an app-server regression test with an optional HTTP MCP endpoint that accepts
a connection but never responds, and verify that `thread/start` completes without
waiting for its configured startup timeout.

GitOrigin-RevId: 56fe0188cc1ceaf746b0b65e8b7dd1fd2e13420d
2026-07-13 15:47:03 +00:00
Matthew Zeng 2f7d89b141 Extract connector runtime snapshot management (#32698)
## What changed

- Move the Codex Apps tool cache into a reusable `codex-connectors` runtime manager keyed by account and workspace.
- Represent cached tools as atomically published snapshots with refresh timestamps while preserving the newest accepted fetch generation.
- Harden disk persistence with bounded reads, atomic file replacement, and serialized writes so an older fetch cannot overwrite newer state.

## Testing

Add coverage for identity isolation, snapshot timestamps, oversized cache files, atomic replacement, and concurrent persistence ordering.

GitOrigin-RevId: 5ea2234469daae3abf54b030244c3251de62ca5a
2026-07-13 05:02:52 +00:00
stevenlee-oai 555aa79d5a [connectors] Refresh codex_apps /ps/mcp auth (#31486)
[Codex Thread
019f2408-dc59-79f2-b245-4c11debd1a61](https://codex-thread-link.openai.chatgpt-team.site/thread/019f2408-dc59-79f2-b245-4c11debd1a61)

## Why

Long-lived Codex sessions can outlive the ChatGPT bearer token that was
present when the MCP runtime started.

The Responses path already recovers from token expiration by refreshing
or reloading the shared `AuthManager`. The reserved `codex_apps`
hosted-plugin client did not observe that update: `McpConnectionManager`
built its `/ps/mcp` HTTP auth once from a `CodexAuth` snapshot, and
`auth_provider_from_auth` copied that snapshot bearer into a static
`BearerAuthProvider`.

After the copied bearer expired, `/ps/mcp` kept sending it even though
Responses had a newer token in the same `AuthManager`. The failure
occurred before downstream connector execution, so unrelated apps such
as Gmail, Slack, and Google Calendar could all fail with the same
transport-level `401 token_expired`.

This replaces
[openai/codex#29474](https://github.com/openai/codex/pull/29474), which
was closed for inactivity without being merged. A new long-lived-session
report reproduced the same simultaneous `/ps/mcp` expiry pattern across
unrelated apps.

## What changed

- Add an `AuthManager`-backed request-header provider in
`codex-model-provider`. It keeps an `Arc<AuthManager>` and reads
`auth_cached()` for each outbound request, so the next `/ps/mcp` call
sees a token refreshed by the existing Responses/auth-recovery flow.
- Scope that provider to the startup account, ChatGPT user, and
workspace identity. Same-identity token reloads are followed; an account
switch emits no ambient auth until account-scoped MCP state is rebuilt.
- Have `McpConnectionManager` construct the dynamic provider only for
the reserved `codex_apps` registration used by the hosted-plugin
`/ps/mcp` path.

| MCP path | Auth behavior after this change |
| --- | --- |
| Reserved `codex_apps` hosted-plugin `/ps/mcp` | Read current
same-identity auth from the shared `AuthManager` per request |
| `codex_apps` with `CODEX_CONNECTORS_TOKEN` | Keep the environment
bearer-token override |
| User-configured/direct MCP registrations | Keep their existing
configured auth path |

## Non-goals

- No plugin-service changes.
- No downstream Slack, Gmail, Calendar, or other connector
OAuth/link-refresh changes.
- No auth UI changes.
- No behavior change for user-configured/direct MCP registrations.
- No new `/ps/mcp`-initiated token refresh; this makes `/ps/mcp` observe
refreshes already performed through the shared `AuthManager`.

## Tests

- `just test -p codex-model-provider`
- Covers same-identity token reloads and refuses a changed startup
identity.
- `just test -p codex-mcp`
- `just test -p codex-core mcp_auth_refresh`
- Creates the reserved hosted-plugin `codex_apps` `/ps/mcp` client
before the shared `AuthManager` changes, updates that same manager
through its public external-auth path, performs a real `tools/call`, and
asserts the request uses the current bearer.
2026-07-08 21:46:11 -04:00
sayan-oai 3c2bfe7c5d Make Apps guidance react to MCP availability (#30226)
## Why

Generic Apps guidance is emitted only while building static initial
context. If the Apps MCP is unavailable then and recovers later in the
same turn, its tools can become usable without the model receiving the
guidance for using them.

## What

- move generic Apps guidance into a persisted `apps_instructions` World
State section
- derive availability from the request's MCP runtime while preserving
the existing feature, auth, orchestrator-MCP, and config gates
- recognize legacy and retained Apps fragments so resume and compaction
do not duplicate guidance
- register Apps guidance as rollback-trimmable context
- remove the old static injection path and its now-unused connector
helper
- keep tool construction on its existing independent `list_all_tools()`
read rather than adding a request-wide cache; a reconnect between the
two reads can differ for one request and reconciles on the next request

Apps and plugin guidance now render after the remaining static
host-skills block, with Apps before Plugins.

## Testing

- `just fmt`
- `just test -p codex-core apps_instructions`
- `just test -p codex-core
apps_guidance_appears_after_background_recovery_within_a_turn`
- `just test -p codex-core
drop_last_n_user_turns_trims_context_updates_above_rolled_back_turn`
2026-07-06 14:29:19 -07:00
Channing Conger 84fe70c30e elicitations: Move to shared ElicitationService (#30627)
## Why

Code-mode tool results could return to the model while an MCP
elicitation was still waiting for user input. This differed from
parallel tool calling and could let the model continue before the user
resolved the request.

We need one session-level view of outstanding elicitations so tool
runtimes can consistently hold results until every pending elicitation
is resolved.

  ## What changed

- Added a counted, session-owned ElicitationService with RAII
registrations.
- Registered both core-originated and server-originated MCP elicitations
with the service.
- Migrated out-of-band elicitation tracking and unified exec timeout
pausing to the shared service.
- Made code-mode functions.exec and functions.wait capture their runtime
result normally, then hold it before returning while an elicitation is
outstanding.
  - Kept terminate: true immediate; only its result is held.
  - Preserved model-visible wall time across the elicitation hold.
- Kept the behavior session-scoped, with concurrent elicitations holding
the pause until all registrations are released.
2026-07-06 11:20:32 -07:00
jif fb8598df3f Keep MCP elicitation routable across runtime refreshes (#30127)
## Why

An MCP tool call can still be waiting for an elicitation response when
an environment update replaces the thread's MCP runtime.

Before this change:

```text
runtime A starts a tool call and asks the user
environment becomes ready, so runtime B is published
client answers the prompt through runtime B
runtime B cannot find runtime A's pending responder
```

The response is lost and the original tool call stays blocked.

## What changed

All MCP runtimes for one thread now share a small elicitation router:

```text
runtime A ---\
               shared router: response token -> exact pending responder
runtime B ---/
```

When Codex surfaces an MCP elicitation, it assigns a unique opaque
response token. The router records which pending request owns that
token. A replacement runtime reuses the same router, so the latest
runtime can deliver a response to a request started by the previous
runtime.

The Codex-owned token also prevents two runtime connections that reuse
the same MCP server request ID from receiving each other's responses.

This does not retain or search old MCP managers. Only the pending
responder map is shared.

## Covered scenario

The integration test exercises the complete failure mode:

1. A thread starts while its selected environment is still unavailable.
2. A configured MCP server starts a tool call and asks the client for
input.
3. The environment becomes ready, causing Codex to publish a replacement
MCP runtime.
4. The client answers the original prompt after the replacement.
5. The original tool call receives that answer and completes.

A focused routing test also creates two runtimes with the same server
request ID and verifies that each response reaches the exact request
that emitted its token.

## Scope

This PR changes only elicitation response routing across MCP runtime
replacement. It does not change when runtimes are rebuilt, which
environments contribute MCP configuration, or how environment
availability is detected.
2026-06-26 01:28:14 +00:00
Owen Lin 703793c22e feat(core, mcp): cache codex_apps tools in memory (#29003)
## Description

This makes Codex Apps tool reads use a shared in-memory snapshot instead
of rereading the disk cache every time `list_all_tools()` runs. Disk
still seeds the cache on startup and gets updated after successful
fetches, but it is no longer the live read path.

The core change is that `McpManager` now owns a process-scoped
`CodexAppsToolsCache`. Codex threads in the same app-server process now
share this Codex Apps in-memory tools snapshot. The snapshot is keyed by
the Codex home plus the Codex Apps identity: the active Codex auth
user/workspace and the effective Codex Apps MCP source config.

There's already code to hard-refresh the cache, so we respect it in this
PR.

## Local benchmark

I ran a local steady-state microbenchmark of the exact repeated Codex
Apps cached-tools read this PR removes, using the same real local cache
payload in both trees: `3,678,138` bytes and `381` tools. The cache file
was already warm in the OS page cache, so this measures same-process
reread/deserialization work rather than cold-disk latency or full turn
latency. Each run is 25 iterations (mimicking a turn that makes 25
inference calls).

| Version | Run 1 | Run 2 | Avg |
|---|---:|---:|---:|
| `origin/main` disk read + JSON deserialize + `filter_tools` | `50.755
ms` | `52.894 ms` | `51.825 ms` |
| This branch in-memory `current_tools` + `filter_tools` | `0.740 ms` |
`0.778 ms` | `0.759 ms` |

That removes about `51 ms` from each repeated Codex Apps cached-tools
read on this machine, roughly `68x` faster for that subpath. It is
useful evidence for the hot path this PR changes, but not a claim that
every production turn gets `51 ms` faster; end-to-end impact also
depends on the rest of `list_all_tools()` and tool-payload construction.

This is on my M2 Max macbook, so with a slower disk this would be much
worse (and indeed we did see this really blew up turn runtime with a
slow disk).
2026-06-25 20:54:48 +00:00
jif b215961a56 Support OAuth for HTTP MCP servers from selected executor plugins (#28529)
## Why

#28522 routes selected-plugin HTTP MCP traffic through the owning
executor, but OAuth bootstrap and refresh still used host-local clients.
Executor-only servers therefore cannot complete discovery or login
through the same network boundary as the MCP connection.

## What changed

- adapt `codex_exec_server::HttpClient` to RMCP 1.8's `OAuthHttpClient`
contract
- let RMCP own discovery, dynamic registration, PKCE, token exchange,
and refresh
- route auth status, persisted-token startup, and app-server login
through the server runtime while preserving the existing local discovery
path
- add optional `threadId` to `mcpServer/oauth/login` and echo it in the
completion notification
- implement RMCP's redirect policy and 1 MiB OAuth response limit over
executor HTTP
- cover selected-thread OAuth discovery and login through an
executor-only route

Depends on #28522.
2026-06-25 10:31:17 +01:00
Adam Perry @ OpenAI e639e8c4bd connectors: own app metadata types (#29723)
## Why

Connector metadata is consumed by connector discovery, ChatGPT
integration, core, and TUI code. Treating app-server's wire DTO as the
shared domain model reverses the intended dependency direction.

## What changed

- Added connector-owned app branding, review, screenshot, metadata, and
info types.
- Added explicit conversions in app-server and TUI while preserving
app-server's wire payloads.
- Removed production app-server-protocol dependencies from connectors
and ChatGPT connector code.

## Stack

This is PR 4 of 6, stacked on [PR
#29722](https://github.com/openai/codex/pull/29722). Review only the
delta from `codex/split-config-layer-types`. Next: [PR
#29724](https://github.com/openai/codex/pull/29724).

## Validation

- Connector and tools coverage passed.
- App-server app-list coverage passed: 13 tests.
2026-06-23 22:08:23 -07:00
Ahmed Ibrahim a22e3d0b82 Remove redundant Codex Apps manager flag (#29518)
## Why

Codex Apps server admission is already decided before
`McpConnectionManager` is constructed. `effective_mcp_servers` and
`effective_mcp_servers_from_configured` remove the server when the apps
feature or required authentication is unavailable, so storing the same
decision on the manager duplicates state that can drift from the
effective server map.

## What changed

- Remove `host_owned_codex_apps_enabled` from `McpConnectionManager` and
its constructor.
- Identify the host-owned Codex Apps server by its reserved server name
once it is present in the effective server map.
- Remove the now-unused flag calculations and constructor arguments from
production and test callsites.
2026-06-22 23:19:42 -07:00
Alex Daley 4af7762f01 [connectors] Ignore synthetic links for app accessibility (#28770)
Summary
- Stop treating Codex Apps MCP tools with
`_meta._codex_apps.synthetic_link: true` as evidence that a connector is
accessible in `app/list`.
- Preserve synthetic tools in the agent-facing MCP connector set so they
remain available for install/auth flows.
- Keep the app-list accessibility cache limited to connectors backed by
at least one non-synthetic tool.
- Add focused regression coverage for both sides of the boundary.

Validation
- `just fmt`
- `just test -p codex-core
synthetic_links_are_exposed_to_the_agent_but_not_accessible_in_app_list`
- `git diff --check`
- A crate-wide `just test -p codex-core` run completed with 2,699
passing and 51 unrelated local sandbox/state failures, primarily state
DB migration races (`UNIQUE constraint failed:
_sqlx_migrations.version`).
2026-06-18 17:19:24 -04:00
Eric Ning 29eb434bc5 [codex] Remove hardcoded app ID filters (#28947)
## Summary

- remove the duplicated originator-specific connector ID denylists
- stop filtering connector directory/accessibility results and
live/cached Codex Apps MCP tools by hardcoded connector ID
- remove the now-unused `codex-login` dependency from
`codex-utils-plugins`
- update regression coverage so formerly blocked connector IDs are
preserved

## Why

The client-side policy was duplicated across crates, used opaque IDs
without ownership or expiry information, and could drift between app
listing and MCP tool behavior. Server-provided visibility,
authorization, plugin discoverability, accessibility, enabled-state
handling, and consequential-tool approval templates remain unchanged.

## Validation

- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `git diff --check`
- confirmed the final diff contains no hardcoded denylist symbols

A targeted `codex-mcp` test build spent an unusually long time in local
compilation/linking. Its first attempt exposed a test-only `PartialEq`
assertion issue, which was corrected. A follow-up non-linking `cargo
check -p codex-mcp --tests` was still running when this draft was
opened; CI should provide the complete Rust validation.
2026-06-18 20:29:01 +00:00
Gabriel Peal 21a599fa56 Support openai/form extended form elicitations (#27500)
# Summary
Allow App Server clients to opt into `openai/form` MCP elicitations.
2026-06-18 11:54:49 -07:00
Owen Lin 38211a3ff8 [codex] trace tools build latency (#28782)
Add more tracing spans around tool building.
2026-06-17 14:53:54 -07:00
Matthew Zeng 1fbaac1e50 [codex] Reuse Apps policy evaluation across MCP tool exposure (#27813)
## Summary

- move `AppToolPolicyEvaluator` and the Apps config/requirements policy
logic from `codex-core` into `codex-connectors`
- resolve one immutable policy snapshot per exposure build and reuse it
across every Codex Apps MCP tool
- keep core as a thin adapter from MCP metadata to connector-owned
policy input while preserving the call-time defense-in-depth check

## Why

`build_mcp_tool_exposure` evaluates every Codex Apps tool on each
sampling request. The old path rebuilt effective Apps configuration for
every tool, and the policy implementation lived in the already-large
core crate even though it is connector-specific.

The connector-owned evaluator keeps the expensive config merge/decode
out of the loop and gives core only the effective policy result it
needs.

## Performance

With the real 557-tool Apps corpus, `build_mcp_tool_exposure` measured
3.74 ms and 3.33 ms after the extraction (3.54 ms mean). The original
path measured 807 ms mean, so the final result retains the 99.6%
reduction.

## Validation

- `cargo check -p codex-connectors -p codex-core`
- `just test -p codex-connectors` — 15 passed
- `just test -p codex-core --lib connectors` — 35 passed
- `just test -p codex-core --lib mcp_tool_exposure` — 5 passed
- `just test -p codex-core --lib mcp_tool_call` — 72 passed
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just fix -p codex-connectors`
- `just fix -p codex-core`
- `just fmt`
2026-06-15 15:24:33 -07:00
rphilizaire-openai bacfc5e4c0 [codex] add latency tracing spans (#27710)
## Why

We have some large gaps in our thread start, resume, and pre-sampling
traces that make it hard to tell where latency is coming from.

## What Changed

- Added coarse spans around thread start/resume, turn context
construction, rollout reconstruction, skill/plugin loading, and tool
preparation.
- Added a breakdown of discoverable-tool preparation across connector
loading, plugin discovery, and local plugin details.

## Testing

- `cargo check -p codex-app-server -p codex-core -p codex-core-skills -p
codex-core-plugins`
- Built the app-server locally and exercised thread start, first turn,
follow-up turn, server restart, thread resume, and a resumed turn.
2026-06-12 17:11:32 -07:00
Celia Chen 9915d34684 feat: use encrypted local secrets for MCP OAuth (#27541)
## Summary

- store MCP OAuth credentials in the configured auth credential backend
- support encrypted-local OAuth storage, including legacy keyring
migration
- propagate the credential backend through MCP refresh, session, CLI,
and app-server paths

## Stack

1. #27504 — config and feature flag
2. #27535 — auth-specific secret namespaces
3. #27539 — encrypted CLI auth storage
4. this PR — encrypted MCP OAuth storage

This is a parallel review stack; the original #17931 remains unchanged.

## Tests

- `just test -p codex-rmcp-client` (the transport round-trip test passed
after building the required `codex` binary and retrying)
- `just test -p codex-mcp`
- `just test -p codex-app-server
refresh_config_uses_latest_auth_keyring_backend`
- `just test -p codex-core
refresh_mcp_servers_is_deferred_until_next_turn`
- `just test -p codex-cli mcp`
- `just fix -p codex-rmcp-client -p codex-mcp -p codex-core -p codex-cli
-p codex-app-server -p codex-protocol`
- `just bazel-lock-check`
2026-06-12 22:03:51 +00:00
Alex Zamoshchin 3cac2e0d3f [ez][codex-rs] Support approvals reviewer in app defaults (#27075)
[from codex]

## Summary

- add `approvals_reviewer` support to `[apps._default]`
- resolve connected-app reviewers in per-app, app-default, then global
order
- expose the setting through the v2 config API and regenerate schema
fixtures

## Context

PR #25167 added `apps.<connector_id>.approvals_reviewer`, but the shared
app defaults table could not specify the reviewer. This extends the same
behavior to `[apps._default]` while preserving per-app overrides.

Managed `allowed_approvals_reviewers` requirements still constrain both
default and per-app values. A disallowed app value falls back to the
global reviewer, and non-app MCP servers continue using the global
reviewer.

## Testing

- `just write-config-schema`
- `just write-app-server-schema`
- `just fmt`
- `just test -p codex-config`
- `just test -p codex-core app_approvals_reviewer`
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server config_read_includes_apps`
2026-06-12 09:06:58 -07:00
jif 4a5a676499 Resolve MCP server registrations through a catalog (#27634)
## Why

MCP servers currently come from user config, local plugins,
compatibility Apps synthesis, and host extensions. Those sources were
composed by mutating a shared map, leaving registration identity,
precedence, removal, and provenance implicit in assembly order.

Before adding executor-owned MCPs, Codex needs one durable resolution
boundary above `McpConnectionManager`. This PR introduces that boundary
while preserving current server configuration, policy, and runtime
behavior. Executor-scoped registrations and explicit policy layers
remain follow-ups.

## What changed

- Add typed `McpServerRegistration` inputs and an immutable
`ResolvedMcpCatalog` in `codex-mcp`.
- Retain each registration's complete `McpServerConfig`, including its
environment binding, while recording its source and provenance.
- Preserve the existing structural precedence between plugin, config,
compatibility, and ordered extension sources.
- Resolve equal-precedence actions by contribution order; provenance IDs
are used only for diagnostics and cannot affect the winner.
- Preserve extension removals and the existing name-scoped `enabled =
false` veto.
- Report same-tier conflicts with every contender and the final catalog
outcome, including whether the winning action registers or removes the
server.
- Require MCP contributors to provide a stable diagnostic identity.
- Derive materialized server maps and plugin ownership from the resolved
catalog.

`McpConnectionManager`, transport startup, tool calls, and resource
routing continue to consume the same effective `McpServerConfig` values.

## Scope

This PR does not add new MCP capabilities or change user-visible
behavior. It does not add executor plugin discovery, thread-scoped
registrations, dynamic refresh generations, or new user/managed policy
semantics.

## Verification

- Added focused catalog coverage for source precedence, complete
configuration preservation, disabled vetoes, plugin ownership,
contribution-order tie breaking, removal outcomes, and conflict
diagnostics.
- Extended hosted Apps coverage for ordered extension removal and
Apps-disabled hosts with and without the hosted extension installed.
- `cargo check -p codex-mcp --tests -p codex-extension-api -p
codex-core`
2026-06-11 21:54:52 +02:00
Charlie Marsh 41b4fabbb4 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.
2026-06-10 08:33:21 -07:00
Ahmed Ibrahim a7b6baecc6 [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
2026-06-10 00:17:58 -07:00
jif 4ec3b8eeea Route hosted Apps MCP through extensions (#27191)
## Stack

- Base: #27184
- This PR is the second vertical and should be reviewed against
`jif/external-plugins-1`, not `main`.

## Why

CCA is moving toward a split runtime where the orchestrator may have no
filesystem or executor, but it still needs to activate remotely hosted
plugin components. HTTP MCP servers are the simplest complete example:
they need configuration and host authentication, but they do not need an
executor process.

The Apps MCP endpoint is currently synthesized by a special-purpose
loader inside the MCP runtime. That works locally, but it leaves hosted
MCP activation outside the extension model being established in #27184.
It also makes the Apps path a poor foundation for plugins whose skills,
MCP servers, connectors, and hooks may come from different sources or
execute in different places.

This PR moves that one behavior behind an extension-owned contribution
while preserving the existing local fallback. It deliberately does not
introduce a generic plugin activation framework.

## What changed

### MCP extension contribution

`codex-extension-api` gains an ordered `McpServerContributor` contract.
A contributor returns typed `Set` or `Remove` overlays for MCP server
configuration; later contributors win for the names they own.

The contract stays at the existing MCP configuration boundary.
Extensions do not create a second connection manager or transport
abstraction.

### Hosted Apps MCP extension

A new `codex-mcp-extension` contributes the reserved `codex_apps` server
from the existing Apps feature, ChatGPT base URL, path override, and
product SKU configuration.

When `apps_mcp_path_override` is enabled for `https://chatgpt.com`, the
resulting streamable HTTP endpoint is
`https://chatgpt.com/backend-api/ps/mcp`. The existing ChatGPT-auth gate
remains authoritative, so this server can run in an orchestrator-only
process without being exposed for API-key sessions.

### One resolved runtime view

`McpManager` now distinguishes three views:

- **configured:** config- and plugin-backed servers before extension
overlays;
- **runtime:** configured servers plus host-installed extension
contributions;
- **effective:** runtime servers after auth gating and compatibility
built-ins.

App-server installs the hosted MCP extension and uses the runtime view
for thread startup, refresh, status, threadless resource reads,
connector discovery, and MCP OAuth lookup. This keeps
`mcpServer/oauth/login` consistent with the servers exposed by the other
MCP APIs. The hosted Apps server itself continues to use existing
ChatGPT host authentication rather than MCP OAuth.

## Compatibility

Hosts that do not install the MCP extension retain the existing Apps MCP
synthesis path. This preserves current local-only, CLI, and
standalone-host behavior while app-server exercises the extension path.

Disabling Apps removes the reserved `codex_apps` entry, and losing
ChatGPT auth removes it from the effective runtime view. Executor
availability is not consulted for this HTTP transport.

## Follow-ups

The next vertical will resolve a manifest-declared stdio MCP server from
an executor-selected plugin root and execute it in the environment that
owns that root. Later verticals can add backend-owned skills, connector
metadata, hooks, durable selection semantics, and incremental local
convergence without changing the component-specific runtime boundaries
introduced here.

## Verification

Focused coverage was added for:

- contributing the hosted Apps MCP at `/backend-api/ps/mcp` without an
executor;
- requiring ChatGPT auth in the effective runtime view;
- removing a reserved configured Apps server when the Apps feature is
disabled.

`cargo check -p codex-app-server -p codex-mcp-extension -p
codex-extension-api -p codex-mcp` passed. Tests and Clippy were not run
locally under the current development instruction; CI provides the full
validation pass.
2026-06-09 22:44:16 +02:00
Alex Zamoshchinandjif-oai 4d80d808b4 [app-server][core] Add connector-level Guardian reviewer overrides (#25167)
Context: https://openai.slack.com/archives/C0B4JAF0Q2C/p1779912328647229

```
approvals_reviewer = "auto_review"

[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
enabled = true
approvals_reviewer = "user"
default_tools_approval_mode = "prompt"
```

<img width="230" height="84" alt="Screenshot 2026-05-31 at 11 56 34 AM"
src="https://github.com/user-attachments/assets/e319f8f7-0983-42a7-98cd-3302732fa406"
/>

<img width="841" height="233" alt="Screenshot 2026-05-31 at 11 52 42 AM"
src="https://github.com/user-attachments/assets/7ac76645-4e90-4d00-8242-f031146a22a5"
/>

-------

```
approvals_reviewer = "user"

[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa]
enabled = true
approvals_reviewer = "auto_review"
default_tools_approval_mode = "prompt"
```
<img width="195" height="83" alt="Screenshot 2026-05-31 at 12 02 27 PM"
src="https://github.com/user-attachments/assets/3d374dc8-8aa2-466f-a13f-e4ed8567aa2e"
/>
<img width="771" height="207" alt="Screenshot 2026-05-31 at 12 05 42 PM"
src="https://github.com/user-attachments/assets/105c2575-68d6-4ca6-8e69-dc8c82da36a2"
/>



## Summary
- add `apps.<connector_id>.approvals_reviewer` to override Guardian or
user review routing per connected app
- apply overrides across direct app MCP calls, delegated MCP prompts,
and app-server MCP elicitation review while preserving global behavior
for non-app MCP servers
- expose and document the config through app-server v2 and generated
schemas, while honoring global managed reviewer requirements

---------

Co-authored-by: jif-oai <jif@openai.com>
2026-06-02 17:04:11 +02:00
xl-openai f2b725102b [codex] Cache remote plugin catalog for suggestions (#25457)
## Summary
- cache the global remote plugin catalog when remote plugin listing runs
and warm it during startup
- use the cached remote catalog in plugin install recommendations with
canonical `plugin@openai-curated-remote` ids
- reuse the session `PluginsManager` for plugin recommendations so
remote cache state is visible on the recommend path
- skip core installed-state verification for remote plugin install
suggestions while leaving local plugin and connector verification
unchanged

## Testing
- `just fmt`
- `git diff --check`
- `cargo test -p codex-core
list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
- `cargo test -p codex-core
remote_plugin_install_suggestions_skip_core_installed_verification`
- `cargo test -p codex-app-server
plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled`

Earlier focused checks during the same branch: codex-tools TUI filter
test, request_plugin_install tests, and codex-app-server build.
2026-06-01 22:10:52 -07:00
Eric Ning e929bb5c88 [codex] Update remote connector suggestions (#25172)
## Summary

- Use the session-loaded plugin app IDs as the source of connector
suggestion candidates.
- Remove the redundant plugin reload from
`tool_suggest_connector_ids()`.
- Add regression coverage for connectors declared by a loaded remote
plugin, using the Databricks app case.

## Context

Loaded remote plugins can declare app connector IDs in `.app.json`. The
session-owned `PluginsManager` already loads those plugins and exposes
their effective app IDs.

The connector suggestion path was creating a separate `PluginsManager`
and recomputing plugin app IDs. That new manager does not share the
session manager’s remote installed plugin cache, so app IDs from loaded
remote plugins were missing from connector suggestions.

## Fix

Pass the already-loaded effective app IDs into connector suggestion
generation and use them directly as the plugin-derived connector
candidate set.

Connector candidates are now built from:

- App IDs declared by loaded plugins
- Explicitly configured connector discoverables
- Existing disabled-suggestion filtering

This avoids a second plugin-manager lookup and keeps connector
suggestions aligned with the plugins actually loaded for the turn.

## Behavior

For example, when a plugin is loaded and its `.app.json` declares data
apps, `list_available_plugins_to_install` can now return those data
connectors.

This does not create plugin suggestions from the plugin itself. Plugin
suggestions still come from eligible uninstalled entries in the
marketplace catalog and require existing matching/filtering rules.

## Validation

- `just fmt`
- Added regression coverage for a loaded-plugin connector ID appearing
in discoverable tools
- Attempted `just test -p codex-core`; the command exited unsuccessfully
in the local test environment without useful failure detail captured in
the run output
2026-05-29 17:57:34 -07:00
Noah MacCallum 8e5f561697 Filter plugin install suggestions by installed apps (#24996)
## Summary

- Keep the original `TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST` as a
fallback seed list, so users with no installed plugins still get initial
install suggestions.
- Allow additional install suggestions from trusted marketplaces:
`openai-curated` and `openai-bundled`.
- Require non-fallback, non-configured marketplace candidates to share
`.app.json` connector IDs with already installed plugins.
- Preserve explicit configured plugin discoverables as an override,
while still omitting installed, disabled, and `NOT_AVAILABLE` plugins.

## Context

`list_available_plugins_to_install` controls which plugins the model can
trigger via `request_plugin_install`. We want a small starter set for
empty/new users, but we also want installed workflow plugins to unlock
relevant source plugins without maintaining every source plugin ID by
hand.

This keeps the legacy plugin ID allowlist only as the starter fallback.
For everything else, the trusted marketplace is the candidate boundary,
and installed app connector overlap is the relevance filter. For
example, an installed Sales plugin can make HubSpot and Granola
suggestible when those source plugins are in `openai-curated` and share
Sales app connector IDs, while an unrelated test-source plugin with an
app connector not declared by Sales stays hidden.

## Test Coverage

- Empty/no-installed-plugin case: returns the fallback seed plugins from
the original allowlist.
- Installed-app expansion: returns non-fallback marketplace plugins only
when their app connector IDs overlap with an installed plugin.
- Sales workflow case: installed Sales declares HubSpot and Granola
apps, so `hubspot@openai-curated` and `granola@openai-curated` are
returned.
- Sales negative case: `test-source@openai-curated` has an app connector
not declared by Sales, so it is not returned.
- Existing guardrails: installed plugins, disabled suggestions, and
`NOT_AVAILABLE` plugins remain omitted; explicit configured
discoverables still work as an override.

## Validation

- `just fmt`
- `just test -p codex-core plugins::discoverable::tests`
- `just test -p codex-core` was attempted earlier, but current `main` /
local env failed with unrelated existing failures around missing
`test_stdio_server`, CLI/code-mode MCP tool setup, and
unified_exec/shell snapshot flakes/timeouts. The touched discoverable
tests pass.
2026-05-29 15:32:04 -07:00
pakrym-oai ff7513cd83 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`
2026-05-26 08:21:15 -07:00
starr-openai 298e5cfce1 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`
2026-05-21 17:19:54 +02:00
starr-openai 5c43a64e2b Make local environment optional in EnvironmentManager (#23369)
## Summary
- make `EnvironmentManager` local environment/runtime paths optional
- simplify constructor surface around snapshot materialization
- rename local env accessors to `require_local_environment` /
`try_local_environment`

## Validation
- devbox Bazel build for touched crate surfaces
- `//codex-rs/exec-server:exec-server-unit-tests`
- `//codex-rs/app-server-client:app-server-client-unit-tests`
- filtered touched `//codex-rs/core:core-unit-tests` cases
2026-05-19 12:55:34 -07:00
Matthew Zeng 192481d1a1 [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)*
2026-05-11 12:23:55 -07:00
viyatb-oai d0fa2d81d8 feat(connectors): support managed app tool approval requirements (#21061)
## Why

Managed requirements can already centrally disable apps, but they could
not express the per-tool app approval rules that normal config already
supports. That left admins without a way to enforce connector tool
approvals through `/etc/codex/requirements.toml` or cloud requirements.

## What changed

- Extend app requirements with per-tool `approval_mode` entries.
- Merge managed app tool requirements across managed sources while
preserving higher-precedence exact tool settings.
- Apply managed tool approvals separately from user app config so
managed policy is matched only on raw MCP `tool.name`, while user config
keeps the existing raw-name-then-title convenience fallback.
- Add coverage for local requirements, cloud requirements parsing,
managed-over-user precedence, and a title-collision case that must not
widen managed auto-approval.

## Configuration shape

Local `/etc/codex/requirements.toml` and cloud requirements use the same
TOML shape:

```toml
[apps.connector_123123.tools."calendar/list_events"]
approval_mode = "approve"
```

This is a per-tool approval rule keyed by app ID and raw MCP tool name,
not an app-level boolean such as `apps.connector_123123.approve = true`.
2026-05-11 19:08:26 +00:00
Matthew Zeng 2f3a2d7a86 Using cached connector directory for discoverable tools list (#21497)
## Summary

Startup tool construction currently depends on connector directory
metadata for `tool_suggest` discoverables. On a cold directory cache,
that can put slow connector-directory requests on the blocking path even
though the tools array only needs directory data for install
suggestions, not for the live connector MCP tools themselves.

This PR keeps the discoverables path off that cold network fetch:
- read connector directory metadata from cache only when building
discoverable tools
- persist connector directory metadata to
`~/.codex/cache/codex_app_directory/<hash>.json` and use it to hydrate
the in-memory cache on later runs before the normal refresh path updates
it
- use connector-directory-specific cache naming to distinguish this
metadata cache from the separate Codex Apps tools-spec cache

This reduces first-turn startup work without changing how live connector
MCP tools are sourced. Longer term, directory-backed install suggestions
should move to a search-based flow so they no longer need to be inlined
into the tools prompt at all.

## Testing

- `cargo test -p codex-connectors`
- `cargo test -p codex-chatgpt`
- `cargo test -p codex-core
request_plugin_install_is_available_without_search_tool_after_discovery_attempts`
- `cargo test -p codex-core
tool_suggest_uses_connector_id_fallback_when_directory_cache_is_empty`
2026-05-08 14:14:11 -07:00
starr-openaiandCodex 5f2543b74e Load configured environments from CODEX_HOME (#20667)
## Why

The earlier PRs add stdio transport support and the config-backed
environment provider, but the feature remains inert until normal Codex
entrypoints construct `EnvironmentManager` with enough context to
discover `CODEX_HOME/environments.toml`. This final stack PR activates
the provider while preserving the legacy `CODEX_EXEC_SERVER_URL`
fallback when no environments file exists.

**Stack position:** this is PR 5 of 5. It is the product wiring PR that
activates the configured environment provider added in PR 4.

## What Changed

- Thread `codex_home` into `EnvironmentManagerArgs`.
- Change `EnvironmentManager::new(...)` to load the provider from
`CODEX_HOME`.
- Preserve legacy behavior by falling back to
`DefaultEnvironmentProvider::from_env()` when `environments.toml` is
absent.
- Make `environments.toml`-backed managers start new threads with all
configured environments, default first, while keeping the legacy env-var
path single-default.
- Update the app-server, TUI, exec, MCP server, connector, prompt-debug,
and thread-manager-sample callsites to pass `codex_home` and handle
provider-loading errors.

## Self-Review Notes

- The multi-environment startup path is intentionally tied to the
`environments.toml` provider. Using `>1` configured environment as the
only signal would also expand the legacy `CODEX_EXEC_SERVER_URL`
provider because it keeps `local` addressable alongside `remote`.
- The startup environment list is still derived inside
`EnvironmentManager`; the provider only says whether its snapshot should
start new threads with all configured environments.
- The thread-manager sample was updated to pass the current
`ThreadManager::new(...)` installation id argument so the stack compiles
under Bazel.

## Stack

- 1. https://github.com/openai/codex/pull/20663 - Add stdio exec-server
listener
- 2. https://github.com/openai/codex/pull/20664 - Add stdio exec-server
client transport
- 3. https://github.com/openai/codex/pull/20665 - Make environment
providers own default selection
- 4. https://github.com/openai/codex/pull/20666 - Add CODEX_HOME
environments TOML provider
- **5. This PR:** https://github.com/openai/codex/pull/20667 - Load
configured environments from CODEX_HOME

Split from original draft: https://github.com/openai/codex/pull/20508

## Validation

- `just fmt`
- `git diff --check`
- `bazel build --config=remote --strategy=remote
--remote_download_toplevel
//codex-rs/thread-manager-sample:codex-thread-manager-sample`
- `bazel test --config=remote --strategy=remote
--remote_download_toplevel
//codex-rs/exec-server:exec-server-unit-tests`
- `bazel test --config=remote --strategy=remote
--remote_download_toplevel --test_sharding_strategy=disabled
--test_arg=default_thread_environment_selections_use_manager_default_id
//codex-rs/core:core-unit-tests`
- `bazel test --config=remote --strategy=remote
--remote_download_toplevel --test_sharding_strategy=disabled
--test_arg=start_thread_uses_all_default_environments_from_codex_home
//codex-rs/core:core-unit-tests`

## Documentation

This activates `CODEX_HOME/environments.toml`; user-facing documentation
should be added before this stack is treated as a documented public
workflow.

---------

Co-authored-by: Codex <noreply@openai.com>
2026-05-08 11:17:56 -07:00
pakrym-oai 857e731478 [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`
2026-05-07 10:16:10 -07:00
jif-oaiandCodex b2268999fe 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>
2026-05-07 10:36:32 +02:00
Clark DuVall 346070a424 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
2026-05-06 19:42:45 +00:00
Matthew Zeng f9a907aebe 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.
2026-05-06 07:18:00 +00:00
sayan-oai b9e8df47da Use MCP server instructions in deferred namespace descriptions (#21053)
## Why

MCP servers can provide `instructions` that explain what their tools are
for. Directly exposed MCP namespaces already use those instructions when
a connector description is not available, but deferred `tool_search`
results did not preserve that fallback. The direct path falls back from
connector metadata to server instructions, while the deferred path only
carried `connector_description` and otherwise fell back to generic
namespace text.

That meant a plain MCP server could provide useful model-facing guidance
and still appear as `Tools in the X namespace.` whenever it was
discovered lazily through `tool_search`.

## What changed

- Store one model-facing `namespace_description` on `ToolInfo`, using
connector descriptions for connector-backed tools and server
instructions for plain MCP servers.
- Thread that namespace description through the `tool_search` source
list, search indexing, and returned namespace metadata.
- Add an end-to-end regression test for deferred non-app MCP search
results exposing server instructions as the namespace description.

## Verification

- `cargo test -p codex-tools
search_tool_description_lists_each_mcp_source_once --lib`
- `cargo test -p codex-core --test all
tool_search_uses_non_app_mcp_server_instructions_as_namespace_description`
2026-05-04 19:36:07 +00:00
xl-openai 7b3de63041 Move plugin out of core. (#20348) 2026-04-30 14:26:14 -07:00
starr-openaiandCodex e1ec9e63a0 Add environment provider snapshot (#20058)
## Summary
- Change `EnvironmentProvider` to return concrete `Environment`
instances instead of `EnvironmentConfigurations`.
- Make `DefaultEnvironmentProvider` provide the provider-visible `local`
environment plus optional `remote` environment from
`CODEX_EXEC_SERVER_URL`.
- Keep `EnvironmentManager` as the concrete cache while exposing its own
explicit local environment for `local_environment()` fallback paths.

## Validation
- `just fmt`
- `git diff --check`

---------

Co-authored-by: Codex <noreply@openai.com>
2026-04-28 20:05:18 -07:00
Matthew Zeng ebdf3a878c Support disabling tool suggest for specific tools. (#20072)
## Summary
- Add `disable_tool_suggest` to app and plugin config, schema, and
TypeScript output
- Exclude disabled connectors and plugins from tool suggestion discovery
- Persist "never show again" tool-suggestion choices back into
`config.toml`
- Update config docs and add coverage for connector and plugin
suppression

## Testing
- Added and updated unit tests for config persistence and tool-suggest
filtering
- Not run (not requested)
2026-04-29 00:19:34 +00:00
Eric Traut 4e0cf945b7 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`.
2026-04-28 09:29:57 -07:00
efrazer-oai c08177f7d0 refactor: load agent identity runtime eagerly (#19763)
## Summary

AgentIdentity auth previously registered the process task lazily behind
a `OnceCell`. That meant the auth object could be constructed before its
runtime task binding was known.

This PR makes AgentIdentity auth load the runtime task at auth load time
and stores the resulting process task id directly on the auth object.
The model-provider call path can then read a concrete task id instead of
handling a missing lazy value.

## Stack

1. [refactor: make auth loading
async](https://github.com/openai/codex/pull/19762) (merged)
2. **This PR:** [refactor: load AgentIdentity runtime
eagerly](https://github.com/openai/codex/pull/19763)
3. [fix: configure AgentIdentity AuthAPI base
URL](https://github.com/openai/codex/pull/19904)
4. [feat: verify AgentIdentity JWTs with
JWKS](https://github.com/openai/codex/pull/19764)

## Important call sites

| Area | Change |
| --- | --- |
| `AgentIdentityAuth::load` | Registers the process task during auth
loading and stores `process_task_id`. |
| `CodexAuth::from_agent_identity_jwt` | Awaits AgentIdentity auth
loading. |
| model-provider auth | Reads a concrete `process_task_id` instead of an
optional lazy value. |
| AgentIdentity auth tests | Mock task registration now covers eager
runtime allocation. |

## Design decisions

AgentIdentity auth now treats task registration as part of constructing
a usable auth object. That matches how callers use the value: once auth
is present, the model-provider path expects the task-scoped assertion
data to be ready.

## Testing

Tests: targeted Rust auth test compilation, formatter, scoped Clippy
fix, and Bazel lock check.
2026-04-27 21:09:26 -07:00
efrazer-oai 2009f6e894 refactor: make auth loading async (#19762)
## Summary

Auth loading used to expose synchronous construction helpers in several
places even though some auth sources now need async work. This PR makes
the auth-loading surface async and updates the callers to await it.

This is intentionally only plumbing. It does not change how
AgentIdentity tokens are decoded, how task runtime ids are allocated, or
how JWT signatures are verified.

## Stack

1. **This PR:** [refactor: make auth loading
async](https://github.com/openai/codex/pull/19762)
2. [refactor: load AgentIdentity runtime
eagerly](https://github.com/openai/codex/pull/19763)
3. [feat: verify AgentIdentity JWTs with
JWKS](https://github.com/openai/codex/pull/19764)

## Important call sites

| Area | Change |
| --- | --- |
| `codex-login` auth loading | `CodexAuth` and `AuthManager`
construction paths now await auth loading. |
| app-server startup | Auth manager construction is awaited during
initialization. |
| CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now
await the same behavior. |
| cloud requirements storage loader | The loader becomes async so it can
share the same auth construction path. |
| auth tests | Tests that load auth now run in async contexts. |

## Testing

Tests: targeted Rust auth test compilation, formatter, scoped Clippy
fix, and Bazel lock check.
2026-04-27 11:00:27 -07:00
pakrym-oai 9c3abcd46c [codex] Move config loading into codex-config (#19487)
## Why

Config loading had become split across crates: `codex-config` owned the
config types and merge logic, while `codex-core` still owned the loader
that assembled the layer stack. This change consolidates that
responsibility in `codex-config`, so the crate that defines config
behavior also owns how configs are discovered and loaded.

To make that move possible without reintroducing the old dependency
cycle, the shell-environment policy types and helpers that
`codex-exec-server` needs now live in `codex-protocol` instead of
flowing through `codex-config`.

This also makes the migrated loader tests more deterministic on machines
that already have managed or system Codex config installed by letting
tests override the system config and requirements paths instead of
reading the host's `/etc/codex`.

## What Changed

- moved the config loader implementation from `codex-core` into
`codex-config::loader` and deleted the old `core::config_loader` module
instead of leaving a compatibility shim
- moved shell-environment policy types and helpers into
`codex-protocol`, then updated `codex-exec-server` and other downstream
crates to import them from their new home
- updated downstream callers to use loader/config APIs from
`codex-config`
- added test-only loader overrides for system config and requirements
paths so loader-focused tests do not depend on host-managed config state
- cleaned up now-unused dependency entries and platform-specific cfgs
that were surfaced by post-push CI

## Testing

- `cargo test -p codex-config`
- `cargo test -p codex-core config_loader_tests::`
- `cargo test -p codex-protocol -p codex-exec-server -p
codex-cloud-requirements -p codex-rmcp-client --lib`
- `cargo test --lib -p codex-app-server-client -p codex-exec`
- `cargo test --no-run --lib -p codex-app-server`
- `cargo test -p codex-linux-sandbox --lib`
- `cargo shear`
- `just bazel-lock-check`

## Notes

- I did not chase unrelated full-suite failures outside the migrated
loader surface.
- `cargo test -p codex-core --lib` still hits unrelated proxy-sensitive
failures on this machine, and Windows CI still shows unrelated
long-running/timeouting test noise outside the loader migration itself.
2026-04-26 15:10:53 -07:00