Hmbown/CodeWhale · error · anyhow::Error
MCP server '{}' collides with already-registered server '{ex
Error message
MCP server '{}' collides with already-registered server '{existing}': both qualify tools as 'mcp__{}__*' What it means
McpRegistry::register_server() refuses a new registration whose sanitized name folds to the same qualified-tool prefix as an already-registered server. sanitize_component maps '-', '.', and case to '_', so 'my-server', 'my_server', and 'My.Server' all produce mcp__my_server__*; registering two of them would let either server answer a qualified name meant for the other. Re-registering the exact same name is allowed (that is how restart works) — only collisions between different names are rejected.
Source
Thrown at crates/mcp/src/lib.rs:232
}
impl McpManager {
/// Register an MCP server with its config, tool filter, and client implementation.
///
/// Fails when the server's name collides with an already-registered server
/// after `sanitize_component` folding. Qualified tool names are built
/// from the sanitized name, so `my-server`, `my_server`, and `My.Server`
/// all produce `mcp__my_server__*`: registering two of them would let
/// either server answer a qualified name meant for the other. Re-registering
/// the same name replaces it, which is how restart works.
pub fn register_server(
&mut self,
config: McpServerConfig,
filter: ToolFilter,
client: Box<dyn McpManagedClient>,
) -> Result<()> {
if let Some(existing) = self.colliding_server_name(&config.name) {
bail!(
"MCP server '{}' collides with already-registered server '{existing}': \
both qualify tools as 'mcp__{}__*'",
config.name,
sanitize_component(&config.name)
);
}
self.clients.insert(config.name.clone(), client);
self.configs.insert(config.name.clone(), (config, filter));
Ok(())
}
/// Returns a registered server whose sanitized name matches `name`'s but
/// which is not `name` itself.
fn colliding_server_name(&self, name: &str) -> Option<&str> {
let sanitized = sanitize_component(name);
self.configs
.keys()
.find(|existing| existing.as_str() != name && sanitize_component(existing) == sanitized)View on GitHub (pinned to 0c42157ee5)
Solutions
- Rename one of the two colliding servers in its McpServerConfig so the sanitized forms differ
- Unregister the stale server first (unregister_server) if it is no longer wanted, then register the new one
- If this is a restart, keep the identical name — exact re-registration replaces cleanly and does not collide
Example fix
// before
registry.register_server(cfg_for("my-server"), filter, client)?; // later...
registry.register_server(cfg_for("my_server"), filter, client)?; // collides
// after: distinct names, or replace by reusing the exact one
registry.unregister_server("my-server")?;
registry.register_server(cfg_for("my_server"), filter, client)?; Defensive patterns
Strategy: validation
Validate before calling
fn sanitize_component(s: &str) -> String {
s.chars().map(|c| if matches!(c, '-' | '.') { '_' } else { c }).collect::<String>().to_ascii_lowercase()
}
// before registering, check the folded name is unique among registered names:
let folded = sanitize_component(&config.name);
assert!(!registered_folded_names.contains(&folded), "name collision on {folded}"); Try / catch
if let Err(err) = registry.register_server(config, filter, client) {
if err.to_string().contains("collides with already-registered server") {
// pick a genuinely different name, not just different punctuation
config.name = format!("{}-2", config.name);
return registry.register_server(config, filter, client);
}
return Err(err);
} Prevention
- Enforce a single naming convention (lowercase, one separator style) for server names at config load
- Deduplicate config entries against their sanitized forms before any registration
- Remember exact-name re-registration is the supported restart path — never rename for restarts
When it happens
Trigger: Calling register_server() with name "my-server" while "my_server" or "My.Server" is already in the registry; loading an MCP config that lists two servers whose names differ only by separators or case; programmatically deriving server names (e.g. from URLs or file paths) that fold to the same sanitized form.
Common situations: Config files maintained by hand where one entry was renamed but the old copy stayed; multiple teams adding servers named after the same product with different punctuation; environments that normalize names differently across restarts.
Related errors
- server '{server_name}' is not registered
- qualified MCP tool name '{qualified_tool_name}' is ambiguous
- MCP server '{name}' already exists in {}. Use `codewhale mcp
- Invalid MCP tool name: {prefixed_name}
- Ambiguous MCP tool name '{prefixed_name}' matches more than
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/79c3fefc5bf77958.
Report an issue: GitHub.