Hmbown/CodeWhale · error · anyhow::Error
tool '{tool_name}' on MCP server '{server_name}' is blocked
Error message
tool '{tool_name}' on MCP server '{server_name}' is blocked by the tool filter What it means
McpRegistry::call_tool() enforces the server's ToolFilter at invocation time, not just at listing time: if the filter denies the tool (deny-list hit, or allow-list that does not include it), calling it by bare or qualified name is refused. This makes the listing-time filter authoritative for execution too, so a tool hidden from listings cannot be reached by guessing its name.
Source
Thrown at crates/mcp/src/lib.rs:388
}
}
Ok(out)
}
/// Call a tool on a specific server by name.
///
/// The server's [`ToolFilter`] is enforced on invocation, not just at
/// listing time: a denied (or not-allowed) tool cannot be executed by
/// addressing the server directly, whether by bare or qualified name.
pub fn call_tool(&self, server_name: &str, tool_name: &str, arguments: Value) -> Result<Value> {
let client = self
.clients
.get(server_name)
.with_context(|| format!("MCP server '{server_name}' not available"))?;
if let Some((_, filter)) = self.configs.get(server_name)
&& !allowed_by_filter(tool_name, filter)
{
bail!("tool '{tool_name}' on MCP server '{server_name}' is blocked by the tool filter");
}
client.call_tool(tool_name, arguments)
}
/// Call a tool using its fully qualified name (e.g., `mcp__server__tool`).
pub fn call_qualified_tool(
&self,
qualified_tool_name: &str,
arguments: Value,
) -> Result<Value> {
let parsed = parse_qualified_tool_name(qualified_tool_name)
.with_context(|| format!("invalid qualified MCP tool name: {qualified_tool_name}"));
// An exact registration is the answer. Whatever the tool returns —
// including an error — is returned as-is: falling through to the scan
// below on a *call* failure would re-execute the same tool, and for a
// file write, a commit, or a paid API call that second invocation is a
// second real side effect. Only a failed *lookup* falls through.View on GitHub (pinned to 0c42157ee5)
Solutions
- Update the ToolFilter for that server: remove the tool from the deny list, or add it to the allow list
- Use list_tools() on the registry to see exactly which tools the filter currently permits
- Verify you are targeting the right server — filters are per-server, and the same tool name may be allowed elsewhere
Example fix
// before
let filter = ToolFilter::Allow(vec!["search_code".into()]);
registry.register_server(config, filter, client)?;
let result = registry.call_tool("github", "create_issue", args)?; // blocked
// after
let filter = ToolFilter::Allow(vec!["search_code".into(), "create_issue".into()]);
let result = registry.call_tool("github", "create_issue", args)?; Defensive patterns
Strategy: validation
Validate before calling
// Consult the filtered listing before calling:
let permitted: Vec<String> = registry
.list_tools()?
.into_iter()
.map(|t| t.tool_name)
.collect();
if !permitted.contains(&"create_issue".to_string()) {
bail!("tool not permitted by filter; update the ToolFilter first");
} Try / catch
match registry.call_tool(server, tool, args) {
Ok(v) => Ok(v),
Err(err) if err.to_string().contains("blocked by the tool filter") => {
Err(anyhow!("'{tool}' is filtered on '{server}'; adjust the ToolFilter config"))
}
Err(err) => Err(err),
} Prevention
- Derive allow-lists from the server's advertised tool list at config time instead of hardcoding
- Re-generate filters when upgrading a server whose tools changed
- Surface filtered-vs-available tools in operator-facing config validation
When it happens
Trigger: Calling call_tool("github", "create_issue", ...) while the github server's filter has "create_issue" in its deny list; calling a tool by its qualified name (mcp__github__create_issue) hoping to bypass the filter; an allow-list configured before the server added new tools, so every newly added tool is blocked until the allow-list is updated.
Common situations: Least-privilege setups that allow read-only tools and later need a write tool; deny-lists added after an incident; filters written against old tool names that the server renamed in a new version.
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 tool '{tool_name}' is disabled for server '{server_name}
- MCP server '{name}' already has bearer/static Authorization
- app-server auth token cannot be empty
- external credential access is disabled for {}
- MCP server '{}' collides with already-registered server '{ex
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/ac71d01ddba32dfd.
Report an issue: GitHub.