pub mod admin;
pub mod adr;
pub mod adr_rank;
pub mod budget;
pub mod dependency;
pub mod enums;
pub mod graph;
pub mod inspect;
pub mod navigation;
pub mod render;
pub mod schema;
pub mod search;
pub(crate) mod source_lines;
use rmcp::model::{MetaObject, Tool};
pub use crate::session::{AlwaysLoad, ToolProfile};
pub const CORE_TOOLS: &[&str] = &[
"search_symbols",
"inspect_symbol",
"find_references",
"get_file_outline",
"get_enclosing_scope",
"calculate_blast_radius",
];
pub(crate) fn accepted_tool_definitions() -> Vec<Tool> {
let mut tools = Vec::with_capacity(19);
tools.extend(admin::tool_definitions());
tools.extend(navigation::tool_definitions());
tools.extend(graph::tool_definitions());
tools.extend(adr::tool_definitions());
tools.extend(dependency::tool_definitions());
tools.extend(search::tool_definitions());
tools.extend(inspect::tool_definitions());
tools
}
pub fn all_tool_definitions() -> Vec<Tool> {
let mut tools = accepted_tool_definitions();
for tool in &mut tools {
let mut schema = (*tool.input_schema).clone();
schema::publish_schema(&tool.name, &mut schema);
tool.input_schema = std::sync::Arc::new(schema);
}
tools
}
pub fn tool_definitions_for(profile: ToolProfile, always_load: AlwaysLoad) -> Vec<Tool> {
let mut tools = all_tool_definitions();
if profile == ToolProfile::Core {
tools.retain(|t| CORE_TOOLS.contains(&&*t.name));
}
if always_load == AlwaysLoad::Core {
for tool in tools.iter_mut().filter(|t| CORE_TOOLS.contains(&&*t.name)) {
let mut meta = serde_json::Map::new();
meta.insert("anthropic/alwaysLoad".into(), serde_json::Value::Bool(true));
tool.meta = Some(MetaObject(meta));
}
}
tools
}
pub fn cli_hint(see_also: &str) -> String {
let name: String = see_also
.chars()
.take_while(|c| c.is_ascii_lowercase() || *c == '_')
.collect();
let is_extended = all_tool_definitions().iter().any(|t| t.name == name)
&& !CORE_TOOLS.contains(&name.as_str());
if is_extended {
format!("{see_also} (CLI: scryer {})", name.replace('_', "-"))
} else {
see_also.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cli_hints_name_the_cli_form_of_extended_tools_only() {
assert_eq!(
cli_hint("trace_call_hierarchy(symbol: \"x\") for all 9 callers"),
"trace_call_hierarchy(symbol: \"x\") for all 9 callers (CLI: scryer trace-call-hierarchy)"
);
assert_eq!(
cli_hint("query_adrs for the full text"),
"query_adrs for the full text (CLI: scryer query-adrs)"
);
assert_eq!(
cli_hint("find_references(symbol: \"x\")"),
"find_references(symbol: \"x\")"
);
assert_eq!(
cli_hint("rest of body: lines 3-9"),
"rest of body: lines 3-9"
);
}
#[test]
fn the_core_tools_all_exist() {
let names: Vec<String> = all_tool_definitions()
.iter()
.map(|t| t.name.to_string())
.collect();
for core in CORE_TOOLS {
assert!(names.iter().any(|n| n == core), "{core} is not a tool");
}
}
}