mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
298e5cfce1
## Summary - route each configured MCP server through an explicit per-server `environment_id` instead of a manager-wide remote toggle - default omitted `environment_id` to `local`, resolve named ids through `EnvironmentManager`, and fail only the affected MCP server when an explicit id is unknown - keep local stdio on the existing local launcher path for now, while named-environment stdio uses the selected environment backend and requires an absolute `cwd` - allow local HTTP MCP servers to keep using the ambient HTTP client when no local `Environment` is configured; named-environment HTTP MCPs use that environment's HTTP client ## Validation - devbox Bazel build: `bazel build --bes_backend= --bes_results_url= //codex-rs/cli:codex //codex-rs/rmcp-client:test_stdio_server //codex-rs/rmcp-client:test_streamable_http_server` - devbox app-server config matrix with real `config.toml` / `environments.toml` files covering omitted local, explicit local, omitted local under remote default, explicit remote stdio, local HTTP without local env, explicit remote HTTP, local stdio without local env, unknown explicit env, and remote stdio without `cwd`
147 lines
4.4 KiB
Rust
147 lines
4.4 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,
|
|
},
|
|
environment_id: crate::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
|
|
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,
|
|
},
|
|
environment_id: crate::DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
|
|
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(())
|
|
}
|