Hmbown/CodeWhale · error · anyhow::Error

Unknown MCP tool name

Error message

Unknown MCP tool name: {name}

What it means

authorize_call() validates a proposed MCP tool name against the caller-supplied rules; if tool_matches_any_rule(rules, name) matches, the name is on a deny list and the call is rejected with "Unknown MCP tool name". The message is intentionally opaque so untrusted input cannot distinguish a denied tool from a nonexistent one.

Solutions

  1. Call only tool names returned by the server's tools/list for the session.
  2. Check the tool name against the configured deny rules before dispatching.
  3. Fix casing/spelling — matching is rule-based and exact for plain names.
  4. If the tool should be callable, remove it from the deny rules in config.

Example fix

// before
McpConnection::authorize_call(&rules, "shell_exec", &input)?;
// after: use a catalogued name
let name = catalog.resolve("shell", "exec")?; // canonical name
McpConnection::authorize_call(&rules, &name, &input)?;
Defensive patterns

Strategy: validation

Validate before calling

// Only dispatch names present in the session's tool catalog
let allowed: std::collections::HashSet<&str> = catalog.tools.iter().map(|t| t.name.as_str()).collect();
assert!(allowed.contains(name.as_str()), "tool not in catalog: {name}");

Type guard

fn is_catalogued(catalog: &ToolCatalog, name: &str) -> bool {
    catalog.tools.iter().any(|t| t.name == name)
}

Try / catch

match McpConnection::authorize_call(&rules, &name, &input) {
    Err(e) if e.to_string().contains("Unknown MCP tool name") => {
        eprintln!("tool '{name}' is denied or unknown; pick from tools/list");
    }
    other => other?,
}

Prevention

When it happens

Trigger: A tools/call (or resource/prompt access) whose tool name matches a deny rule in the rules slice passed to authorize_call — including model-supplied tool names that were not in the approved catalog.

Common situations: The model hallucinates or miscases a tool name; config deny-lists a tool that a client still tries to call; a renamed tool's old name is still being called.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/mcp.rs:2929

    pub(crate) fn tool_allowed(&self, name: &str) -> bool {
        !crate::core::engine::tool_catalog::tool_matches_any_rule(&self.disallowed_tools, name)
    }

    fn require_server(&self, server: &str) -> Result<()> {
        anyhow::ensure!(
            self.server_allowed(server),
            "Failed to find MCP server: {server}"
        );
        Ok(())
    }

    pub(crate) fn authorize_call(
        rules: &[String],
        name: &str,
        input: &serde_json::Value,
    ) -> Result<()> {
        anyhow::ensure!(
            !crate::core::engine::tool_catalog::tool_matches_any_rule(rules, name),
            "Unknown MCP tool name: {name}"
        );
        if matches!(
            name,
            "list_mcp_resources"
                | "list_mcp_resource_templates"
                | "mcp_read_resource"
                | "read_mcp_resource"
                | "mcp_get_prompt"
        ) && let Some(server) = input.get("server").and_then(serde_json::Value::as_str)
        {
            anyhow::ensure!(
                !Self::server_denied_by(rules, server),
                "Failed to find MCP server: {server}"
            );
        }
        Ok(())

View on GitHub (pinned to 73e0f67d83)