Preserve ChatGPT auth for trusted staging MCP servers (#36983)

## What changed

- Treat an MCP server as trusted for ChatGPT authentication when its origin
  matches the configured HTTPS `chatgpt-staging.com` host or one of its
  subdomains.
- Continue trusting the production ChatGPT origin and falling back to OAuth for
  other transports and origins.

## Testing

- Cover both the staging apex domain and a staging subdomain.

GitOrigin-RevId: 1b1d06ab214141d39eb38d46de9dfb74e6fedb3e
This commit is contained in:
yijiang12 authored and copyberry committed 2026-08-04 22:13:50 +00:00
1 parent b9ba969f39
commit bf05737da4
2 files changed
+52 -13

No files matched your search

+33 -13
View File
@@ -269,6 +269,38 @@ pub fn effective_mcp_servers(
effective_mcp_servers_from_configured(configured_mcp_servers(config), config, auth) effective_mcp_servers_from_configured(configured_mcp_servers(config), config, auth)
} }
fn is_trusted_chatgpt_mcp_server(
transport: &McpServerTransportConfig,
chatgpt_base_url: &str,
) -> bool {
let McpServerTransportConfig::StreamableHttp { url, .. } = transport else {
return false;
};
let Ok(server_url) = url::Url::parse(url) else {
return false;
};
if !matches!(server_url.scheme(), "http" | "https") {
return false;
}
if url::Url::parse(CHATGPT_CODEX_BASE_URL)
.ok()
.is_some_and(|chatgpt_url| server_url.origin() == chatgpt_url.origin())
{
return true;
}
url::Url::parse(chatgpt_base_url)
.ok()
.is_some_and(|staging_url| {
staging_url.scheme() == "https"
&& staging_url.domain().is_some_and(|host| {
host == "chatgpt-staging.com" || host.ends_with(".chatgpt-staging.com")
})
&& server_url.origin() == staging_url.origin()
})
}
/// Converts a materialized server map to its auth-gated runtime view. /// Converts a materialized server map to its auth-gated runtime view.
/// ///
/// Compatibility built-ins and extension overlays must already be reflected in /// Compatibility built-ins and extension overlays must already be reflected in
@@ -278,24 +310,12 @@ pub fn effective_mcp_servers_from_configured(
config: &McpConfig, config: &McpConfig,
auth: Option<&CodexAuth>, auth: Option<&CodexAuth>,
) -> HashMap<String, EffectiveMcpServer> { ) -> HashMap<String, EffectiveMcpServer> {
let chatgpt_origin = url::Url::parse(CHATGPT_CODEX_BASE_URL)
.ok()
.map(|url| url.origin());
let mut servers = configured_servers let mut servers = configured_servers
.into_iter() .into_iter()
.map(|(name, mut server)| { .map(|(name, mut server)| {
match server.auth.clone() { match server.auth.clone() {
McpServerAuth::ChatGpt => { McpServerAuth::ChatGpt => {
let server_origin = match &server.transport { if !is_trusted_chatgpt_mcp_server(&server.transport, &config.chatgpt_base_url) {
McpServerTransportConfig::StreamableHttp { url, .. } => {
url::Url::parse(url)
.ok()
.filter(|url| matches!(url.scheme(), "http" | "https"))
.map(|url| url.origin())
}
McpServerTransportConfig::Stdio { .. } => None,
};
if server_origin.as_ref() != chatgpt_origin.as_ref() {
server.auth = McpServerAuth::OAuth; server.auth = McpServerAuth::OAuth;
} }
} }
+19
View File
@@ -362,6 +362,25 @@ fn codex_apps_server_config_forwards_originator_and_configured_product_sku_heade
} }
} }
#[test]
fn effective_mcp_servers_preserve_chatgpt_auth_for_staging() {
for url in [
"https://chatgpt-staging.com",
"https://preview.chatgpt-staging.com",
] {
let mut config = test_mcp_config(PathBuf::new());
config.chatgpt_base_url = url.to_string();
let server = codex_apps_mcp_server_config(
url, /*apps_mcp_product_sku*/ None, /*originator*/ None,
);
let configured = HashMap::from([("staging".to_string(), server)]);
let effective =
effective_mcp_servers_from_configured(configured, &config, /*auth*/ None);
assert_eq!(effective["staging"].config().auth, McpServerAuth::ChatGpt);
}
}
#[tokio::test] #[tokio::test]
async fn effective_mcp_servers_preserve_runtime_servers() { async fn effective_mcp_servers_preserve_runtime_servers() {
let codex_home = tempfile::tempdir().expect("tempdir"); let codex_home = tempfile::tempdir().expect("tempdir");