{"record":{"id":"37bb22a2a9576561","repo":"Hmbown/CodeWhale","slug":"qualified-mcp-tool-name-qualified-tool-name-is","errorCode":null,"errorMessage":"qualified MCP tool name '{qualified_tool_name}' is ambiguous across servers: {}","messagePattern":"qualified MCP tool name '(.+?)' is ambiguous across servers: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/mcp/src/lib.rs","lineNumber":454,"sourceCode":"            }\n        }\n        match matches.len() {\n            0 => {}\n            1 => {\n                let (server_name, tool_name) = &matches[0];\n                let client = self\n                    .clients\n                    .get(*server_name)\n                    .with_context(|| format!(\"MCP server '{server_name}' not available\"))?;\n                return client.call_tool(tool_name, arguments);\n            }\n            _ => {\n                matches.sort();\n                let servers: Vec<&str> = matches\n                    .iter()\n                    .map(|(server_name, _)| server_name.as_str())\n                    .collect();\n                bail!(\n                    \"qualified MCP tool name '{qualified_tool_name}' is ambiguous across servers: \\\n                     {}\",\n                    servers.join(\", \")\n                );\n            }\n        }\n\n        let (server_name, tool_name) = parsed?;\n        self.call_tool(&server_name, &tool_name, arguments)\n    }\n\n    /// List all resources from all running servers.\n    pub fn list_resources(&self) -> Result<Vec<McpResourceDescriptor>> {\n        let mut out = Vec::new();\n        for server_name in self.configs.keys() {\n            let Some(client) = self.clients.get(server_name) else {\n                continue;\n            };","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/mcp/src/lib.rs#L436-L472","documentation":"McpRegistry::call_qualified_tool() could not resolve the qualified name against an exact registered server, so it scanned every running server's tools for ones whose qualified name equals the request — and found two or more. Sanitization folds '-', '.', and case into '_', and the mcp__<server>__<tool> join uses '__', so different (server, tool) pairs can flatten to the same string (e.g. server 'a' tool 'b__c' vs server 'a_b' tool 'c' both yield mcp__a__b__c). The message lists the sorted server names that match; dispatch refuses to pick one because iteration order is not a choice.","triggerScenarios":"Passing a qualified name whose server segment does not exactly match a registered server name, forcing the scan path, while two servers expose tools that flatten to that same qualified name; servers whose names contain underscores combined with tools whose names contain '__'; duplicates of the same server registered under near-identical names.","commonSituations":"Hand-built qualified names copied from logs; tool catalogs where generic tool names ('run', 'search') exist on several servers and the caller dropped or mistyped the server segment; name changes after sanitization rules were tightened.","solutions":["Use the exact registered server name in the server segment so the exact-registration path resolves without scanning","Prefer call_tool(server, tool, args) with explicit names when you know the target server","Rename one of the colliding servers or tools so their qualified names no longer flatten to the same string"],"exampleFix":"// before\nlet v = registry.call_qualified_tool(\"mcp__a__b__c\", args)?; // matches a/b__c and a_b/c\n\n// after: name the server exactly\nlet v = registry.call_tool(\"a_b\", \"c\", args)?;","handlingStrategy":"validation","validationCode":"// Prefer the unambiguous direct API when you know the server:\nregistry.call_tool(\"a_b\", \"c\", args)?;\n\n// If you must use qualified names, ensure the server segment exactly matches a registered name\n// and the name was produced by the crate's qualify helper, not hand-built.","typeGuard":"fn is_exact_qualified_name(name: &str, registered: &[String]) -> bool {\n    name.starts_with(\"mcp__\")\n        && name[5..].split_once(\"__\").is_some_and(|(server, _)| {\n            registered.iter().any(|r| r == server)\n        })\n}","tryCatchPattern":"match registry.call_qualified_tool(name, args) {\n    Ok(v) => Ok(v),\n    Err(err) if err.to_string().contains(\"is ambiguous across servers\") => {\n        // pick a server from the listed candidates and call it directly\n        let server = pick_server_from_error(&err)?;\n        registry.call_tool(&server, &tool, args)\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Build qualified names with the crate's formatter; never concatenate by hand","Avoid '__' inside tool or server names so flattening cannot overlap","Route calls through call_tool(server, tool) whenever the target server is known"],"tags":["mcp","qualified-name","ambiguity","routing","sanitization"],"backgroundTag":"ambiguous-identifier","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}