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
- Call only tool names returned by the server's tools/list for the session.
- Check the tool name against the configured deny rules before dispatching.
- Fix casing/spelling — matching is rule-based and exact for plain names.
- 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
- Always source tool names from the current tools/list response.
- Keep the deny rules and the advertised catalog in sync after config edits.
- Normalize casing of tool names before calling.
- Remove old tool names from callers after a server tool rename.
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
- MCP operation on plugin server
- Refusing to MCP server ' ' from plugin bundle ` `: .
- A managed, project or plugin connector already uses this…
- Absolute path should not warn
- active plugin registry is missing its pre-dotenv…
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)