Commit Graph
150 Commits
Author SHA1 Message Date
Evan Fannin 41014b11bd Add MCP event discovery and subscriptions (#37494)
## What changed

- Expose hosted Plugin Runtime event definitions through `McpResourceClient::list_events`.
- Add cancellable `events/stream` subscriptions that route lifecycle notifications to the matching request and cancel when the stream is dropped.
- Bound event notification and queue sizes, time out stalled response headers, and close streamable HTTP responses locally on cancellation.

## Testing

- Add streamable HTTP integration coverage for isolated concurrent subscriptions, local cancellation, and stalled response-header timeouts.

GitOrigin-RevId: 98c6fffa8f004eb2638c1b0705f659222cbbdfdb
2026-08-07 19:36:52 +00:00
Cam moten 9daa491f7c Harden local MCP server process tree cleanup (#37366)
## Why

A local stdio MCP server can exit while its descendants remain alive. Cleaning up
only the server process can therefore leak processes after client shutdown.

## What changed

- Contain locally launched MCP servers in non-breakaway Windows job objects and
  terminate the job during shutdown so descendants exit with the server.
- Preserve compatibility when job creation or assignment is unavailable by
  retrying without containment and terminating an owned process handle instead.
- Use the process-group member fallback when terminating piped processes on
  macOS.

## Testing

- Cover descendant cleanup and breakaway prevention for both local MCP protocol
  modes on Windows.
- Cover the Windows nested-job assignment fallback and verify the suspended
  process resumes before handle-based termination.

GitOrigin-RevId: 33d3cc122de8f7ce0ae69a649b12ea0ff64b7c6f
2026-08-07 03:34:49 +00:00
jif b3ffe3d001 Recover MCP servers after OAuth reauthentication (#37337)
## Why

An OAuth-backed Streamable HTTP MCP server can fail startup when its stored credentials are rejected. If a client then completes OAuth sign-in and replaces those credentials, the failed server needs to become available without restarting the Codex session.

## What changed

- Track the credential store and OAuth tokens used for each MCP connection, and detect replacements after authentication failures.
- Refresh affected MCP servers before the next turn and include their recovered tools in the model request.
- Classify HTTP authentication challenges and rejected refresh tokens as requiring reauthentication, with sign-in guidance appropriate to local and client-managed environments.

## Testing

- Cover recovery from expired credentials through an externally written replacement token.
- Cover authentication-error classification, startup messaging, and `Auto` store migration from file credentials to the keyring.

GitOrigin-RevId: 9b090089d4e7a44ffc182f4e25da20a18e97b70f
2026-08-07 00:17:46 +00:00
jif 74b8f8db93 Cover remote MCP discovery timeout cleanup (#37248)
## What changed

Extend the executor-backed Streamable HTTP timeout test to cover both legacy
`initialize` and MCP 2026 `server/discover` requests. Verify that each stalled
handshake times out and releases the serial executor for subsequent requests.

GitOrigin-RevId: 3493904433c95265b9f645813723e890b7610abb
2026-08-06 09:46:04 +00:00
jif e244a9d94e Bound remote MCP handshake HTTP requests (#37168)
## Why

A streamable HTTP MCP handshake could time out while its executor-backed HTTP
request continued running, leaving the serial executor blocked for later
requests.

## What changed

- Track the remaining initialization deadline for streamable HTTP transports.
- Apply it to `initialize`, `notifications/initialized`, and discovery HTTP
  requests, then clear it when the handshake finishes.

## Testing

- Added a regression test that stalls the remote MCP endpoint, waits for the
  handshake timeout, and verifies that a subsequent executor request completes.

GitOrigin-RevId: d4980a332e898309c885cbaa4fdd598924b440e1
2026-08-05 21:52:09 +00:00
Cam moten f2d825533c Fall back to per-process MCP cleanup on macOS (#37068)
## Why

Signaling an MCP server's process group can fail with `EPERM` on macOS,
preventing owned descendants from being terminated.

## What changed

- When a group signal is denied, enumerate the group's processes and signal
  members that still belong to the expected process group.
- Apply the fallback to both `SIGTERM` and `SIGKILL` during MCP stdio server
  cleanup, while retaining the existing group signaling behavior elsewhere.
- Reject invalid process group IDs and avoid signaling processes that have
  moved to another group.

## Testing

Added macOS tests covering cleanup with a live or exited group leader,
`SIGTERM`-resistant descendants, escalation to `SIGKILL`, and unsafe group IDs.

GitOrigin-RevId: db722475b94dece74244ce8109b3e92f6cfb202a
2026-08-05 09:43:26 +00:00
jacobzhou-oai 56b82e676c Enforce Agent Plugin runtime boundaries (#37027)
## What changed

- Track Agent Plugin manifests through plugin, skill, and MCP loading so their capabilities use format-specific behavior without changing legacy plugins.
- Discover only direct-child skills, exclude app and hook capabilities, isolate MCP data, and reject MCP configuration files that are non-regular or resolve outside the plugin root.
- Bound model-visible skill instructions, plugin instructions, MCP descriptions, schemas, individual tools, and the aggregate Agent Plugin MCP tool set.
- Stop MCP and OAuth redirects when Agent Plugins send configured or authorization headers, while retaining existing redirect behavior for legacy MCP servers.

## Testing

- Add coverage for capability filtering, skill discovery, isolated MCP data and reserved-path expansion, unsafe MCP configuration files, context limits, and redirect handling.

GitOrigin-RevId: c9af66b051269f3226628ca280a58d32c808c38f
2026-08-05 04:54:33 +00:00
jacobzhou-oai bd12b3a9ec Add Agent Plugins MCP config parsing (#36796)
## What changed

- Add `parse_agent_plugin_mcp_config` to translate Agent Plugins v1 `mcp.json` files into Codex MCP server configuration.
- Normalize `stdio` and streamable HTTP transports, including `PLUGIN_ROOT` and `PLUGIN_DATA` expansion, contained plugin paths, secure endpoint validation, and filtering of client-owned HTTP headers.
- Keep valid sibling servers when another server is invalid, while returning per-server parse errors.
- Preserve UTF-8 HTTP header values when forwarding streamable HTTP protocol headers.

## Testing

- Cover transport mapping, placeholder expansion, path containment, schema and field validation, per-server errors, platform-specific environment handling, and UTF-8 headers.

GitOrigin-RevId: ed4ab0fcf495afbb381ce48beb93989629444c56
2026-08-03 20:36:59 +00:00
jif 164b3bfeab Isolate MCP OAuth credentials by environment (#36310)
## Why

MCP servers running in an executor must not reuse host-owned OAuth credentials,
even when they have the same configured server name and URL.

## What changed

- Derive environment-scoped credential names for executor-owned MCP servers and
  use them consistently for login, logout, authentication, and connection setup.
- Mark executor-owned entries in the file credential store and fail closed when
  loading or saving an entry could cross the host/executor boundary.
- Preserve compatibility with existing local OAuth credentials, including local
  server names that overlap the new reserved prefixes.

## Testing

- Extend the executor MCP integration test to verify that executor requests use
  the executor token, never send the host token, and persist both credentials
  separately.

GitOrigin-RevId: 4fc92d0533b0fe2e0df34f6d47b81e3d20d07807
2026-07-31 11:23:46 +00:00
jif 7ec480dda5 Distinguish unknown MCP authentication status (#36045)
## Why

OAuth discovery failures do not establish that an MCP server lacks OAuth
support. Reporting those failures as `unsupported` conflates an inconclusive
check with a confirmed result.

## What changed

- Add an `unknown` MCP authentication status across the protocol, app server,
  CLI, and TUI.
- Preserve OAuth discovery errors so callers can report `unknown`, while
  retaining `unsupported` for servers known not to support OAuth.
- Document the distinction in the app server API.

## Testing

- Verify transient HTTP discovery errors are preserved.
- Verify `codex mcp list --json` reports `unknown` when discovery is rate
  limited.

GitOrigin-RevId: e4562985971606740538e542ec7eeee502111964
2026-07-29 23:24:43 +00:00
jif 3e3ae08839 Limit MCP catalog pagination (#36039)
## Why

MCP catalog discovery must not allow a server to keep pagination running or
growing without bounds.

## What changed

- Apply the shared pagination collector to tool, resource, and resource-template
  discovery.
- Limit each catalog to 100 pages and 1,024 items, and reject pagination cursors
  larger than 64 KiB or any repeated cursor.
- Bound the entire pagination operation by the configured tool timeout, falling
  back to 30 seconds when no timeout is configured.

## Testing

Add unit coverage for every limit and an end-to-end MCP test that preserves
valid multi-page tools while excluding a server that returns an oversized
cursor.

GitOrigin-RevId: 961a1bfa5fe8a265daf56e342a919c980cd720fa
2026-07-29 22:37:09 +00:00
thomas a05bcda3db Upgrade rmcp to 3.0.0 (#36001)
## What changed

- Update the Rust MCP SDK from `3.0.0-beta.3` to `3.0.0` and adapt to its renamed metadata and server discovery types.
- Accept discovery responses without server identity metadata, using the configured server name as a fallback, and rely on the SDK's native support for namespaced server identity metadata.
- Preserve typed OAuth HTTP errors so transport failures, cross-origin redirects, and transient HTTP responses are reported instead of being treated as anonymous access.

## Testing

- Cover modern discovery with namespaced or missing server identity over HTTP, SSE, and stdio.
- Cover OAuth discovery error propagation for transport failures, redirects, and transient status codes.

GitOrigin-RevId: 12c1e45136cca89ce4fb15986c2b5df14608682a
2026-07-29 17:38:20 +00:00
jif ddf33ea802 Bound MCP namespace descriptions (#35941)
## What changed

- Limit model-facing MCP namespace descriptions to 1,000 bytes without splitting UTF-8 characters or mutating the stored server metadata.
- Cap the aggregate source-description list in `tool_search` at 4 KiB while retaining source names and discovery instructions.
- Cover direct and deferred MCP tool exposure, including multibyte descriptions and tool visibility.

GitOrigin-RevId: 92736ec39475a1d6b3b03f5e3063e28225d8a5f6
2026-07-29 11:49:47 +00:00
thomas 9f4c20aadc Handle legacy MCP discovery prevalidation errors (#35840)
## Why

Some legacy MCP servers reject `server/discover` before creating a session and
return a null-ID JSON-RPC error with a non-JSON content type or without echoing
the rejected protocol version. These responses prevented the client from
falling back to legacy initialization.

## What changed

- Parse HTTP 400 discovery errors without a session ID regardless of their
  declared content type.
- Fall back to `initialize` when error code `-32000` reports a missing session
  or advertises only known legacy protocol versions, including supported
  versions other than `2025-06-18`.
- Continue rejecting unrelated error codes, correlated responses, non-400
  statuses, malformed version lists, and lists containing modern or unknown
  versions.

## Testing

Added MCP discovery coverage for legacy error variants, server-selected legacy
protocol versions, non-JSON content types, and non-legacy rejection cases.

GitOrigin-RevId: 4d3ee2d7d852ce0d0ab3f486da23cb653a7ff5ca
2026-07-28 22:57:09 +00:00
Celia Chen 709283b432 Use configured HTTP clients for all MCP OAuth requests (#35814)
## What changed

- Require callers to provide an HTTP client for MCP OAuth discovery and login, removing the separate direct `reqwest` path.
- Use the shared `http` and `url` types throughout the MCP client and drop its direct `reqwest` dependency.
- Preserve configured MCP headers when routing OAuth discovery through the provided client.

## Testing

- Add coverage that routed OAuth discovery forwards configured headers.

GitOrigin-RevId: 1345f56bd794626533133447597a5908e57b9b48
2026-07-28 19:11:36 +00:00
Celia Chen 9ea975a2dc Route MCP OAuth through configured HTTP clients (#35806)
## What changed

- Pass resolved, route-aware HTTP clients through MCP OAuth discovery and login so CLI commands, plugin installation, and skill dependency setup honor configured proxies and execution environments.
- Apply per-plugin MCP server configuration and requirements before starting OAuth during installation, and skip disabled servers or servers assigned to unowned environments.
- Preserve configured MCP server policies when merging remotely installed plugin metadata.

## Testing

- Cover proxy-routed OAuth for `codex mcp add`, `codex mcp login`, plugin installation, and skill MCP dependencies.
- Cover plugin-install OAuth filtering for disabled servers, plugin requirements, and unowned environments.

GitOrigin-RevId: f84c88820e24a627faa78d6bed1b371682ecdc2f
2026-07-28 18:25:29 +00:00
thomas f2bee854a7 Complete MCP 2026 client support (#35725)
## What changed

- Drive multi-round `tools/call` and `resources/read` requests through
  `input_required` responses, preserving opaque request state and elicitation
  metadata across JSON, SSE, and stdio transports.
- Decode modern discovery and elicitation result shapes while retaining legacy
  initialization, elicitation defaults, and discovery fallback behavior.
- Apply the 8 MiB modern-protocol response limit to JSON, SSE events, and stdio
  messages, and reject executor stream recovery when output sequence gaps would
  corrupt the protocol stream.

## Testing

Added integration coverage for discovery, multi-round requests, legacy
fallbacks, message limits, stdio launchers, SSE responses, session recovery,
and executor output recovery.

GitOrigin-RevId: d26a08883a2825e8ce2408ac498114b33859225e
2026-07-28 06:06:58 +00:00
thomas be2e4afcd7 Add MCP 2026-07-28 discovery support (#35724)
## What changed

- Add an opt-in `mcp_2026_07_28` protocol mode while preserving the legacy
  lifecycle by default.
- Negotiate the new protocol over streamable HTTP with `server/discover`,
  including bounded responses, redirect protection, and fallback only when a
  response establishes that the endpoint is legacy-only.
- Require stdio servers to opt in with
  `CODEX_MCP_PROTOCOL_VERSION=2026-07-28`, and add a bounded local stdio
  transport for the modern lifecycle.
- Consume paginated tool, resource, and resource-template catalogs in modern
  mode, reject repeated cursors, and retain discovered server identity.
- Reconnect reusable MCP clients when their selected protocol mode changes.

## Testing

- Cover HTTP JSON and SSE discovery, legacy fallback and rejection cases,
  redirects, retries, response limits, and pagination.
- Cover local and executor stdio discovery, protocol markers, message limits,
  and legacy compatibility.

GitOrigin-RevId: f6a78816e127d2a482292d63b91c8384f1595903
2026-07-28 05:52:12 +00:00
thomas 61de0d8fe8 Upgrade rmcp to 3.0.0-beta.3 (#35720)
## What changed

- Update the `rmcp` model and transport integrations for `3.0.0-beta.3`.
- Preserve legacy `elicitation/create` form requests, schema defaults, metadata, and wire-format compatibility while adopting the new elicitation types.
- Harden OAuth discovery by using GET-first discovery without starting an MCP session, preventing cross-origin header redirects, validating authorization-server and callback issuers, and retaining compatibility with metadata that omits an issuer.
- Keep stored OAuth credentials readable and avoid requiring reauthorization after transient refresh failures.

## Testing

- Add coverage for legacy elicitation round trips, OAuth discovery variants, callback issuer validation, credential migration, and refresh failures.

GitOrigin-RevId: 756197d26f3fd347c28c400228f6b3d06ed493b7
2026-07-28 05:17:25 +00:00
Celia Chen 89a3b89c4c Route MCP auth discovery through runtime HTTP clients (#35239)
## Why

MCP authentication checks need to use the same HTTP routing as the MCP
transport so servers reached through configured proxies can be discovered
reliably.

## What changed

- Resolve OAuth discovery and authentication status through each server's
  runtime HTTP client for both local and managed environments.
- Keep local discovery capped at five seconds while allowing explicit login
  requests to retain their requested timeout.
- Resolve refreshed MCP configuration and its runtime context from the same
  snapshot.

## Testing

- Cover OAuth discovery through an environment proxy and macOS system proxy
  resolution.
- Cover proxied MCP startup and runtime refresh with updated authorization
  headers.
- Verify capped and preserved OAuth discovery timeout policies.

GitOrigin-RevId: 461fb1d4786e547df8b1e6b2215a8ac40438a3aa
2026-07-24 20:04:23 +00:00
Celia Chen 09241ae4db Decouple exec-server HTTP from reqwest types (#35059)
## What changed

- Rename `ReqwestHttpClient` to `RouteAwareHttpClient` to reflect that delegated HTTP uses Codex's shared route-aware transport.
- Use `codex_http_client` response and error types plus transport-neutral `http` and `url` types, removing the exec server's direct `reqwest` dependency.

## Testing

- Cover fragment stripping and Unicode hostname normalization for delegated HTTP requests.

GitOrigin-RevId: 8b0fc60a76004feb57198bfb4afb1371c9ceb1bd
2026-07-24 00:53:05 +00:00
Celia Chen 94ebae725e Route exec-server WebSockets through configured proxies (#35056)
## Why

Remote environment connections need to honor Codex's effective outbound proxy policy, including when a rendezvous connection reconnects.

## What changed

- Pass the configured `HttpClientFactory` into remote environment transports and use `WebSocketConnector` for exec-server and rendezvous WebSockets.
- Resolve proxy routes asynchronously so these connections can use the configured system proxy.
- Add connector options that preserve Tungstenite's default TLS behavior and enable `TCP_NODELAY` for latency-sensitive rendezvous traffic.

## Testing

- Verify prepared remote environments connect through a configured system proxy.
- Verify initial and reconnected encrypted relay peers use the system proxy.
- Cover default TLS selection and opt-in `TCP_NODELAY` behavior in the WebSocket client.

GitOrigin-RevId: 8a8da2116e37cb3a891269d0c0b037986fecdd3c
2026-07-24 00:16:42 +00:00
Celia Chen 1ee8f49175 Route exec-server HTTP through configured proxy policy (#35023)
## Why

Delegated HTTP requests need to honor the same outbound proxy policy as the
Codex process that starts the exec server.

## What changed

- Pass the configured `HttpClientFactory` through local and remote exec-server
  startup and use route-aware client pools for delegated HTTP and local MCP
  requests.
- Preserve per-request timeouts and follow-or-stop redirect behavior while
  keeping request URLs and sensitive response headers out of diagnostics.

## Testing

- Cover configured system-proxy routing across the exec-server transport.
- Cover both redirect policies and verify that success and failure logs do not
  expose request or response secrets.

GitOrigin-RevId: 4af6aec1d265c4db62dfcb6e1fb076fb31736137
2026-07-23 22:39:28 +00:00
jif 34b935e3e5 Replace closed MCP connections during reconciliation (#34957)
## Why

MCP connection reconciliation can otherwise reuse a client after its service or
underlying transport has stopped.

## What changed

- Add `RmcpClient::is_closed` to report closed client, service, and transport
  states.
- Exclude closed clients when selecting a reusable MCP connection so
  reconciliation starts a replacement.

## Testing

- Add a reconciliation test that disconnects an in-process MCP transport and
  verifies that its connection is replaced.

GitOrigin-RevId: 366a85c9959dbe1763923d7e33a4ecf52d9c3c6a
2026-07-23 15:29:10 +00:00
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
Eric Traut 6e0455fdc4 Set a default user agent for MCP HTTP requests (#34883)
## What changed

- Send `codex-mcp-client/<version>` as the default user agent for streamable HTTP and OAuth requests.
- Preserve user agents supplied through configured HTTP headers.

## Testing

- Verify the default user agent on OAuth discovery, token refresh, and MCP initialization requests.
- Verify that a configured user agent overrides the default.

GitOrigin-RevId: 659ef8f126df97b3c1b4d01e9e542a673b5ef42b
2026-07-23 06:13:12 +00:00
viyatb-oai 9f6c29e281 Launch managed network proxies on remote executors (#33906)
## Why

Remote executions need managed-network proxy listeners in the executor so their
loopback proxy addresses are reachable by the launched process.

## What changed

- Add a capability-gated exec-server protocol field for executor-local proxy
  launch configuration, including network policy, audit metadata, and execution
  attribution.
- Start the proxy while preparing a remote process, replace inherited proxy
  environment variables with its local addresses, and derive the sandbox
  context from its listeners.
- Keep the proxy alive until inherited output streams close, then shut it down.
- Reject unsupported remote settings such as MITM and credential injection.

## Testing

- Cover configuration round trips and rejection of unsupported settings.
- Verify executor-local startup, blocked-domain enforcement, protocol
  compatibility, and proxy lifetime through process closure.

GitOrigin-RevId: c984f54e3e600aa9ebcbf8cf4574046e2c199d11
2026-07-17 21:20:14 +00:00
rka-oai cbc83d961e Preserve encrypted content in MCP tool outputs (#33509)
## What changed

- Convert MCP text content marked with `_meta["codex/encryptedContent"] = true`
  into `encrypted_content` function call output items.
- Prefer the content-item payload whenever encrypted content is present so that
  `structured_content` does not replace the encrypted output.

## Testing

- Add a stdio MCP integration test that round-trips mixed plaintext and
  encrypted tool output.

GitOrigin-RevId: c3997a16b06ad38ff22b21016ede35b97974d9ec
2026-07-16 05:38: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
Adam Perry @ OpenAI 44954d1b4b Serialize concurrent MCP stdin writes (#33180)
## What changed

- Guard executor-backed MCP stdio sends with a single-permit semaphore so a
  second JSON-RPC message cannot start writing while the first write is pending.
- Add a regression test that drives two concurrent sends and verifies the
  process receives each newline-delimited message in order without overlap.

GitOrigin-RevId: 30c2e51827ecf57b5eb23d9ad1e876734d6e784e
2026-07-14 22:16:52 +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
jif c7a4a7e136 Refactor OAuth store lock contention tests (#32801)
## What changed

- Extend the lock-contention test helper with a callback that runs while the
  aggregate store lock is held.
- Use a scoped worker thread and return its result directly after releasing the
  lock.
- Reuse the helper for concurrent update tests covering both file and secrets
  credential stores.

GitOrigin-RevId: 74b8b5370456e90ecd39fc2af2f0853594ca4f60
2026-07-13 13:56:34 +00:00
jif 8b2c84ddcc Apply MCP startup timeouts during client creation (#32781)
## What changed

- Apply each server's `startup_timeout_sec` (or the default) while creating the
  MCP client, so the deadline also covers transport setup.
- Launch local stdio servers on a blocking task so synchronous command
  resolution and process creation do not prevent the deadline from firing.
- Recognize the new client-startup timeout error and show the existing
  `startup_timeout_sec` configuration hint.

## Testing

- Extend the timeout error display test to cover the client-startup timeout.

GitOrigin-RevId: 1967c62f943d55f6aa18792d4488e52c22f1e717
2026-07-13 12:00:50 +00:00
stevenlee-oai 6962a2ecae Serialize MCP OAuth credential refreshes (#32229)
## Why

Concurrent Codex processes can otherwise refresh the same rotating token, and a
cancelled or partially persisted refresh can leave durable and in-memory MCP
credentials out of sync.

## What changed

- Serialize each credential's read-refresh-write transaction across processes,
  reread the authoritative store after locking, and adopt credentials refreshed
  by another process.
- Keep refresh persistence running after caller cancellation, bound lock and
  provider waits independently, and preserve omitted refresh tokens and scopes.
- Fail MCP startup and operations when refresh or persistence fails instead of
  continuing with stale credentials, while requiring reauthorization for
  missing, unusable, or rejected refresh tokens.
- Exclude OAuth refresh time from the MCP initialization timeout.

## Testing

Add coverage for lock contention, concurrent refreshes, rejected and missing
credentials, storage failures, caller cancellation, and provider timeouts.

GitOrigin-RevId: 4d29b879bec646d2ceb922b526dc793b1a1f5423
2026-07-10 17:59:41 +00:00
stevenlee-oai cbdee7976b Resolve and pin MCP OAuth credential stores (#30293)
[Codex Thread
019edd6d-6f14-74e2-853c-345d1803d4a6](https://codex-thread-link.openai.chatgpt-team.site/thread/019edd6d-6f14-74e2-853c-345d1803d4a6)

## Stack

Review and merge in order. Every layer is independently correct and
documents its safe stopping point.

1. [openai/codex#30292](https://github.com/openai/codex/pull/30292) —
aggregate File/Secrets store locking
2. [openai/codex#30293](https://github.com/openai/codex/pull/30293) —
resolve and lifecycle-pin the exact OAuth store
3. [openai/codex#30416](https://github.com/openai/codex/pull/30416) —
serialized authoritative refresh transaction
4. [openai/codex#30294](https://github.com/openai/codex/pull/30294) —
Codex-owned transport refresh and one-shot 401 recovery
5. [openai/codex#30295](https://github.com/openai/codex/pull/30295) —
login/logout transaction serialization
6. [openai/codex#30296](https://github.com/openai/codex/pull/30296) —
diagnostic-only Auto store drift reporting

**This PR is layer 2.**

## Why

`Auto` is keyring-first with a File fallback, but re-evaluating that
policy during transport reconstruction or persistence can make one MCP
client read from one store and later write to another. With rotating
refresh tokens, the second store may contain an older token. This layer
makes the source selected at client startup explicit and keeps that
authority stable for the client lifecycle.

## What this PR does

- Keeps `resolve_oauth_tokens_from_store_policy` as the single
configured-policy entry point and returns both credentials and the
concrete File or Keyring source that supplied them.
- Puts exact `load`, `save`, and `delete` operations on
`ResolvedOAuthCredentialStore`, making “resolve configured policy” and
“use the selected authority” distinct at call sites.
- Pins the first concrete source in `pinned_credential_store` in the
transport recipe, so initialization retries and session reconstruction
cannot re-evaluate `Auto` and adopt another store.
- Gives `OAuthPersistor` the resolved store and keeps subsequent
persistence and removal on that authority.
- Uses a typed keyring-load error to distinguish aggregate-store
coordination failures from ordinary backend failures; a coordination
failure is surfaced instead of triggering File fallback.
- Keeps login-time `Auto` behavior unchanged: prefer Keyring, fall back
to File when unavailable, and clean up legacy File state after a
successful keyring save.
- Adds structured server/backend context when fallback cleanup fails.

## Explicit decisions and non-goals

- The selection is lifecycle-local and in memory. This PR does not add a
durable backend selector, migration, reconciliation registry, or global
source of truth outside `CODEX_HOME`.
- `Auto` may choose File at the start of a later process if keyring
availability changes. Once this client resolves, a selected-store
failure is returned instead of hot-switching.
- Different `CODEX_HOME` instances remain independent even when they can
access the same Direct keyring credential.
- Cross-process refresh serialization is intentionally not part of this
layer.

## Safe stopping point

This PR can merge alone. A single MCP client no longer hot-switches
credential stores across transport rebuilds or persistence. Two
processes can still refresh the same selected credential concurrently
until layer 3.

## Review size

The net layer is 9 files, +668/−144. The production change remains
focused on store resolution and lifecycle pinning; the largest follow-up
is integration coverage that drives real session recovery.

## Validation

- `just test -p codex-rmcp-client` (99 passed; 5 expected skips)
- Real-client 404 recovery coverage with different Keyring and File
tokens; captured bearer headers prove the stale File token is never sent
- Mutation check: removing the lifecycle pin makes that integration
regression fail by observing the stale File token
2026-07-09 18:46:29 -04:00
jif 6ced1ac5eb Bound remote MCP stdio lines (#31805)
## Why

Remote MCP stdio buffers executor-provided stdout until a JSON-RPC line
completes and stderr until a diagnostic line completes. A malicious or
broken executor can keep sending chunks without a newline, making
orchestrator memory grow without bound.

Executor event boundaries are arbitrary, so a valid message before an
oversized line must not be lost merely because both arrived in one
event.

## What changed

- Limit one stdout JSON-RPC line to 8 MiB.
- Limit one stderr diagnostic line to 1 MiB.
- Check each logical line before copying its bytes into the retained
buffer.
- Preserve complete stdout messages that precede an oversized line and
deliver them before closing the stream.
- Close the MCP transport on overflow; existing transport cleanup
terminates the executor-managed process.
- Preserve multiple lines per chunk, CRLF framing, and final
unterminated messages at EOF.
- Box the pending stdio transport so the added framing state does not
inflate every `ClientState` value.
- Add regression coverage for cross-chunk overflow, bounded lines in a
larger chunk, and a valid prefix before an oversized line.

## Scope

This bounds pre-newline buffering for executor-backed MCP stdio only. It
does not change local stdio framing or address memory amplification
while parsing a complete JSON message.
2026-07-09 23:15:00 +01:00
jif 48cf582331 Round MCP timeout durations in error messages (#31612)
## Summary

MCP operation timeout errors currently print the full debug precision of
the remaining timeout budget. That makes a configured 30-second timeout
show up as something like `29.999999875s`.

This PR rounds the displayed duration to a whole unit, so the error is
clean and stable:

```text
timed out awaiting tools/list after 30s
```

The timeout behavior itself is unchanged; this only affects the
human-facing error text. A regression test covers the reported
`tools/list` case.
2026-07-08 18:58:02 +01:00
stevenlee-oai 6cf42cf165 Serialize shared MCP OAuth credential stores (#30292)
[Codex Thread
019edd6d-6f14-74e2-853c-345d1803d4a6](https://codex-thread-link.openai.chatgpt-team.site/thread/019edd6d-6f14-74e2-853c-345d1803d4a6)

## Stack

Review and merge in order. Every layer is independently correct and
documents its safe stopping point.

1. [openai/codex#30292](https://github.com/openai/codex/pull/30292) —
aggregate File/Secrets store locking
2. [openai/codex#30293](https://github.com/openai/codex/pull/30293) —
resolve and lifecycle-pin the exact OAuth store
3. [openai/codex#30416](https://github.com/openai/codex/pull/30416) —
serialized authoritative refresh transaction
4. [openai/codex#30294](https://github.com/openai/codex/pull/30294) —
Codex-owned transport refresh and one-shot 401 recovery
5. [openai/codex#30295](https://github.com/openai/codex/pull/30295) —
login/logout transaction serialization
6. [openai/codex#30296](https://github.com/openai/codex/pull/30296) —
diagnostic-only Auto store drift reporting

**This PR is layer 1.**

## Why

MCP OAuth credentials stored in File or Secrets share one aggregate map.
Concurrent read-modify-write operations for different MCP servers can
both read the same snapshot and let the later write discard the earlier
update. That is a correctness problem independent of refresh-token
rotation.

## What this PR does

- Adds a bounded cross-process lock around aggregate File and Secrets
loads, saves, and deletes.
- Distinguishes aggregate-lock failures from Secrets backend
unavailability, so Auto can fall back only for the latter and cannot
bypass serialization by reading or writing File.
- Keeps Direct keyring operations outside this lock because they are
already per credential.
- Releases the Secrets aggregate lock before legacy File cleanup so
cross-store cleanup cannot create nested aggregate-lock ordering.
- Tests actual contention by waiting for an observed `WouldBlock`,
rather than assuming a sleeping worker reached the lock.
- Tests load and save with only the Secrets lock path broken while
fallback File remains readable and writable.

## Decisions and non-goals

- This lock protects aggregate-store read-modify-write integrity only.
It does not choose a credential authority or serialize an OAuth refresh
transaction.
- The lock is scoped to the active `CODEX_HOME`, matching the aggregate
files it protects.
- Lock waits are bounded, and coordination failures are surfaced rather
than treated as evidence that Secrets is unavailable.

## Safe stopping point

This PR can merge alone. It prevents lost updates and partial aggregate
reads. Auto can still resolve again during a client lifecycle until
layer 2, and concurrent refreshes remain possible until layer 3.

## Validation

- `just test -p codex-rmcp-client` (96 passed; expected environment
skips)
- Focused aggregate File/Secrets lock contention and Auto fallback tests
2026-07-07 04:03:27 +00:00
felixxia-oai 526f495f3a [codex] Classify nested MCP authentication startup errors (#30257)
## Summary

- classify authentication-required RMCP startup failures, including
errors nested inside `ClientInitializeError::TransportError`
- let `codex-mcp` consume that classification so the existing
`reauthenticationRequired` startup failure reason is emitted
- add a regression test that performs real startup with an expired
persisted OAuth token and no refresh token

## Why

Follow-up to #29877.

RMCP stores streamable HTTP initialization failures inside a dynamic
transport error whose payload is not exposed through the standard Rust
error source chain. The original `anyhow::Error::chain()` check
therefore missed the nested `AuthError::AuthorizationRequired` seen
during real MCP startup and emitted `failureReason: null`.

The transport-specific inspection now lives in `codex-rmcp-client`,
while `codex-mcp` consumes only the domain-level authentication-required
result. This classifier does not distinguish first-time login from
reauthentication; the existing auth-state logic remains responsible for
that distinction.

## User impact

When stored MCP OAuth credentials are expired and cannot be refreshed,
app clients now receive `failureReason: "reauthenticationRequired"` on
the failed startup update and can show the reconnect action. First-time
login and unrelated startup failures remain unchanged.

## Validation

- `just test -p codex-rmcp-client --test streamable_http_oauth_startup
identifies_expired_unrefreshable_token_startup_error`
- `just test -p codex-mcp
startup_outcome_error_identifies_authentication_required`
- `just test -p codex-mcp
mcp_startup_failure_reason_requires_existing_oauth_and_auth_failure`
- `cargo build -p codex-cli --bin codex`
- local app-server probe emitted `failureReason:
"reauthenticationRequired"`
- manual end-to-end reconnect flow confirmed
- `just fmt`
2026-06-26 14:11:13 -07:00
felixxia-oai a6d20ed297 [codex] Surface MCP reauthentication-required startup failures (#29877)
## Summary

- distinguish expired, non-refreshable stored MCP OAuth credentials from
first-time missing credentials
- carry a typed `failureReason: "reauthenticationRequired"` on the
existing `mcpServer/startupStatus/updated` notification only when user
action is required
- keep the public MCP auth-status API unchanged and regenerate the
app-server protocol schemas and documentation

## Why

An MCP server with an expired access token and no usable refresh token
currently fails startup without giving clients a reliable, typed
recovery signal.

The existing startup-status notification is the natural place to carry
this state. Its nullable `failureReason` keeps the recovery reason
attached to the failed startup transition without adding a one-off
notification. Internally, Codex distinguishes first-time login from
reauthentication and emits the reason only when the startup error itself
requires authentication.

## User impact

App clients can prompt an existing user to reconnect an MCP server when
automatic recovery is impossible by handling a failed
`mcpServer/startupStatus/updated` notification whose `failureReason` is
`reauthenticationRequired`. Starting, ready, cancelled, unrelated
failures, and first-time setup carry no reauthentication reason.

## Companion app PR

- openai/openai#1069582

## Validation

- `just test -p codex-app-server-protocol` — 248 passed; schema fixture
tests passed
- `cargo check -p codex-app-server -p codex-tui`
- `just test -p codex-rmcp-client -p codex-mcp` — 184 passed, 2 skipped
- `just test -p codex-protocol -p codex-app-server-protocol -p
codex-mcp` — 579 passed
- `just write-app-server-schema`
- `just fmt`
2026-06-25 21:50:36 +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
Ahmed Ibrahim 4c0706e24a Allow ChatGPT-hosted MCP servers to use session auth (#29733)
## Why

ChatGPT session authentication was inferred from the reserved Codex Apps
server name. That couples credential routing to Codex Apps-specific
behavior and prevents other MCP endpoints hosted by ChatGPT from
explicitly using the current session.

The opt-in also needs a clear security boundary: an arbitrary MCP
configuration must not be able to redirect ChatGPT credentials to
another origin.

## What changed

- Add `use_chatgpt_auth` to HTTP MCP server configuration, defaulting to
`false`.
- Honor the setting only when the parsed server URL has the same HTTP(S)
origin as the configured `chatgpt_base_url`; otherwise remove the
capability before startup.
- Resolve bearer tokens and static or environment-backed authorization
headers before selecting authentication, with configured authorization
taking precedence over ChatGPT session auth.
- Enable the setting for the built-in Codex Apps and hosted plugin
runtime endpoints while keeping Codex Apps caching and tool
normalization scoped to the reserved server.
- Persist the setting through MCP config rewrite paths and expose it in
the generated config schema.
- Load the current login state for `codex mcp list` so reported auth
status matches runtime behavior.

## Verification

Core integration coverage exercises the complete streamable HTTP MCP
startup path and verifies that:

- a same-origin opted-in server receives the current ChatGPT access
token;
- an explicitly configured authorization header takes precedence;
- a different-origin server completes MCP initialization and tool
listing without receiving any ChatGPT authorization header.
2026-06-24 19:21:28 -07:00
rphilizaire-openai 322e33512b [codex] trace MCP startup latency (#28630)
## Summary

- add trace-level instrumentation around per-server MCP setup, client
construction, initialization, and initial tool listing
- trace Codex Apps tool and server-info cache loads
- attach `server_name` to server-scoped spans so slow startup work can
be attributed to a specific MCP server

## Why

`session_init.mcp_manager_init` can occasionally be slow, but its
existing coarse span does not identify whether time is spent loading the
Codex Apps cache, constructing a client, initializing a transport, or
listing tools. These definition-level spans provide that breakdown
without changing startup behavior.

## Validation

- `just test -p codex-mcp` (87 passed)
- `just test -p codex-rmcp-client` (86 passed, 2 skipped)
2026-06-23 17:46:54 -07:00
jif e476fc16ce Prepare managed network sandbox context (#29456)
## Why

Managed network configures commands to use local HTTP and SOCKS proxies.
For commands delegated to the exec server, the proxy environment and the
sandbox policy were prepared separately. On macOS, that meant a command
could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still
denied access to port `43123`.

## What changed

`NetworkProxy` now prepares the command environment and sandbox context
together from the same runtime snapshot:

```text
Prepared managed network
├── command environment: HTTPS_PROXY=http://127.0.0.1:43123
└── sandbox context: allow outbound to 127.0.0.1:43123
```

That context travels with remote exec requests. The exec server
preserves the managed proxy and CA environment, and macOS Seatbelt
allows only the prepared loopback proxy ports without enabling broad
network access or local binding.

The protocol field is optional and the existing enforcement flag remains
in place, preserving compatibility with callers that do not send the new
context.
2026-06-23 20:07:09 +01:00
jif bbe1006890 Update rmcp to 1.8.0 (#29634)
## Summary

- Update `rmcp` and `rmcp-macros` from 1.7.0 to 1.8.0.
- Adapt to the new shared `peer_info` return type.
- Box OAuth status discovery at the MCP boundary to keep the expanded
future type from overflowing Rust's trait recursion limit.

This brings in custom OAuth HTTP client support from
[modelcontextprotocol/rust-sdk#908](https://github.com/modelcontextprotocol/rust-sdk/pull/908).
2026-06-23 15:25:28 +01:00
Adam Perry @ OpenAI 67009bc53f mcp: accept foreign absolute cwd for remote stdio (#29493)
## Why

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

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

## What changed

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

## Validation

- `just test -p codex-utils-path-uri -p codex-config -p codex-mcp -p
codex-rmcp-client` (408 passed)
- `just test -p codex-cli -p codex-rmcp-client` (372 passed)
- `cargo check --workspace --tests`
- `just test` (11,311 passed; 43 unrelated environment/timing failures)
- `just fix -p codex-cli -p codex-config -p codex-core -p codex-mcp -p
codex-mcp-extension -p codex-rmcp-client -p codex-tui`
2026-06-23 01:33:51 +00:00
Adam Perry @ OpenAI 11fab432be path-uri: clarify host-native path conversion (#29501)
## Why

Downstream refactors are producing confusing code with this
functionality having a very generic name. Encoding the specific
conversion approach in the method name makes it clearer.

## What

Rename `PathUri::from_path` to `PathUri::from_host_native_path` and
update its Rust call sites.
2026-06-23 00:02:33 +00:00
jif bd2968a4db Carry sandbox intent to remote exec servers (#29108)
## What changed

PR #29099 stopped sending the orchestrator's concrete sandbox wrapper to
a remote exec-server. Remote commands now arrive as plain native argv.

This PR adds the next piece: Codex also sends portable sandbox intent
next to that plain argv.

For a remote unified-exec command, the request can now include:

- the canonical permission profile before local workspace-root
materialization
- the sandbox cwd and workspace roots as `PathUri` values
- Windows sandbox settings
- the legacy Landlock setting
- whether managed networking must be enforced

The important part is that symbolic entries such as `:workspace_roots`
stay symbolic while crossing the boundary. The executor can then bind
them to its own workspace-root paths instead of receiving
orchestrator-local absolute paths.

The data travels through `ExecRequest` into `ExecParams`. Older
exec-servers can still deserialize requests because the new fields have
defaults.

## Why

The orchestrator should not decide how another machine implements
sandboxing.

For example:

- a local macOS Codex would normally build a Seatbelt command
- a remote Linux executor needs a Linux sandbox command instead

The orchestrator now sends the plain command plus the policy it intended
to enforce. A later PR can let the exec-server choose and build the
correct sandbox for its own operating system.

## Important detail

This keeps the portable intent separate from the local `SandboxType`.

`SandboxType::None` is ambiguous:

- it can mean the command was explicitly approved to run without a
sandbox
- it can also mean the orchestrator host has no concrete sandbox
implementation available

Those cases are different for remote execution. This PR adds
`sandbox_requested` so an executor can still receive sandbox intent when
the orchestrator cannot build a local wrapper. Explicit unsandboxed
retries still send no sandbox context.

## Behavior today

This PR only transports the intent. The exec-server accepts the new
fields but does not apply them yet.

Remote commands therefore remain unsandboxed after this PR, just as they
are after PR #29099.

## Follow-up

The next PR will make exec-server read this portable intent, bind
symbolic workspace permissions to executor-native roots, choose the
sandbox for its own operating system, build the wrapper locally, and
then spawn the command.
2026-06-21 12:33:21 +02:00
pakrym-oai b6d6be2a84 [codex] prototype mcp_history thread hint injection (#29259)
## Why

Prototype whether the harness can invoke the `mcp_history` MCP while
constructing full initial context and expose its thread hint to the
model without requiring a model-issued tool call.

The prototype builds on the context-window lineage added by #29256 and
is now based directly on `main`.

## What changed

- Call `mcp_history/thread_hint` with no arguments while building the
full `<token_budget>` context.
- Pass the current `threadId` through MCP request metadata, matching the
normal MCP tool-call path.
- Serialize only the unstructured `content` result and append it inside
`<token_budget>` when the call succeeds.
- Omit the additional context when the MCP call or content serialization
fails.

## Prototype limitations

- The direct call bypasses the normal model-initiated MCP approval,
lifecycle-event, telemetry, and result-sanitization path.
- The call has no prototype-specific timeout, result-size cap, or
per-window cache.
- MCP latency is added to full-context construction, including
applicable compaction paths.

## Validation

- `just test -p codex-core token_budget`
2026-06-20 18:02:02 -07:00
xl-openai 4e6bc42266 [codex] Support protected resource OAuth discovery (#29022)
## Why

Plugin-install preflight and the actual OAuth login flow used different
discovery implementations. Preflight had a Codex-specific implementation
that only queried authorization-server metadata on the MCP host, while
login already used the upstream `rmcp` Rust MCP SDK. As a result,
servers that advertise a separate authorization server through RFC 9728
Protected Resource Metadata were classified as OAuth-unsupported during
plugin installation, so login was skipped.

## What changed

- delegate plugin-install OAuth discovery to
`rmcp::transport::AuthorizationManager`, the same implementation used by
the login flow
- let `rmcp` follow Protected Resource Metadata first and perform direct
RFC 8414 authorization-server discovery when protected-resource
discovery does not yield usable metadata
- retain Codex's existing HTTP headers, timeout, `no_proxy` behavior,
and scope normalization around that discovery
- add unit coverage and a pure-MCP plugin-install integration test that
proves the protected-resource path reaches OAuth client registration

This only changes shared MCP OAuth discovery. App declarations and
`appsNeedingAuth` behavior are unchanged.

## Verification

- `just test -p codex-rmcp-client auth_status`
- `just test -p codex-app-server plugin_install_starts_mcp_oauth`
- real plugin-install smoke test with an isolated `CODEX_HOME`: both
DigitalOcean MCP servers started OAuth callback listeners, while Linear
continued to start its existing direct-discovery OAuth flow
2026-06-18 21:17:20 -07:00