mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08: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:
committed by
GitHub
Unverified
parent
a0d5fd772e
commit
4c0706e24a
@@ -18,6 +18,7 @@ use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_global_mcp_servers;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_login::AuthManager;
|
||||
use codex_mcp::McpOAuthLoginSupport;
|
||||
use codex_mcp::ResolvedMcpOAuthScopes;
|
||||
use codex_mcp::compute_auth_statuses;
|
||||
@@ -344,6 +345,7 @@ async fn run_add(config_overrides: &CliConfigOverrides, add_args: AddArgs) -> Re
|
||||
};
|
||||
|
||||
let new_entry = McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: transport.clone(),
|
||||
environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
|
||||
enabled: true,
|
||||
@@ -543,8 +545,11 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
)));
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await;
|
||||
let auth = auth_manager.auth().await;
|
||||
let mcp_servers = mcp_manager.configured_servers(&config).await;
|
||||
let effective_mcp_servers = mcp_manager.effective_servers(&config, /*auth*/ None).await;
|
||||
let effective_mcp_servers = mcp_manager.effective_servers(&config, auth.as_ref()).await;
|
||||
|
||||
let mut entries: Vec<_> = mcp_servers.iter().collect();
|
||||
entries.sort_by_key(|(name, _)| *name);
|
||||
@@ -552,7 +557,7 @@ async fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) ->
|
||||
effective_mcp_servers.iter(),
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
config.auth_keyring_backend_kind(),
|
||||
/*auth*/ None,
|
||||
auth.as_ref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ use super::ResolvedMcpCatalog;
|
||||
|
||||
fn server(url: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1272,6 +1272,7 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
|
||||
(
|
||||
"stdio".to_string(),
|
||||
EffectiveMcpServer::configured(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "echo".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1298,6 +1299,7 @@ async fn no_local_runtime_fails_local_stdio_but_keeps_local_http_server() {
|
||||
(
|
||||
"http".to_string(),
|
||||
EffectiveMcpServer::configured(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "http://127.0.0.1:1".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -1405,6 +1407,7 @@ fn mcp_init_error_display_prompts_for_github_pat() {
|
||||
let server_name = "github";
|
||||
let entry = McpAuthStatusEntry {
|
||||
config: Some(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://api.githubcopilot.com/mcp/".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -1458,6 +1461,7 @@ fn mcp_init_error_display_reports_generic_errors() {
|
||||
let server_name = "custom";
|
||||
let entry = McpAuthStatusEntry {
|
||||
config: Some(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com".to_string(),
|
||||
bearer_token_env_var: Some("TOKEN".to_string()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -32,6 +32,7 @@ fn stdio_server(
|
||||
env_vars: Vec<McpServerEnvVar>,
|
||||
) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: command.to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -66,6 +67,7 @@ fn declared_placement_preserves_local_plugin_normalization() {
|
||||
Vec::new(),
|
||||
);
|
||||
let expected_http = McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -104,6 +104,7 @@ mod tests {
|
||||
|
||||
fn stdio_server(environment_id: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "echo".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -130,6 +131,7 @@ mod tests {
|
||||
|
||||
fn http_server(environment_id: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "http://127.0.0.1:1".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -172,6 +172,9 @@ fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem {
|
||||
}
|
||||
}
|
||||
|
||||
if config.use_chatgpt_auth {
|
||||
entry["use_chatgpt_auth"] = value(true);
|
||||
}
|
||||
if !config.enabled {
|
||||
entry["enabled"] = value(false);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ async fn replace_mcp_servers_serializes_per_tool_approval_overrides() -> anyhow:
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -95,6 +96,7 @@ async fn replace_mcp_servers_serializes_oauth_client_id() -> anyhow::Result<()>
|
||||
let servers = BTreeMap::from([(
|
||||
"maas_outlook".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -131,6 +131,12 @@ pub struct McpServerConfig {
|
||||
#[serde(flatten)]
|
||||
pub transport: McpServerTransportConfig,
|
||||
|
||||
/// When `true`, request authentication with the current ChatGPT session. Codex honors this
|
||||
/// only when the server URL has the same origin as the configured ChatGPT base URL and no
|
||||
/// configured bearer token or authorization header resolves.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub use_chatgpt_auth: bool,
|
||||
|
||||
/// Effective environment id for where Codex should start this MCP server.
|
||||
pub environment_id: String,
|
||||
|
||||
@@ -239,6 +245,8 @@ pub struct RawMcpServerConfig {
|
||||
#[serde(default)]
|
||||
pub environment_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub use_chatgpt_auth: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub startup_timeout_sec: Option<f64>,
|
||||
#[serde(default)]
|
||||
pub startup_timeout_ms: Option<u64>,
|
||||
@@ -286,6 +294,7 @@ impl TryFrom<RawMcpServerConfig> for McpServerConfig {
|
||||
bearer_token,
|
||||
bearer_token_env_var,
|
||||
environment_id,
|
||||
use_chatgpt_auth,
|
||||
startup_timeout_sec,
|
||||
startup_timeout_ms,
|
||||
tool_timeout_sec,
|
||||
@@ -329,6 +338,7 @@ impl TryFrom<RawMcpServerConfig> for McpServerConfig {
|
||||
throw_if_set("stdio", "env_http_headers", env_http_headers.as_ref())?;
|
||||
throw_if_set("stdio", "oauth", oauth.as_ref())?;
|
||||
throw_if_set("stdio", "oauth_resource", oauth_resource.as_ref())?;
|
||||
throw_if_set("stdio", "use_chatgpt_auth", use_chatgpt_auth.as_ref())?;
|
||||
let env_vars = env_vars.unwrap_or_default();
|
||||
for env_var in &env_vars {
|
||||
env_var.validate_source()?;
|
||||
@@ -361,6 +371,7 @@ impl TryFrom<RawMcpServerConfig> for McpServerConfig {
|
||||
|
||||
Ok(Self {
|
||||
transport,
|
||||
use_chatgpt_auth: use_chatgpt_auth.unwrap_or_default(),
|
||||
environment_id,
|
||||
startup_timeout_sec,
|
||||
tool_timeout_sec,
|
||||
|
||||
@@ -426,6 +426,7 @@ fn deserialize_ignores_unknown_server_fields() {
|
||||
assert_eq!(
|
||||
cfg,
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "echo".to_string(),
|
||||
args: vec![],
|
||||
|
||||
@@ -677,6 +677,7 @@ async fn load_plugins_loads_default_skills_and_mcp_servers() {
|
||||
mcp_servers: HashMap::from([(
|
||||
"sample".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://sample.example/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -768,6 +769,7 @@ enabled = true
|
||||
HashMap::from([(
|
||||
"counter".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://sample.example/counter/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -1662,6 +1664,7 @@ async fn load_plugins_uses_manifest_configured_component_paths() {
|
||||
HashMap::from([(
|
||||
"custom".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://custom.example/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -1842,6 +1845,7 @@ async fn load_plugins_ignores_manifest_component_paths_without_dot_slash() {
|
||||
HashMap::from([(
|
||||
"default".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://default.example/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -2092,6 +2096,7 @@ fn capability_index_filters_inactive_and_zero_capability_plugins() {
|
||||
let connector = |id: &str| AppConnectorId(id.to_string());
|
||||
let app = |name: &str, connector_id: &str| app_declaration(name, connector_id);
|
||||
let http_server = |url: &str| McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -2555,6 +2555,10 @@
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"use_chatgpt_auth": {
|
||||
"default": null,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -114,6 +114,7 @@ use tempfile::TempDir;
|
||||
|
||||
fn stdio_mcp(command: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: command.to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -140,6 +141,7 @@ fn stdio_mcp(command: &str) -> McpServerConfig {
|
||||
|
||||
fn http_mcp(url: &str) -> McpServerConfig {
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -5566,6 +5568,7 @@ async fn replace_mcp_servers_round_trips_entries() -> anyhow::Result<()> {
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "echo".to_string(),
|
||||
args: vec!["hello".to_string()],
|
||||
@@ -5923,6 +5926,7 @@ async fn replace_mcp_servers_serializes_env_sorted() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: vec!["--verbose".to_string()],
|
||||
@@ -6002,6 +6006,7 @@ async fn replace_mcp_servers_serializes_env_vars() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6057,6 +6062,7 @@ async fn replace_mcp_servers_serializes_sourced_env_vars() -> anyhow::Result<()>
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6115,6 +6121,7 @@ async fn replace_mcp_servers_serializes_cwd() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6170,6 +6177,7 @@ async fn replace_mcp_servers_streamable_http_serializes_bearer_token() -> anyhow
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6237,6 +6245,7 @@ async fn replace_mcp_servers_streamable_http_serializes_custom_headers() -> anyh
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6319,6 +6328,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
let mut servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6357,6 +6367,7 @@ async fn replace_mcp_servers_streamable_http_removes_optional_sections() -> anyh
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -6424,6 +6435,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||
(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: Some("MCP_TOKEN".to_string()),
|
||||
@@ -6452,6 +6464,7 @@ async fn replace_mcp_servers_streamable_http_isolates_headers_between_servers()
|
||||
(
|
||||
"logs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "logs-server".to_string(),
|
||||
args: vec!["--follow".to_string()],
|
||||
@@ -6540,6 +6553,7 @@ async fn replace_mcp_servers_serializes_disabled_flag() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6590,6 +6604,7 @@ async fn replace_mcp_servers_serializes_required_flag() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6640,6 +6655,7 @@ async fn replace_mcp_servers_serializes_tool_filters() -> anyhow::Result<()> {
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -6695,6 +6711,7 @@ async fn replace_mcp_servers_streamable_http_serializes_oauth_resource() -> anyh
|
||||
let servers = BTreeMap::from([(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com/mcp".to_string(),
|
||||
bearer_token_env_var: None,
|
||||
|
||||
@@ -95,6 +95,9 @@ fn serialize_mcp_server_table(config: &McpServerConfig) -> TomlTable {
|
||||
}
|
||||
}
|
||||
|
||||
if config.use_chatgpt_auth {
|
||||
entry["use_chatgpt_auth"] = value(true);
|
||||
}
|
||||
if !config.enabled {
|
||||
entry["enabled"] = value(false);
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
servers.insert(
|
||||
"stdio".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: vec!["--flag".to_string()],
|
||||
@@ -907,6 +908,7 @@ fn blocking_replace_mcp_servers_round_trips() {
|
||||
servers.insert(
|
||||
"http".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: "https://example.com".to_string(),
|
||||
bearer_token_env_var: Some("TOKEN".to_string()),
|
||||
@@ -981,6 +983,7 @@ fn blocking_replace_mcp_servers_serializes_tool_approval_overrides() {
|
||||
servers.insert(
|
||||
"docs".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "docs-server".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1041,6 +1044,7 @@ foo = { command = "cmd" }
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1091,6 +1095,7 @@ foo = { command = "cmd" } # keep me
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1140,6 +1145,7 @@ foo = { command = "cmd", args = ["--flag"] } # keep me
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -1190,6 +1196,7 @@ foo = { command = "cmd" }
|
||||
servers.insert(
|
||||
"foo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "cmd".to_string(),
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -357,6 +357,7 @@ fn mcp_dependency_to_server_config(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "missing url for streamable_http dependency".to_string())?;
|
||||
return Ok(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::StreamableHttp {
|
||||
url: url.clone(),
|
||||
bearer_token_env_var: None,
|
||||
@@ -386,6 +387,7 @@ fn mcp_dependency_to_server_config(
|
||||
.as_ref()
|
||||
.ok_or_else(|| "missing command for stdio dependency".to_string())?;
|
||||
return Ok(McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: command.clone(),
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -395,6 +395,7 @@ async fn run_code_mode_turn_with_rmcp_config(
|
||||
servers.insert(
|
||||
"rmcp".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -184,6 +184,7 @@ fn insert_rmcp_test_server(config: &mut Config, command: String, approval_mode:
|
||||
servers.insert(
|
||||
RMCP_SERVER.to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -30,6 +30,7 @@ async fn refresh_shuts_down_superseded_mcp_stdio_server() -> anyhow::Result<()>
|
||||
servers.insert(
|
||||
"refresh_cleanup".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -47,6 +47,7 @@ use codex_protocol::protocol::Op;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_cargo_bin::cargo_bin;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use core_test_support::apps_test_server::AppsTestServer;
|
||||
use core_test_support::assert_regex_match;
|
||||
use core_test_support::is_remote_test_environment;
|
||||
use core_test_support::responses;
|
||||
@@ -267,6 +268,7 @@ fn copy_binary_to_remote_env(
|
||||
|
||||
struct TestMcpServerOptions {
|
||||
environment_id: String,
|
||||
use_chatgpt_auth: bool,
|
||||
supports_parallel_tool_calls: bool,
|
||||
tool_timeout_sec: Option<Duration>,
|
||||
}
|
||||
@@ -275,6 +277,7 @@ impl Default for TestMcpServerOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
environment_id: codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
|
||||
use_chatgpt_auth: false,
|
||||
supports_parallel_tool_calls: false,
|
||||
tool_timeout_sec: None,
|
||||
}
|
||||
@@ -315,6 +318,7 @@ fn insert_mcp_server(
|
||||
server_name.to_string(),
|
||||
McpServerConfig {
|
||||
transport,
|
||||
use_chatgpt_auth: options.use_chatgpt_auth,
|
||||
environment_id: options.environment_id,
|
||||
enabled: true,
|
||||
required: false,
|
||||
@@ -1227,6 +1231,7 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
|
||||
stdio_transport(rmcp_test_server_bin, /*env*/ None, Vec::new()),
|
||||
TestMcpServerOptions {
|
||||
environment_id: remote_aware_environment_id(),
|
||||
use_chatgpt_auth: false,
|
||||
supports_parallel_tool_calls: true,
|
||||
tool_timeout_sec: Some(Duration::from_secs(2)),
|
||||
},
|
||||
@@ -2298,6 +2303,155 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn streamable_http_chatgpt_auth_respects_configured_authorization() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let Some(chatgpt_auth_server) =
|
||||
start_streamable_http_test_server("chatgpt-auth", Some("Access Token")).await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let chatgpt_auth_url = chatgpt_auth_server.url().to_string();
|
||||
let chatgpt_base_url = chatgpt_auth_url
|
||||
.strip_suffix("/mcp")
|
||||
.expect("test MCP URL should end in /mcp")
|
||||
.to_string();
|
||||
|
||||
let chatgpt_auth_fixture = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = chatgpt_base_url;
|
||||
insert_mcp_server(
|
||||
config,
|
||||
"chatgpt_auth",
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
url: chatgpt_auth_url,
|
||||
bearer_token_env_var: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
},
|
||||
TestMcpServerOptions {
|
||||
environment_id: remote_aware_environment_id(),
|
||||
use_chatgpt_auth: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
.build_with_remote_env(&server)
|
||||
.await?;
|
||||
wait_for_mcp_server(&chatgpt_auth_fixture.codex, "chatgpt_auth").await?;
|
||||
drop(chatgpt_auth_fixture);
|
||||
chatgpt_auth_server.shutdown().await;
|
||||
|
||||
let Some(configured_auth_server) =
|
||||
start_streamable_http_test_server("configured-auth", Some("configured-token")).await?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let configured_auth_url = configured_auth_server.url().to_string();
|
||||
let configured_auth_base_url = configured_auth_url
|
||||
.strip_suffix("/mcp")
|
||||
.expect("test MCP URL should end in /mcp")
|
||||
.to_string();
|
||||
|
||||
let configured_auth_fixture = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = configured_auth_base_url;
|
||||
insert_mcp_server(
|
||||
config,
|
||||
"configured_auth",
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
url: configured_auth_url,
|
||||
bearer_token_env_var: None,
|
||||
http_headers: Some(HashMap::from([(
|
||||
"Authorization".to_string(),
|
||||
"Bearer configured-token".to_string(),
|
||||
)])),
|
||||
env_http_headers: None,
|
||||
},
|
||||
TestMcpServerOptions {
|
||||
environment_id: remote_aware_environment_id(),
|
||||
use_chatgpt_auth: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
.build_with_remote_env(&server)
|
||||
.await?;
|
||||
|
||||
wait_for_mcp_server(&configured_auth_fixture.codex, "configured_auth").await?;
|
||||
drop(configured_auth_fixture);
|
||||
configured_auth_server.shutdown().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn streamable_http_chatgpt_auth_is_not_sent_to_another_origin() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let untrusted_server = MockServer::start().await;
|
||||
let untrusted_apps = AppsTestServer::mount(&untrusted_server).await?;
|
||||
let untrusted_mcp_url = format!("{}/api/codex/apps", untrusted_apps.chatgpt_base_url);
|
||||
let trusted_chatgpt_base_url = server.uri();
|
||||
|
||||
let fixture = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_config(move |config| {
|
||||
config.chatgpt_base_url = trusted_chatgpt_base_url;
|
||||
insert_mcp_server(
|
||||
config,
|
||||
"untrusted_origin",
|
||||
McpServerTransportConfig::StreamableHttp {
|
||||
url: untrusted_mcp_url,
|
||||
bearer_token_env_var: None,
|
||||
http_headers: None,
|
||||
env_http_headers: None,
|
||||
},
|
||||
TestMcpServerOptions {
|
||||
use_chatgpt_auth: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
|
||||
wait_for_mcp_server(&fixture.codex, "untrusted_origin").await?;
|
||||
let observed_requests = untrusted_server
|
||||
.received_requests()
|
||||
.await
|
||||
.expect("mock server should capture MCP startup requests")
|
||||
.into_iter()
|
||||
.filter(|request| request.url.path() == "/api/codex/apps")
|
||||
.filter_map(|request| {
|
||||
let body: Value = serde_json::from_slice(&request.body).ok()?;
|
||||
let method = body.get("method")?.as_str()?.to_string();
|
||||
let authorization = request
|
||||
.headers
|
||||
.get("authorization")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(str::to_string);
|
||||
Some((method, authorization))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
observed_requests,
|
||||
vec![
|
||||
("initialize".to_string(), None),
|
||||
("notifications/initialized".to_string(), None),
|
||||
("tools/list".to_string(), None),
|
||||
],
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This test writes to a fallback credentials file in CODEX_HOME.
|
||||
/// Ideally, we wouldn't need to serialize the test but it's much more cumbersome to wire CODEX_HOME through the code.
|
||||
#[test]
|
||||
|
||||
@@ -1090,6 +1090,7 @@ async fn tool_search_indexes_only_enabled_non_app_mcp_tools() -> Result<()> {
|
||||
servers.insert(
|
||||
"rmcp".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
@@ -1216,6 +1217,7 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result<
|
||||
servers.insert(
|
||||
"rmcp".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
@@ -1364,6 +1366,7 @@ async fn tool_search_uses_non_app_mcp_server_instructions_as_namespace_descripti
|
||||
servers.insert(
|
||||
"rmcp".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -657,6 +657,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result<
|
||||
servers.insert(
|
||||
server_name.to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -276,6 +276,7 @@ async fn token_budget_context_injects_plain_thread_hint_text() -> Result<()> {
|
||||
servers.insert(
|
||||
"notes".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -383,6 +383,7 @@ async fn mcp_tool_call_output_exceeds_limit_truncated_for_model() -> Result<()>
|
||||
servers.insert(
|
||||
server_name.to_string(),
|
||||
codex_config::types::McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: codex_config::types::McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
@@ -481,6 +482,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
|
||||
servers.insert(
|
||||
server_name.to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
@@ -773,6 +775,7 @@ async fn mcp_tool_call_output_not_truncated_with_custom_limit() -> Result<()> {
|
||||
servers.insert(
|
||||
server_name.to_string(),
|
||||
codex_config::types::McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: codex_config::types::McpServerTransportConfig::Stdio {
|
||||
command: rmcp_test_server_bin,
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -166,6 +166,7 @@ async fn reads_declared_config_only_through_executor_file_system() {
|
||||
vec![(
|
||||
"demo".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "demo-mcp".to_string(),
|
||||
args: Vec::new(),
|
||||
@@ -221,6 +222,7 @@ async fn reads_manifest_object_config_without_executor_file_system_access() {
|
||||
vec![(
|
||||
"counter".to_string(),
|
||||
McpServerConfig {
|
||||
use_chatgpt_auth: false,
|
||||
transport: McpServerTransportConfig::Stdio {
|
||||
command: "counter-mcp".to_string(),
|
||||
args: Vec::new(),
|
||||
|
||||
@@ -787,6 +787,12 @@ impl RmcpClient {
|
||||
} => {
|
||||
let default_headers =
|
||||
build_default_headers(http_headers.clone(), env_http_headers.clone())?;
|
||||
let auth_provider =
|
||||
if bearer_token.is_some() || default_headers.contains_key(AUTHORIZATION) {
|
||||
None
|
||||
} else {
|
||||
auth_provider.clone()
|
||||
};
|
||||
|
||||
let initial_oauth_tokens = if bearer_token.is_none()
|
||||
&& auth_provider.is_none()
|
||||
@@ -861,7 +867,7 @@ impl RmcpClient {
|
||||
StreamableHttpClientAdapter::new(
|
||||
Arc::clone(http_client),
|
||||
default_headers,
|
||||
auth_provider.clone(),
|
||||
auth_provider,
|
||||
),
|
||||
http_config,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user