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.
This commit is contained in:
Ahmed Ibrahim authored and GitHub committed 2026-06-24 19:21:28 -07:00
1 parent a0d5fd772e
commit 4c0706e24a
29 files changed
+313 -59

No files matched your search

+33 -50
View File
@@ -154,7 +154,7 @@ impl McpConnectionManager {
);
let tool_plugin_provenance = Arc::new(tool_plugin_provenance);
let startup_submit_id = submit_id.clone();
let codex_apps_auth_provider = auth
let chatgpt_auth_provider = auth
.filter(|auth| auth.uses_codex_backend())
.map(codex_model_provider::auth_provider_from_auth);
let mcp_servers = mcp_servers.clone();
@@ -173,17 +173,13 @@ impl McpConnectionManager {
},
)
.await;
let (codex_apps_tools_cache_context, runtime_auth_provider) =
if server_name == CODEX_APPS_MCP_SERVER_NAME {
codex_apps_cache_context_and_auth_provider(
&server,
&codex_home,
&codex_apps_tools_cache_key,
codex_apps_auth_provider.clone(),
)
} else {
regular_mcp_cache_context_and_auth_provider()
};
let codex_apps_tools_cache_context = if server_name == CODEX_APPS_MCP_SERVER_NAME {
codex_apps_tools_cache_context(&codex_home, &codex_apps_tools_cache_key)
} else {
regular_mcp_tools_cache_context()
};
let runtime_auth_provider =
chatgpt_auth_provider_for_server(&server, chatgpt_auth_provider.clone());
let async_managed_client = AsyncManagedClient::new(
server_name.clone(),
server,
@@ -852,48 +848,35 @@ impl Drop for McpConnectionManager {
}
}
/// Creates the host-owned state used only by the Codex Apps server.
///
/// The tools cache is scoped to the authenticated user. Runtime authentication is supplied only
/// when the server is not already configured to read a bearer token from the environment.
fn codex_apps_cache_context_and_auth_provider(
server: &EffectiveMcpServer,
/// Creates the per-user tools cache context used only by the Codex Apps server.
fn codex_apps_tools_cache_context(
codex_home: &Path,
codex_apps_tools_cache_key: &CodexAppsToolsCacheKey,
codex_apps_auth_provider: Option<SharedAuthProvider>,
) -> (
Option<CodexAppsToolsCacheContext>,
Option<SharedAuthProvider>,
) {
let uses_env_bearer_token =
server
.configured_config()
.is_some_and(|config| match &config.transport {
McpServerTransportConfig::StreamableHttp {
bearer_token_env_var,
..
} => bearer_token_env_var.is_some(),
McpServerTransportConfig::Stdio { .. } => false,
});
(
Some(CodexAppsToolsCacheContext {
codex_home: codex_home.to_path_buf(),
user_key: codex_apps_tools_cache_key.clone(),
}),
if uses_env_bearer_token {
None
} else {
codex_apps_auth_provider
},
)
) -> Option<CodexAppsToolsCacheContext> {
Some(CodexAppsToolsCacheContext {
codex_home: codex_home.to_path_buf(),
user_key: codex_apps_tools_cache_key.clone(),
})
}
/// Keeps regular MCP servers isolated from the host-owned Codex Apps cache and auth provider.
fn regular_mcp_cache_context_and_auth_provider() -> (
Option<CodexAppsToolsCacheContext>,
Option<SharedAuthProvider>,
) {
(None, None)
/// Keeps regular MCP servers isolated from the Codex Apps tools cache.
fn regular_mcp_tools_cache_context() -> Option<CodexAppsToolsCacheContext> {
None
}
/// Makes ChatGPT authentication available to servers that explicitly opt in.
/// The HTTP transport applies it only when no configured authorization resolves.
fn chatgpt_auth_provider_for_server(
server: &EffectiveMcpServer,
chatgpt_auth_provider: Option<SharedAuthProvider>,
) -> Option<SharedAuthProvider> {
if !server
.configured_config()
.is_some_and(|config| config.use_chatgpt_auth)
{
return None;
}
chatgpt_auth_provider
}
async fn emit_update(