{"record":{"id":"ac71d01ddba32dfd","repo":"Hmbown/CodeWhale","slug":"tool-tool-name-on-mcp-server-server-name-i","errorCode":null,"errorMessage":"tool '{tool_name}' on MCP server '{server_name}' is blocked by the tool filter","messagePattern":"tool '(.+?)' on MCP server '(.+?)' is blocked by the tool filter","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/mcp/src/lib.rs","lineNumber":388,"sourceCode":"            }\n        }\n        Ok(out)\n    }\n\n    /// Call a tool on a specific server by name.\n    ///\n    /// The server's [`ToolFilter`] is enforced on invocation, not just at\n    /// listing time: a denied (or not-allowed) tool cannot be executed by\n    /// addressing the server directly, whether by bare or qualified name.\n    pub fn call_tool(&self, server_name: &str, tool_name: &str, arguments: Value) -> Result<Value> {\n        let client = self\n            .clients\n            .get(server_name)\n            .with_context(|| format!(\"MCP server '{server_name}' not available\"))?;\n        if let Some((_, filter)) = self.configs.get(server_name)\n            && !allowed_by_filter(tool_name, filter)\n        {\n            bail!(\"tool '{tool_name}' on MCP server '{server_name}' is blocked by the tool filter\");\n        }\n        client.call_tool(tool_name, arguments)\n    }\n\n    /// Call a tool using its fully qualified name (e.g., `mcp__server__tool`).\n    pub fn call_qualified_tool(\n        &self,\n        qualified_tool_name: &str,\n        arguments: Value,\n    ) -> Result<Value> {\n        let parsed = parse_qualified_tool_name(qualified_tool_name)\n            .with_context(|| format!(\"invalid qualified MCP tool name: {qualified_tool_name}\"));\n\n        // An exact registration is the answer. Whatever the tool returns —\n        // including an error — is returned as-is: falling through to the scan\n        // below on a *call* failure would re-execute the same tool, and for a\n        // file write, a commit, or a paid API call that second invocation is a\n        // second real side effect. Only a failed *lookup* falls through.","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/mcp/src/lib.rs#L370-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet filter = ToolFilter::Allow(vec![\"search_code\".into()]);\nregistry.register_server(config, filter, client)?;\nlet result = registry.call_tool(\"github\", \"create_issue\", args)?; // blocked\n\n// after\nlet filter = ToolFilter::Allow(vec![\"search_code\".into(), \"create_issue\".into()]);\nlet result = registry.call_tool(\"github\", \"create_issue\", args)?;","handlingStrategy":"validation","validationCode":"// Consult the filtered listing before calling:\nlet permitted: Vec<String> = registry\n    .list_tools()?\n    .into_iter()\n    .map(|t| t.tool_name)\n    .collect();\nif !permitted.contains(&\"create_issue\".to_string()) {\n    bail!(\"tool not permitted by filter; update the ToolFilter first\");\n}","typeGuard":null,"tryCatchPattern":"match registry.call_tool(server, tool, args) {\n    Ok(v) => Ok(v),\n    Err(err) if err.to_string().contains(\"blocked by the tool filter\") => {\n        Err(anyhow!(\"'{tool}' is filtered on '{server}'; adjust the ToolFilter config\"))\n    }\n    Err(err) => Err(err),\n}","preventionTips":["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"],"tags":["mcp","tool-filter","authorization","allowlist","denylist"],"backgroundTag":"permission-denied","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}