use crate::types::ToolDefinition;
pub fn assemble_tool_pool(
built_in: &[ToolDefinition],
mcp_tools: &[ToolDefinition],
) -> Vec<ToolDefinition> {
let mut builtin_sorted = built_in.to_vec();
builtin_sorted.sort_by(|a, b| a.name.cmp(&b.name));
let mut mcp_sorted = mcp_tools.to_vec();
mcp_sorted.sort_by(|a, b| a.name.cmp(&b.name));
let mut combined: Vec<ToolDefinition> = builtin_sorted;
combined.extend(mcp_sorted);
let mut seen = std::collections::HashSet::new();
combined.retain(|t| seen.insert(t.name.clone()));
combined
}
pub fn filter_tools_by_deny_rules(
tools: &[ToolDefinition],
deny_rules: &[String],
) -> Vec<ToolDefinition> {
tools
.iter()
.filter(|tool| {
!deny_rules.iter().any(|rule| {
if &tool.name == rule {
return true;
}
if rule.ends_with("__") && tool.name.starts_with(rule.as_str()) {
return true;
}
if rule.ends_with('_') && tool.name.starts_with(rule.as_str()) {
return true;
}
if rule == "*" {
return true;
}
false
})
})
.cloned()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_tool(name: &str) -> ToolDefinition {
ToolDefinition {
name: name.to_string(),
description: format!("Tool {}", name),
input_schema: ToolDefinition::default().input_schema,
..Default::default()
}
}
#[test]
fn test_assemble_tool_pool_sorts_builtins() {
let built_in = vec![make_tool("Zebra"), make_tool("Alpha"), make_tool("Beta")];
let result = assemble_tool_pool(&built_in, &[]);
assert_eq!(result.iter().map(|t| &t.name).collect::<Vec<_>>(), &["Alpha", "Beta", "Zebra"]);
}
#[test]
fn test_assemble_tool_pool_sorts_mcp_tools() {
let mcp = vec![make_tool("mcp__server_Z"), make_tool("mcp__server_A")];
let result = assemble_tool_pool(&[], &mcp);
assert_eq!(
result.iter().map(|t| &t.name).collect::<Vec<_>>(),
&["mcp__server_A", "mcp__server_Z"]
);
}
#[test]
fn test_assemble_tool_pool_builtins_first() {
let built_in = vec![make_tool("B")];
let mcp = vec![make_tool("A")];
let result = assemble_tool_pool(&built_in, &mcp);
assert_eq!(result.iter().map(|t| &t.name).collect::<Vec<_>>(), &["B", "A"]);
}
#[test]
fn test_assemble_tool_pool_dedup_builtins_win() {
let built_in = vec![make_tool("Read")];
let mcp = vec![make_tool("Read")];
let result = assemble_tool_pool(&built_in, &mcp);
assert_eq!(result.len(), 1);
assert_eq!(&result[0].name, "Read");
}
#[test]
fn test_filter_tools_by_deny_rules_exact() {
let tools = vec![make_tool("Bash"), make_tool("Read")];
let rules = vec!["Bash".to_string()];
let result = filter_tools_by_deny_rules(&tools, &rules);
assert_eq!(result.iter().map(|t| &t.name).collect::<Vec<_>>(), &["Read"]);
}
#[test]
fn test_filter_tools_by_deny_rules_mcp_server_prefix() {
let tools = vec![
make_tool("mcp__fs_read"),
make_tool("mcp__fs_write"),
make_tool("mcp__git_status"),
];
let rules = vec!["mcp__fs_".to_string()];
let result = filter_tools_by_deny_rules(&tools, &rules);
assert_eq!(result.iter().map(|t| &t.name).collect::<Vec<_>>(), &["mcp__git_status"]);
}
#[test]
fn test_filter_tools_by_deny_rules_wildcard() {
let tools = vec![make_tool("Bash"), make_tool("Read")];
let rules = vec!["*".to_string()];
let result = filter_tools_by_deny_rules(&tools, &rules);
assert!(result.is_empty());
}
#[test]
fn test_assemble_full_flow() {
let built_in = vec![make_tool("Zebra"), make_tool("Alpha")];
let mcp = vec![
make_tool("mcp__z"),
make_tool("Zebra"), make_tool("mcp__a"),
];
let result = assemble_tool_pool(&built_in, &mcp);
let names: Vec<_> = result.iter().map(|t| &t.name).collect();
assert_eq!(names, &["Alpha", "Zebra", "mcp__a", "mcp__z"]);
}
}