use std::path::Path; use anyhow::Result; use codex_core::config::load_global_mcp_servers; use codex_core::config::write_global_mcp_servers; use codex_core::config_types::McpServerTransportConfig; use predicates::prelude::PredicateBooleanExt; use predicates::str::contains; use pretty_assertions::assert_eq; use serde_json::Value as JsonValue; use serde_json::json; use tempfile::TempDir; fn codex_command(codex_home: &Path) -> Result { let mut cmd = assert_cmd::Command::cargo_bin("codex")?; cmd.env("CODEX_HOME", codex_home); Ok(cmd) } #[test] fn list_shows_empty_state() -> Result<()> { let codex_home = TempDir::new()?; let mut cmd = codex_command(codex_home.path())?; let output = cmd.args(["mcp", "list"]).output()?; assert!(output.status.success()); let stdout = String::from_utf8(output.stdout)?; assert!(stdout.contains("No MCP servers configured yet.")); Ok(()) } #[tokio::test] async fn list_and_get_render_expected_output() -> Result<()> { let codex_home = TempDir::new()?; let mut add = codex_command(codex_home.path())?; add.args([ "mcp", "add", "docs", "--env", "TOKEN=secret", "--", "docs-server", "--port", "4000", ]) .assert() .success(); let mut servers = load_global_mcp_servers(codex_home.path()).await?; let docs_entry = servers .get_mut("docs") .expect("docs server should exist after add"); match &mut docs_entry.transport { McpServerTransportConfig::Stdio { env_vars, .. } => { *env_vars = vec!["APP_TOKEN".to_string(), "WORKSPACE_ID".to_string()]; } other => panic!("unexpected transport: {other:?}"), } write_global_mcp_servers(codex_home.path(), &servers)?; let mut list_cmd = codex_command(codex_home.path())?; let list_output = list_cmd.args(["mcp", "list"]).output()?; assert!(list_output.status.success()); let stdout = String::from_utf8(list_output.stdout)?; assert!(stdout.contains("Name")); assert!(stdout.contains("docs")); assert!(stdout.contains("docs-server")); assert!(stdout.contains("TOKEN=*****")); assert!(stdout.contains("APP_TOKEN=*****")); assert!(stdout.contains("WORKSPACE_ID=*****")); assert!(stdout.contains("Status")); assert!(stdout.contains("Auth")); assert!(stdout.contains("enabled")); assert!(stdout.contains("Unsupported")); let mut list_json_cmd = codex_command(codex_home.path())?; let json_output = list_json_cmd.args(["mcp", "list", "--json"]).output()?; assert!(json_output.status.success()); let stdout = String::from_utf8(json_output.stdout)?; let parsed: JsonValue = serde_json::from_str(&stdout)?; assert_eq!( parsed, json!([ { "name": "docs", "enabled": true, "transport": { "type": "stdio", "command": "docs-server", "args": [ "--port", "4000" ], "env": { "TOKEN": "secret" }, "env_vars": [ "APP_TOKEN", "WORKSPACE_ID" ], "cwd": null }, "startup_timeout_sec": null, "tool_timeout_sec": null, "auth_status": "unsupported" } ] ) ); let mut get_cmd = codex_command(codex_home.path())?; let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?; assert!(get_output.status.success()); let stdout = String::from_utf8(get_output.stdout)?; assert!(stdout.contains("docs")); assert!(stdout.contains("transport: stdio")); assert!(stdout.contains("command: docs-server")); assert!(stdout.contains("args: --port 4000")); assert!(stdout.contains("env: TOKEN=*****")); assert!(stdout.contains("APP_TOKEN=*****")); assert!(stdout.contains("WORKSPACE_ID=*****")); assert!(stdout.contains("enabled: true")); assert!(stdout.contains("remove: codex mcp remove docs")); let mut get_json_cmd = codex_command(codex_home.path())?; get_json_cmd .args(["mcp", "get", "docs", "--json"]) .assert() .success() .stdout(contains("\"name\": \"docs\"").and(contains("\"enabled\": true"))); Ok(()) } #[tokio::test] async fn get_disabled_server_shows_single_line() -> Result<()> { let codex_home = TempDir::new()?; let mut add = codex_command(codex_home.path())?; add.args(["mcp", "add", "docs", "--", "docs-server"]) .assert() .success(); let mut servers = load_global_mcp_servers(codex_home.path()).await?; let docs = servers .get_mut("docs") .expect("docs server should exist after add"); docs.enabled = false; write_global_mcp_servers(codex_home.path(), &servers)?; let mut get_cmd = codex_command(codex_home.path())?; let get_output = get_cmd.args(["mcp", "get", "docs"]).output()?; assert!(get_output.status.success()); let stdout = String::from_utf8(get_output.stdout)?; assert_eq!(stdout.trim_end(), "docs (disabled)"); Ok(()) }