mirror of
https://github.com/openai/codex.git
synced 2026-05-24 04:54:52 +00:00
## Why Some MCP OAuth providers require a pre-registered public client ID and cannot rely on dynamic client registration. Codex already supports MCP OAuth, but it had no way to supply that client ID from config into the PKCE flow. ## What changed - add `oauth.client_id` under `[mcp_servers.<server>]` config, including config editing and schema generation - thread the configured client ID through CLI, app-server, plugin login, and MCP skill dependency OAuth entrypoints - configure RMCP authorization with the explicit client when present, while preserving the existing dynamic-registration path when it is absent - add focused coverage for config parsing/serialization and OAuth URL generation ## Verification - `cargo test -p codex-config -p codex-rmcp-client -p codex-mcp -p codex-core-plugins` - `cargo test -p codex-core blocking_replace_mcp_servers_round_trips --lib` - `cargo test -p codex-core replace_mcp_servers_streamable_http_serializes_oauth_resource --lib` - `cargo test -p codex-core config_schema_matches_fixture --lib` ## Notes Broader local package runs still hit unrelated pre-existing stack overflows in: - `codex-app-server::in_process_start_clamps_zero_channel_capacity` - `codex-core::resume_agent_from_rollout_uses_edge_data_when_descendant_metadata_source_is_stale`
147 lines
4.3 KiB
Rust
147 lines
4.3 KiB
Rust
use super::*;
|
|
use crate::McpServerOAuthConfig;
|
|
use crate::McpServerToolConfig;
|
|
use pretty_assertions::assert_eq;
|
|
use std::collections::HashMap;
|
|
use std::time::SystemTime;
|
|
use std::time::UNIX_EPOCH;
|
|
|
|
#[tokio::test]
|
|
async fn replace_mcp_servers_serializes_per_tool_approval_overrides() -> anyhow::Result<()> {
|
|
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
|
let codex_home = std::env::temp_dir().join(format!(
|
|
"codex-config-mcp-edit-test-{}-{unique_suffix}",
|
|
std::process::id()
|
|
));
|
|
let servers = BTreeMap::from([(
|
|
"docs".to_string(),
|
|
McpServerConfig {
|
|
transport: McpServerTransportConfig::Stdio {
|
|
command: "docs-server".to_string(),
|
|
args: Vec::new(),
|
|
env: None,
|
|
env_vars: Vec::new(),
|
|
cwd: None,
|
|
},
|
|
experimental_environment: None,
|
|
enabled: true,
|
|
required: false,
|
|
supports_parallel_tool_calls: true,
|
|
disabled_reason: None,
|
|
startup_timeout_sec: None,
|
|
tool_timeout_sec: None,
|
|
default_tools_approval_mode: Some(AppToolApproval::Auto),
|
|
enabled_tools: None,
|
|
disabled_tools: None,
|
|
scopes: None,
|
|
oauth: None,
|
|
oauth_resource: None,
|
|
tools: HashMap::from([
|
|
(
|
|
"search".to_string(),
|
|
McpServerToolConfig {
|
|
approval_mode: Some(AppToolApproval::Approve),
|
|
},
|
|
),
|
|
(
|
|
"read".to_string(),
|
|
McpServerToolConfig {
|
|
approval_mode: Some(AppToolApproval::Prompt),
|
|
},
|
|
),
|
|
]),
|
|
},
|
|
)]);
|
|
|
|
ConfigEditsBuilder::new(&codex_home)
|
|
.replace_mcp_servers(&servers)
|
|
.apply()
|
|
.await?;
|
|
|
|
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
let serialized = std::fs::read_to_string(&config_path)?;
|
|
assert_eq!(
|
|
serialized,
|
|
r#"[mcp_servers.docs]
|
|
command = "docs-server"
|
|
supports_parallel_tool_calls = true
|
|
default_tools_approval_mode = "auto"
|
|
|
|
[mcp_servers.docs.tools]
|
|
|
|
[mcp_servers.docs.tools.read]
|
|
approval_mode = "prompt"
|
|
|
|
[mcp_servers.docs.tools.search]
|
|
approval_mode = "approve"
|
|
"#
|
|
);
|
|
|
|
let loaded = load_global_mcp_servers(&codex_home).await?;
|
|
assert_eq!(loaded, servers);
|
|
|
|
std::fs::remove_dir_all(&codex_home)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn replace_mcp_servers_serializes_oauth_client_id() -> anyhow::Result<()> {
|
|
let unique_suffix = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos();
|
|
let codex_home = std::env::temp_dir().join(format!(
|
|
"codex-config-mcp-oauth-edit-test-{}-{unique_suffix}",
|
|
std::process::id()
|
|
));
|
|
let servers = BTreeMap::from([(
|
|
"maas_outlook".to_string(),
|
|
McpServerConfig {
|
|
transport: McpServerTransportConfig::StreamableHttp {
|
|
url: "https://example.com/mcp".to_string(),
|
|
bearer_token_env_var: None,
|
|
http_headers: None,
|
|
env_http_headers: None,
|
|
},
|
|
experimental_environment: None,
|
|
enabled: true,
|
|
required: false,
|
|
supports_parallel_tool_calls: false,
|
|
disabled_reason: None,
|
|
startup_timeout_sec: None,
|
|
tool_timeout_sec: None,
|
|
default_tools_approval_mode: None,
|
|
enabled_tools: None,
|
|
disabled_tools: None,
|
|
scopes: None,
|
|
oauth: Some(McpServerOAuthConfig {
|
|
client_id: Some("eci-prd-pub-codex-123".to_string()),
|
|
}),
|
|
oauth_resource: None,
|
|
tools: HashMap::new(),
|
|
},
|
|
)]);
|
|
|
|
ConfigEditsBuilder::new(&codex_home)
|
|
.replace_mcp_servers(&servers)
|
|
.apply()
|
|
.await?;
|
|
|
|
let config_path = codex_home.join(CONFIG_TOML_FILE);
|
|
let serialized = std::fs::read_to_string(&config_path)?;
|
|
assert_eq!(
|
|
serialized,
|
|
r#"[mcp_servers.maas_outlook]
|
|
url = "https://example.com/mcp"
|
|
|
|
[mcp_servers.maas_outlook.oauth]
|
|
client_id = "eci-prd-pub-codex-123"
|
|
"#
|
|
);
|
|
|
|
let loaded = load_global_mcp_servers(&codex_home).await?;
|
|
assert_eq!(loaded, servers);
|
|
|
|
std::fs::remove_dir_all(&codex_home)?;
|
|
|
|
Ok(())
|
|
}
|