{"record":{"id":"79c3fefc5bf77958","repo":"Hmbown/CodeWhale","slug":"mcp-server-collides-with-already-registered-s","errorCode":null,"errorMessage":"MCP server '{}' collides with already-registered server '{existing}': both qualify tools as 'mcp__{}__*'","messagePattern":"MCP server '(.+?)' collides with already-registered server '(.+?)': both qualify tools as 'mcp__(.+?)__\\*'","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/mcp/src/lib.rs","lineNumber":232,"sourceCode":"}\n\nimpl McpManager {\n    /// Register an MCP server with its config, tool filter, and client implementation.\n    ///\n    /// Fails when the server's name collides with an already-registered server\n    /// after `sanitize_component` folding. Qualified tool names are built\n    /// from the sanitized name, so `my-server`, `my_server`, and `My.Server`\n    /// all produce `mcp__my_server__*`: registering two of them would let\n    /// either server answer a qualified name meant for the other. Re-registering\n    /// the same name replaces it, which is how restart works.\n    pub fn register_server(\n        &mut self,\n        config: McpServerConfig,\n        filter: ToolFilter,\n        client: Box<dyn McpManagedClient>,\n    ) -> Result<()> {\n        if let Some(existing) = self.colliding_server_name(&config.name) {\n            bail!(\n                \"MCP server '{}' collides with already-registered server '{existing}': \\\n                 both qualify tools as 'mcp__{}__*'\",\n                config.name,\n                sanitize_component(&config.name)\n            );\n        }\n        self.clients.insert(config.name.clone(), client);\n        self.configs.insert(config.name.clone(), (config, filter));\n        Ok(())\n    }\n\n    /// Returns a registered server whose sanitized name matches `name`'s but\n    /// which is not `name` itself.\n    fn colliding_server_name(&self, name: &str) -> Option<&str> {\n        let sanitized = sanitize_component(name);\n        self.configs\n            .keys()\n            .find(|existing| existing.as_str() != name && sanitize_component(existing) == sanitized)","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/mcp/src/lib.rs#L214-L250","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nregistry.register_server(cfg_for(\"my-server\"), filter, client)?; // later...\nregistry.register_server(cfg_for(\"my_server\"), filter, client)?; // collides\n\n// after: distinct names, or replace by reusing the exact one\nregistry.unregister_server(\"my-server\")?;\nregistry.register_server(cfg_for(\"my_server\"), filter, client)?;","handlingStrategy":"validation","validationCode":"fn sanitize_component(s: &str) -> String {\n    s.chars().map(|c| if matches!(c, '-' | '.') { '_' } else { c }).collect::<String>().to_ascii_lowercase()\n}\n\n// before registering, check the folded name is unique among registered names:\nlet folded = sanitize_component(&config.name);\nassert!(!registered_folded_names.contains(&folded), \"name collision on {folded}\");","typeGuard":null,"tryCatchPattern":"if let Err(err) = registry.register_server(config, filter, client) {\n    if err.to_string().contains(\"collides with already-registered server\") {\n        // pick a genuinely different name, not just different punctuation\n        config.name = format!(\"{}-2\", config.name);\n        return registry.register_server(config, filter, client);\n    }\n    return Err(err);\n}","preventionTips":["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"],"tags":["mcp","registry","naming","sanitization","duplicate"],"backgroundTag":"name-collision","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}