mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
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:
1 parent
a0d5fd772e
commit
4c0706e24a
29 files changed
+313
-59
No files matched your search
@@ -16,8 +16,6 @@ use tracing::warn;
|
||||
|
||||
use crate::server::EffectiveMcpServer;
|
||||
|
||||
use super::CODEX_APPS_MCP_SERVER_NAME;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct McpOAuthLoginConfig {
|
||||
pub url: String,
|
||||
@@ -141,7 +139,9 @@ where
|
||||
let futures = servers.into_iter().map(|(name, server)| {
|
||||
let name = name.clone();
|
||||
let config = server.configured_config().cloned();
|
||||
let has_runtime_auth = name == CODEX_APPS_MCP_SERVER_NAME
|
||||
let has_runtime_auth = config
|
||||
.as_ref()
|
||||
.is_some_and(|config| config.use_chatgpt_auth)
|
||||
&& auth.is_some_and(CodexAuth::uses_codex_backend)
|
||||
&& config.as_ref().is_some_and(|config| {
|
||||
matches!(
|
||||
|
||||
@@ -257,9 +257,26 @@ pub fn effective_mcp_servers_from_configured(
|
||||
config: &McpConfig,
|
||||
auth: Option<&CodexAuth>,
|
||||
) -> HashMap<String, EffectiveMcpServer> {
|
||||
let chatgpt_origin = url::Url::parse(&config.chatgpt_base_url)
|
||||
.ok()
|
||||
.filter(|url| matches!(url.scheme(), "http" | "https"))
|
||||
.map(|url| url.origin());
|
||||
let mut servers = configured_servers
|
||||
.into_iter()
|
||||
.map(|(name, server)| (name, EffectiveMcpServer::configured(server)))
|
||||
.map(|(name, mut server)| {
|
||||
if server.use_chatgpt_auth {
|
||||
let server_origin = match &server.transport {
|
||||
McpServerTransportConfig::StreamableHttp { url, .. } => url::Url::parse(url)
|
||||
.ok()
|
||||
.filter(|url| matches!(url.scheme(), "http" | "https"))
|
||||
.map(|url| url.origin()),
|
||||
McpServerTransportConfig::Stdio { .. } => None,
|
||||
};
|
||||
server.use_chatgpt_auth =
|
||||
server_origin.is_some() && server_origin.as_ref() == chatgpt_origin.as_ref();
|
||||
}
|
||||
(name, EffectiveMcpServer::configured(server))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
if !host_owned_codex_apps_enabled(config, auth) {
|
||||
servers.remove(CODEX_APPS_MCP_SERVER_NAME);
|
||||
@@ -457,6 +474,7 @@ pub fn codex_apps_mcp_server_config(
|
||||
mcp_server_config_for_url(
|
||||
codex_apps_mcp_url_for_base_url(chatgpt_base_url),
|
||||
apps_mcp_product_sku,
|
||||
/*use_chatgpt_auth*/ true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -471,10 +489,18 @@ pub fn hosted_plugin_runtime_mcp_server_config(
|
||||
} else {
|
||||
format!("{base_url}/api/codex")
|
||||
};
|
||||
mcp_server_config_for_url(format!("{base_url}/ps/mcp"), apps_mcp_product_sku)
|
||||
mcp_server_config_for_url(
|
||||
format!("{base_url}/ps/mcp"),
|
||||
apps_mcp_product_sku,
|
||||
/*use_chatgpt_auth*/ true,
|
||||
)
|
||||
}
|
||||
|
||||
fn mcp_server_config_for_url(url: String, apps_mcp_product_sku: Option<&str>) -> McpServerConfig {
|
||||
fn mcp_server_config_for_url(
|
||||
url: String,
|
||||
apps_mcp_product_sku: Option<&str>,
|
||||
use_chatgpt_auth: bool,
|
||||
) -> McpServerConfig {
|
||||
let http_headers = apps_mcp_product_sku.map(|product_sku| {
|
||||
HashMap::from([("X-OpenAI-Product-Sku".to_string(), product_sku.to_string())])
|
||||
});
|
||||
@@ -486,6 +512,7 @@ fn mcp_server_config_for_url(url: String, apps_mcp_product_sku: Option<&str>) ->
|
||||
http_headers,
|
||||
env_http_headers: None,
|
||||
},
|
||||
use_chatgpt_auth,
|
||||
environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
|
||||
enabled: true,
|
||||
required: false,
|
||||
|
||||
@@ -296,6 +296,7 @@ async fn effective_mcp_servers_preserve_runtime_servers() {
|
||||
catalog.register(McpServerRegistration::from_config(
|
||||
"sample".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://user.example/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -321,6 +322,7 @@ async fn effective_mcp_servers_preserve_runtime_servers() {
|
||||
catalog.register(McpServerRegistration::from_config(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://docs.example/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
Reference in new issue
Block a user