Support package-style MCP server names (#41700)

## What changed

- Allow MCP server names to contain `:`, `@`, `/`, and `.`, enabling names such as `npm:@modelcontextprotocol/server-sequential.thinking`.
- Preserve these names across `mcp add`, `get`, `list`, and `remove`, runtime tool namespaces, and OAuth credential lookup.
- Quote non-bare server names in generated `config.toml` recovery hints and keep similarly escaped OAuth credential names isolated.

## Testing

- Add CLI and runtime round-trip coverage for npm-style names.
- Add snapshot coverage for quoted recovery hints and a regression test for OAuth credential-name collisions.

GitOrigin-RevId: 97e9353cdbc544db4eb2377ddbfa1d2d539b1e04
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-30 16:45:34 +00:00
1 parent 88f776588f
commit 94cbbddafc
12 files changed
+159 -13

No files matched your search

+47
View File
@@ -73,6 +73,53 @@ async fn add_and_remove_server_updates_global_config() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn npm_server_names_round_trip_through_cli() -> Result<()> {
let codex_home = TempDir::new()?;
let name = "npm:@modelcontextprotocol/server-sequential.thinking";
codex_command(codex_home.path())?
.args(["mcp", "add", name, "--", "echo", "hello"])
.assert()
.success();
let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?;
assert!(config.contains(&format!("[mcp_servers.\"{name}\"]")));
let servers = load_global_mcp_servers(codex_home.path()).await?;
assert_eq!(
servers.keys().map(String::as_str).collect::<Vec<_>>(),
vec![name]
);
let output = codex_command(codex_home.path())?
.args(["mcp", "get", name, "--json"])
.assert()
.success()
.get_output()
.stdout
.clone();
let server: serde_json::Value = serde_json::from_slice(&output)?;
assert_eq!(server["name"], name);
let output = codex_command(codex_home.path())?
.args(["mcp", "list", "--json"])
.assert()
.success()
.get_output()
.stdout
.clone();
let listed: Vec<serde_json::Value> = serde_json::from_slice(&output)?;
assert_eq!(listed.len(), 1);
assert_eq!(listed[0]["name"], name);
codex_command(codex_home.path())?
.args(["mcp", "remove", name])
.assert()
.success();
assert!(load_global_mcp_servers(codex_home.path()).await?.is_empty());
Ok(())
}
#[tokio::test]
async fn add_and_login_discover_oauth_through_configured_http_proxy() -> Result<()> {
let codex_home = TempDir::new()?;