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

  1. Rename one of the two colliding servers in its McpServerConfig so the sanitized forms differ
  2. Unregister the stale server first (unregister_server) if it is no longer wanted, then register the new one
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/79c3fefc5bf77958. Report an issue: GitHub.