Hmbown/CodeWhale · error

qualified MCP tool name

Error message

qualified MCP tool name '{qualified_tool_name}' is ambiguous within server '{server_name}'

What it means

Raised by `resolve_original_tool_name` (crates/mcp/src/lib.rs) during `call_qualified_tool`: more than one tool within a single server's tool list qualifies to the same qualified name `mcp__<server>__<tool>`, so the library cannot tell which underlying tool the caller meant. It refuses to guess and returns the ambiguity error.

Solutions

  1. Fix or configure the MCP server so its tool names sanitize to distinct components.
  2. List the server's tools (`list_tools`) to see the conflicting names, then call the intended one by its unique unqualified name.
  3. Filter tools (ToolFilter) so only one of the colliding tools is visible, making resolution unambiguous.
  4. If a server upgrade caused it, pin or roll back the server version until names are unique again.

Example fix

// before: server exposes 'run.tests' and 'run_tests' -> both qualify as mcp__srv__run_tests
call_qualified_tool("srv", "mcp__srv__run_tests", input)
// after: distinct tool names on the server
call_qualified_tool("srv", "mcp__srv__run_tests", input) // with 'run.tests' renamed server-side
Defensive patterns

Strategy: validation

Validate before calling

// Rust: ensure the qualified name is unique within the server before calling
let target = "mcp__srv__run_tests";
let matches: Vec<_> = manager.list_tools()?.into_iter()
    .filter(|t| t.server == "srv" && qualify_tool_name("srv", &t.tool_name) == target)
    .collect();
anyhow::ensure!(matches.len() == 1, "ambiguous tool: {}", target);

Type guard

fn resolves_uniquely(server: &str, qualified: &str, tools: &[McpToolDescriptor]) -> bool {
    tools.iter().filter(|t| qualify_tool_name(server, &t.tool_name) == qualified).count() == 1
}

Try / catch

match manager.call_qualified_tool(server, qualified, input) {
    Err(e) if e.to_string().contains("is ambiguous within server") => {
        // list tools, pick the exact unqualified name, retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling `call_qualified_tool` when one server exposes two tools whose names sanitize to the same component (e.g. 'run.tests' and 'run_tests'), making `qualify_tool_name` match multiple entries in the filtered tool list.

Common situations: An MCP server publishes tools with names differing only by punctuation/case after sanitization; a server upgrade renames tools into a collision; the caller hand-built the qualified string from a stale tool listing.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/378c7d414c211e6a. Report an issue: GitHub.

Appendix: source

Thrown at crates/mcp/src/lib.rs:312

        server_name: &str,
        tool_segment: &str,
        qualified_tool_name: &str,
    ) -> Result<String> {
        let Some(client) = self.clients.get(server_name) else {
            return Ok(tool_segment.to_string());
        };
        let Ok(tools) = client.list_tools() else {
            return Ok(tool_segment.to_string());
        };
        let filter = self.configs.get(server_name).map(|(_, filter)| filter);
        let mut matches = tools.iter().filter(|tool| {
            filter.is_none_or(|filter| allowed_by_filter(&tool.tool_name, filter))
                && qualify_tool_name(server_name, &tool.tool_name) == qualified_tool_name
        });
        match (matches.next(), matches.next()) {
            (Some(tool), None) => Ok(tool.tool_name.clone()),
            (None, _) => Ok(tool_segment.to_string()),
            (Some(_), Some(_)) => bail!(
                "qualified MCP tool name '{qualified_tool_name}' is ambiguous within server '{server_name}'"
            ),
        }
    }

    /// Start all registered servers, emitting status updates via the callback.
    ///
    /// Returns a summary of which servers are ready, failed, or cancelled.
    pub fn start_all<F>(&self, mut emit: F) -> McpStartupCompleteEvent
    where
        F: FnMut(McpStartupUpdateEvent),
    {
        let mut ready = Vec::new();
        let mut failed = Vec::new();
        let mut cancelled = Vec::new();
        for (server_name, (cfg, _)) in &self.configs {
            if !cfg.enabled {
                emit(McpStartupUpdateEvent {

View on GitHub (pinned to 73e0f67d83)