## What changed
- Select fresh authentication for each HTTP operation on eligible OpenAI MCP connections while preserving the connection's credential source and account network policy. Stop redirects when requests carry runtime assertions.
- Pass the owning thread's authentication and runtime context through app discovery, connector metadata, and trusted-access requests. Include the runtime authentication mode in MCP connection and tool-cache identities.
- Give resumed actors analytics clients bound to their own authentication context, and serialize stored-credential reloads with token refresh while preserving host-provided credentials.
## Testing
Add coverage for fresh request assertions, revoked account policies, redirect handling, resumed-actor isolation, and MCP uploads in dual-header and assertion-only modes.
GitOrigin-RevId: f06900a6db61cd6cf53c3aa8075afeec4d5706d9
## Why
Stdio servers without `CODEX_MCP_PROTOCOL_VERSION` use the legacy handshake, even when they support MCP `2026-07-28`.
## What changed
- Add the disabled-by-default `mcp_connection_sharing` feature to enable discovery for eligible local stdio servers.
- Recover from failed or late discovery with a fresh handshake when needed, waiting for the previous helper to release resources before restarting.
- Retain a working legacy connection when an upgrade attempt fails for the same account, except on authentication errors.
- Keep tool catalogs separate by protocol and invalidate legacy catalogs when discovery negotiates the modern protocol.
## Testing
Add regression coverage for failed and late discovery, legacy fallback, protocol-specific catalog reuse, message limits, and Windows descendant cleanup.
GitOrigin-RevId: 36c675ef3b81d32fccf06ccbddda2312cfe602c9
## Why
Cached executor MCP declarations must not gain access to host environment credentials when reconnecting to an executor that lacks environment credential resolution.
## What changed
- Retain credential policy through MCP registration, catalog materialization, and runtime setup. Derive selected HTTP plugin policy from the plugin's source environment.
- Reject host bearer-token fallback and transports requiring host environment headers or helpers for executor-owned declarations. Preserve host-configured credential resolution.
- Include credential policy in connection and tool catalog cache identities, and avoid reading host environment values for executor-only identities.
## Testing
Add regression coverage for legacy executors, cached declarations after a capability downgrade, policy preservation through catalog rebuilds, and connection and tool cache isolation.
GitOrigin-RevId: 2d5b85e4e98668c637c538cbe528914c936bd831
## Why
MCP OAuth configuration supported a pre-registered client ID but could not supply a client secret for token exchange or refresh.
## What changed
- Add `oauth.client_secret` and `codex mcp add --oauth-client-secret`, requiring a nonempty secret and client ID. Persist the secret in server configuration and accept `clientSecret` in plugin configuration.
- Pass configured credentials through CLI, app-server, and plugin login flows and token refresh. Require a new login when a configured confidential client's ID differs from stored credentials.
- Redact secrets in debug output and keep them out of authorization URLs and persisted OAuth token records.
- Invalidate cached OAuth connections when the configured client ID or secret changes.
## Testing
Add coverage for configuration validation and round trips, CLI argument redaction and subsequent login, app-server login, connection invalidation, and refresh with `client_secret_basic` and `client_secret_post` in both refresh modes. Verify secret exclusion from token records and reject mismatched client IDs before contacting the provider.
GitOrigin-RevId: 85bb0e410e9e68e6cd68eb6359badfc7eac78147
## Why
Read-only MCP discovery and invocation need a consistent policy across requests, connection reuse, and tool catalogs. Reusing an unrestricted connection or cached catalog can bypass filtered discovery.
## What changed
- Thread `requires_read_only_mcp_tools` through MCP configuration and client setup, leaving it disabled by default.
- When enabled, set `openai/readOnly` to `true` in `tools/list` and `tools/call` request metadata, overriding a caller-supplied `false` while preserving other metadata, pagination, and arguments.
- Include the policy in connection identity and bypass shared Apps and persistent tool catalog caches for read-only connections.
## Testing
Add coverage for metadata propagation in legacy and modern protocol modes, preservation through session recovery, and isolation from unrestricted connections and shared Apps catalogs.
GitOrigin-RevId: f4a3a4021a54e302bffd53f08092b080c056246b
## Why
Enterprise MCP authorization must remain controlled by host, user, or managed configuration. Project settings and plugin declarations must not redirect enterprise credentials or downgrade the selected authentication mode.
## What changed
- Add `auth = "ema_auth"`, shared `mcp_enterprise_managed_auth` IdP configuration, and per-plugin enterprise registration settings.
- Require enterprise registrations to come from one non-project configuration layer, preserve managed IdP precedence, and reject project overrides that change authorization or re-enable disabled enterprise servers.
- Add the disabled-by-default `use_xaa` feature flag and require non-project opt-in or a managed requirement.
- Reject plugin declarations that select `ema_auth`. Keep EMA connections unsupported and block ordinary MCP OAuth login and credential fallback for this mode.
- Extend the configuration schema and serialization for the new settings.
## Testing
Add coverage for IdP precedence, incomplete registrations, alternate credential validation, project-only opt-in, plugin self-declaration rejection, configuration round-tripping, and trusted-project attempts to downgrade enterprise auth to OAuth or ChatGPT auth.
GitOrigin-RevId: f193bdfda74208c98e496a23bf2916955b131a1c
## What changed
- Add the under-development `mcp_oauth_refresh_coordination` feature and pin its selected refresh mode to each MCP connection.
- Treat the refresh mode as part of OAuth connection identity so runtime and MCP configuration refreshes reconnect when the mode changes.
- Keep legacy refresh behavior in builds where coordination is unavailable and emit a warning when coordinated mode is selected.
## Testing
- Cover connection replacement when the feature is toggled through both runtime and MCP configuration refresh paths.
GitOrigin-RevId: 89596150f0338e6ede0e712badecea4450831766
## Why
Plugin MCP trust decisions must describe the exact host-discovered server
registration. Looking up a plugin again by ID can resolve a different root.
## What changed
- Retain the host plugin root in MCP catalog attribution and connection identity.
- Reconnect an MCP server when its attributed plugin root changes.
- Pass the retained root through tool lifecycle attribution and use it directly
when building trusted tool context.
## Testing
Add coverage for catalog identity, connection reconciliation, lifecycle
attribution, and rejecting a different root associated with the same plugin ID.
GitOrigin-RevId: bc92743dd40ab44065a07c3ea972b4956ef8351f
## What changed
- Include the configured authentication mode in the MCP connection identity so
runtime reconciliation replaces connections instead of reusing them after an
authentication-mode change.
- Add a regression test covering a switch from OAuth to ChatGPT authentication
for an executor-owned MCP server.
GitOrigin-RevId: 18cd9df62a9a5ee44594dcde6a844ad7d5e67ee2
## Why
Older executors do not support resolving HTTP header values from their own
environment. Sending environment-backed bearer tokens to them can prevent MCP
servers from starting.
## What changed
- Advertise executor support for environment-backed HTTP headers with the
`httpHeaderEnvVars` capability.
- Delegate bearer-token resolution only when the selected executor advertises
that capability; otherwise, resolve tokens available in the host environment.
- Exclude executor-owned MCP servers that require environment-backed bearer
tokens when the executor cannot resolve them, while retaining compatible
host-owned and executor-owned servers.
## Testing
Added protocol and environment-variable coverage plus an end-to-end test that
simulates an older executor and verifies compatible MCP servers remain usable.
GitOrigin-RevId: 43545926f6f7e0f3b1091eb29d34647f6372bc4a
## Why
Remote HTTP MCP connections can source header values from the host environment,
so those variables must remain part of the connection identity even when the MCP
server runs in an executor environment.
## What changed
- Track `env_http_headers` variables for remote HTTP MCP connections.
- Continue tracking `bearer_token_env_var` only for local environments, where the
host resolves the token.
## Testing
Add coverage that verifies remote connections track host header variables without
tracking executor bearer-token variables, while local connections track both.
GitOrigin-RevId: 8fe175c54a101721a27b76495dcd298f0c2bc6a3
## Why
Executor-owned HTTP MCP servers need to read their bearer credentials from the
selected executor environment instead of the host process.
## What changed
- Preserve `bearer_token_env_var` for executor-owned HTTP MCP configurations and
resolve it when the executor sends each request.
- Extend delegated HTTP headers with executor-local environment references while
rejecting missing, empty, or protected credential variables.
- Keep transport-provided bearer authentication compatible with MCP redirect and
OAuth handling without sending a placeholder authorization value.
## Testing
- Cover authenticated executor-owned MCP requests end to end.
- Cover delegated header resolution and rejection of protected variables.
- Cover parsing executor-owned bearer configuration and transport-provided bearer
behavior.
GitOrigin-RevId: 442bf7382198ffb69eba797eb36d4c74faabda88
## What changed
- Add `http_headers_helper` configuration for local streamable HTTP MCP servers. The configured shell command runs once per connection and returns a JSON object of headers that is cached across requests.
- Apply helper headers to MCP startup and OAuth flows while restricting them to the server origin, stopping redirects, rejecting reserved or duplicate headers, and enforcing output and execution limits.
- Reject helpers for remote or managed-disabled servers, use the local environment working directory, and redact helper commands from `codex mcp list` and `codex mcp get` output.
## Testing
- Cover configuration validation, helper lifecycle and output parsing, origin isolation, OAuth discovery and token refresh, managed requirements, environment selection, and CLI redaction.
GitOrigin-RevId: 84e0e26ce75520b0869d37c72b1678e033bd6818
## Why
Refreshing MCP connection identities should not stall the async executor while another process holds the OAuth credential-store lock.
## What changed
- Probe file and secrets credential stores without waiting during runtime refreshes, retaining the last known credentials when the store is contended.
- Reuse unchanged authentication failures while credentials are temporarily unavailable instead of restarting the connection.
- Run blocking credential loads on a blocking worker during MCP client construction.
- Allow OAuth fallback when a configured authorization header is missing, empty, or invalid.
GitOrigin-RevId: 38dad5606da9a1ceb30fe945000ec9151a5058fa
## 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
## 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
## Why
App-server clients need to advertise structured MCP extension settings, including supported MCP App UI MIME types, rather than only opting into OpenAI form elicitation with a boolean.
## What changed
- Add an `extensions` map to initialize capabilities and preserve `mcpServerOpenaiFormElicitation` as a legacy alias for `openai/form`.
- Capture the declared extension profile when a thread is started, resumed, or forked, propagate it to subagents, and advertise it to downstream MCP servers during initialization.
- Keep the profile stable for the lifetime of the loaded session instead of changing it on later turns or direct tool calls.
## Testing
- Cover extension conversion, downstream MCP initialization, session isolation, legacy form support, and subagent inheritance.
GitOrigin-RevId: fbcedbb74ce788e574b0f884a4c45c4cedb9de54
## 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
## Why
Executor-owned MCP servers must not receive hosted ChatGPT actor credentials or credentials from the host's OAuth store.
## What changed
- Provide the ChatGPT auth provider and consult stored OAuth credentials only for MCP servers in the local environment.
- Require non-local servers configured with `auth = "chatgpt"` to supply a non-empty, valid static `Authorization` header. Reject environment-backed authorization so host secrets are not resolved for an executor-owned server.
- Report unsupported authentication and fail startup before connecting when an executor-owned server has no acceptable explicit authorization.
## Testing
Add coverage for local credential forwarding, non-local credential isolation, static header validation, auth status, OAuth lookup avoidance, and startup rejection.
GitOrigin-RevId: e18f1878f260c93d9babd390a5a7eac86172299b
## 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
Scope MCP sandbox metadata to the MCP server's owning environment.
Previously, `codex/sandbox-state-meta` always used the turn's primary
cwd and rebuilt a legacy sandbox policy from that cwd. That can be wrong
for MCP servers owned by a different execution environment.
This now sends the owning environment cwd as a `file:` URI in
`sandboxCwd`, keeps `permissionProfile` as the permission source of
truth, and omits sandbox-state metadata when a non-default server
environment is not selected for the turn. Local/default MCP servers keep
the existing fallback cwd behavior.
Tests:
- `just fmt`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just test -p codex-mcp`
- `just test -p codex-core mcp_sandbox_cwd`
- `cargo build -p codex-rmcp-client --bin test_stdio_server`
- `just test -p codex-core
stdio_mcp_tool_call_includes_sandbox_state_meta`
## Why
**In short:** this PR resolves already-discovered MCP registrations. It
does not read selected plugins or discover their MCP servers.
The resolved MCP catalog currently builds config and auto-discovered
plugin registrations before runtime contributors are applied. A
thread-selected plugin needs a distinct precedence tier in that same
initial resolution pass: otherwise a disabled lower-precedence winner
can leave stale name-level state behind, and the winning MCP tools
cannot be attributed to the selected package reliably.
This PR adds that catalog boundary before executor discovery is
connected.
## What changed
- Added an explicit selected-plugin registration tier between
auto-discovered plugins and explicit config.
- Collected selected-plugin contributions before the initial catalog
build, while leaving compatibility and generic extension overlays in
their existing runtime phase.
- Retained the winning plugin ID and display name directly on
plugin-owned catalog registrations.
- Derived MCP tool provenance from the winning catalog entry instead of
joining against local-only plugin summaries.
- Retained the winning selected server's tool approval policy in the
running connection manager, so a selected registration cannot inherit
approval behavior from a losing local plugin.
- Kept remembered approval session-scoped for selected plugins until
there is an authority-aware persistence contract; Codex will not write
approval back to an unrelated local plugin.
- Preserved existing name-level disabled vetoes for discovered plugins
and config, while keeping a selected package's own disabled registration
scoped to that registration.
- Preserved deterministic selection order and existing config,
compatibility, and extension precedence.
The resulting order is:
```text
auto-discovered plugin
< selected plugin
< explicit config
< compatibility registration
< extension overlay
```
## Behavior and scope
This is a catalog and provenance change only. No production host
contributes selected-plugin MCP registrations yet, so existing local MCP
behavior remains unchanged.
The stacked follow-up, #27870, installs the executor plugin provider
that produces these registrations. App-server activation remains a
separate final step.
## Verification
Focused tests cover precedence, deterministic selected-plugin conflicts,
disabled-veto behavior across catalog phases, managed requirements
before selected-plugin resolution, winning-server approval policy, and
attribution when local and selected packages share an ID or server name.
CI owns execution of the test suite.
## 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>