2025-09-14 21:30:56 -07:00
|
|
|
use std::path::Path;
|
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use codex_core::config::load_global_mcp_servers;
|
2025-10-30 10:28:32 +00:00
|
|
|
use codex_core::config::types::McpServerTransportConfig;
|
2025-09-14 21:30:56 -07:00
|
|
|
use predicates::str::contains;
|
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
use tempfile::TempDir;
|
|
|
|
|
|
|
|
|
|
fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
|
|
|
|
|
let mut cmd = assert_cmd::Command::cargo_bin("codex")?;
|
|
|
|
|
cmd.env("CODEX_HOME", codex_home);
|
|
|
|
|
Ok(cmd)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_and_remove_server_updates_global_config() -> Result<()> {
|
2025-09-14 21:30:56 -07:00
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args(["mcp", "add", "docs", "--", "echo", "hello"])
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("Added global MCP server 'docs'."));
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
2025-09-14 21:30:56 -07:00
|
|
|
assert_eq!(servers.len(), 1);
|
|
|
|
|
let docs = servers.get("docs").expect("server should exist");
|
2025-09-26 18:24:01 -07:00
|
|
|
match &docs.transport {
|
2025-10-16 21:24:43 -07:00
|
|
|
McpServerTransportConfig::Stdio {
|
|
|
|
|
command,
|
|
|
|
|
args,
|
|
|
|
|
env,
|
|
|
|
|
env_vars,
|
|
|
|
|
cwd,
|
|
|
|
|
} => {
|
2025-09-26 18:24:01 -07:00
|
|
|
assert_eq!(command, "echo");
|
|
|
|
|
assert_eq!(args, &vec!["hello".to_string()]);
|
|
|
|
|
assert!(env.is_none());
|
2025-10-16 21:24:43 -07:00
|
|
|
assert!(env_vars.is_empty());
|
|
|
|
|
assert!(cwd.is_none());
|
2025-09-26 18:24:01 -07:00
|
|
|
}
|
|
|
|
|
other => panic!("unexpected transport: {other:?}"),
|
|
|
|
|
}
|
2025-10-08 13:24:51 -07:00
|
|
|
assert!(docs.enabled);
|
2025-09-14 21:30:56 -07:00
|
|
|
|
|
|
|
|
let mut remove_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
remove_cmd
|
|
|
|
|
.args(["mcp", "remove", "docs"])
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("Removed global MCP server 'docs'."));
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
2025-09-14 21:30:56 -07:00
|
|
|
assert!(servers.is_empty());
|
|
|
|
|
|
|
|
|
|
let mut remove_again_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
remove_again_cmd
|
|
|
|
|
.args(["mcp", "remove", "docs"])
|
|
|
|
|
.assert()
|
|
|
|
|
.success()
|
|
|
|
|
.stdout(contains("No MCP server named 'docs' found."));
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
2025-09-14 21:30:56 -07:00
|
|
|
assert!(servers.is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_with_env_preserves_key_order_and_values() -> Result<()> {
|
2025-09-14 21:30:56 -07:00
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args([
|
|
|
|
|
"mcp",
|
|
|
|
|
"add",
|
|
|
|
|
"envy",
|
|
|
|
|
"--env",
|
|
|
|
|
"FOO=bar",
|
|
|
|
|
"--env",
|
|
|
|
|
"ALPHA=beta",
|
|
|
|
|
"--",
|
|
|
|
|
"python",
|
|
|
|
|
"server.py",
|
|
|
|
|
])
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
2025-10-03 13:02:26 -07:00
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
2025-09-14 21:30:56 -07:00
|
|
|
let envy = servers.get("envy").expect("server should exist");
|
2025-09-26 18:24:01 -07:00
|
|
|
let env = match &envy.transport {
|
|
|
|
|
McpServerTransportConfig::Stdio { env: Some(env), .. } => env,
|
|
|
|
|
other => panic!("unexpected transport: {other:?}"),
|
|
|
|
|
};
|
2025-09-14 21:30:56 -07:00
|
|
|
|
|
|
|
|
assert_eq!(env.len(), 2);
|
|
|
|
|
assert_eq!(env.get("FOO"), Some(&"bar".to_string()));
|
|
|
|
|
assert_eq!(env.get("ALPHA"), Some(&"beta".to_string()));
|
2025-10-08 13:24:51 -07:00
|
|
|
assert!(envy.enabled);
|
2025-09-14 21:30:56 -07:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-10-07 20:21:37 -07:00
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_streamable_http_without_manual_token() -> Result<()> {
|
|
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args(["mcp", "add", "github", "--url", "https://example.com/mcp"])
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
|
|
|
let github = servers.get("github").expect("github server should exist");
|
|
|
|
|
match &github.transport {
|
|
|
|
|
McpServerTransportConfig::StreamableHttp {
|
|
|
|
|
url,
|
|
|
|
|
bearer_token_env_var,
|
2025-10-16 20:15:47 -07:00
|
|
|
http_headers,
|
|
|
|
|
env_http_headers,
|
2025-10-07 20:21:37 -07:00
|
|
|
} => {
|
|
|
|
|
assert_eq!(url, "https://example.com/mcp");
|
|
|
|
|
assert!(bearer_token_env_var.is_none());
|
2025-10-16 20:15:47 -07:00
|
|
|
assert!(http_headers.is_none());
|
|
|
|
|
assert!(env_http_headers.is_none());
|
2025-10-07 20:21:37 -07:00
|
|
|
}
|
|
|
|
|
other => panic!("unexpected transport: {other:?}"),
|
|
|
|
|
}
|
2025-10-08 13:24:51 -07:00
|
|
|
assert!(github.enabled);
|
2025-10-07 20:21:37 -07:00
|
|
|
|
|
|
|
|
assert!(!codex_home.path().join(".credentials.json").exists());
|
|
|
|
|
assert!(!codex_home.path().join(".env").exists());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_streamable_http_with_custom_env_var() -> Result<()> {
|
|
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args([
|
|
|
|
|
"mcp",
|
|
|
|
|
"add",
|
|
|
|
|
"issues",
|
|
|
|
|
"--url",
|
|
|
|
|
"https://example.com/issues",
|
|
|
|
|
"--bearer-token-env-var",
|
|
|
|
|
"GITHUB_TOKEN",
|
|
|
|
|
])
|
|
|
|
|
.assert()
|
|
|
|
|
.success();
|
|
|
|
|
|
|
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
|
|
|
let issues = servers.get("issues").expect("issues server should exist");
|
|
|
|
|
match &issues.transport {
|
|
|
|
|
McpServerTransportConfig::StreamableHttp {
|
|
|
|
|
url,
|
|
|
|
|
bearer_token_env_var,
|
2025-10-16 20:15:47 -07:00
|
|
|
http_headers,
|
|
|
|
|
env_http_headers,
|
2025-10-07 20:21:37 -07:00
|
|
|
} => {
|
|
|
|
|
assert_eq!(url, "https://example.com/issues");
|
|
|
|
|
assert_eq!(bearer_token_env_var.as_deref(), Some("GITHUB_TOKEN"));
|
2025-10-16 20:15:47 -07:00
|
|
|
assert!(http_headers.is_none());
|
|
|
|
|
assert!(env_http_headers.is_none());
|
2025-10-07 20:21:37 -07:00
|
|
|
}
|
|
|
|
|
other => panic!("unexpected transport: {other:?}"),
|
|
|
|
|
}
|
2025-10-08 13:24:51 -07:00
|
|
|
assert!(issues.enabled);
|
2025-10-07 20:21:37 -07:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_streamable_http_rejects_removed_flag() -> Result<()> {
|
|
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args([
|
|
|
|
|
"mcp",
|
|
|
|
|
"add",
|
|
|
|
|
"github",
|
|
|
|
|
"--url",
|
|
|
|
|
"https://example.com/mcp",
|
|
|
|
|
"--with-bearer-token",
|
|
|
|
|
])
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stderr(contains("--with-bearer-token"));
|
|
|
|
|
|
|
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
|
|
|
assert!(servers.is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn add_cant_add_command_and_url() -> Result<()> {
|
|
|
|
|
let codex_home = TempDir::new()?;
|
|
|
|
|
|
|
|
|
|
let mut add_cmd = codex_command(codex_home.path())?;
|
|
|
|
|
add_cmd
|
|
|
|
|
.args([
|
|
|
|
|
"mcp",
|
|
|
|
|
"add",
|
|
|
|
|
"github",
|
|
|
|
|
"--url",
|
|
|
|
|
"https://example.com/mcp",
|
|
|
|
|
"--command",
|
|
|
|
|
"--",
|
|
|
|
|
"echo",
|
|
|
|
|
"hello",
|
|
|
|
|
])
|
|
|
|
|
.assert()
|
|
|
|
|
.failure()
|
|
|
|
|
.stderr(contains("unexpected argument '--command' found"));
|
|
|
|
|
|
|
|
|
|
let servers = load_global_mcp_servers(codex_home.path()).await?;
|
|
|
|
|
assert!(servers.is_empty());
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|