Skip to main content

lean_ctx/tool_defs/
mod.rs

1use std::sync::Arc;
2
3use rmcp::model::Tool;
4use serde_json::{Map, Value};
5
6mod granular;
7pub use granular::{granular_tool_defs, list_all_tool_defs, unified_tool_defs};
8
9pub fn tool_def(name: &'static str, description: &'static str, schema_value: Value) -> Tool {
10    let schema: Map<String, Value> = match schema_value {
11        Value::Object(map) => map,
12        _ => Map::new(),
13    };
14    Tool::new(name, description, Arc::new(schema))
15}
16
17pub const CORE_TOOL_NAMES: &[&str] = &[
18    "ctx_read",
19    "ctx_search",
20    "ctx_shell",
21    "shell",
22    "ctx_tree",
23    "ctx_edit",
24    "ctx_session",
25    "ctx_knowledge",
26    "ctx_overview",
27    "ctx_graph",
28    "ctx_call",
29    "ctx_provider",
30    "ctx_expand",
31];
32
33pub fn core_tool_names() -> &'static [&'static str] {
34    CORE_TOOL_NAMES
35}
36
37pub fn lazy_tool_defs() -> Vec<Tool> {
38    let all = granular_tool_defs();
39    all.into_iter()
40        .filter(|t| CORE_TOOL_NAMES.contains(&t.name.as_ref()))
41        .collect()
42}
43
44pub fn discover_tools(query: &str) -> String {
45    let all = list_all_tool_defs();
46    let query_lower = query.to_lowercase();
47    let matches: Vec<(&str, &str)> = all
48        .iter()
49        .filter(|(name, desc, _)| {
50            name.to_lowercase().contains(&query_lower) || desc.to_lowercase().contains(&query_lower)
51        })
52        .map(|(name, desc, _)| (*name, *desc))
53        .collect();
54
55    if matches.is_empty() {
56        return format!("No tools found matching '{query}'. Try broader terms like: graph, cost, session, search, compress, agent, workflow, gain.");
57    }
58
59    let mut out = format!("{} tools matching '{query}':\n", matches.len());
60    for (name, desc) in &matches {
61        let short = if desc.len() > 80 {
62            &desc[..desc.floor_char_boundary(80)]
63        } else {
64            desc
65        };
66        out.push_str(&format!("  {name} — {short}\n"));
67    }
68    out.push_str(
69        "\nIf your MCP client registers tools only once at startup (static tools/list), \
70use ctx_call (available in lazy mode) to invoke discovered tools:\n\
71  ctx_call {\"name\":\"ctx_graph\",\"arguments\":{\"action\":\"status\"}}\n",
72    );
73    out
74}
75
76pub fn is_full_mode() -> bool {
77    std::env::var("LEAN_CTX_FULL_TOOLS").is_ok_and(|v| v != "0" && !v.eq_ignore_ascii_case("false"))
78        || std::env::var("LEAN_CTX_LAZY_TOOLS")
79            .is_ok_and(|v| v == "0" || v.eq_ignore_ascii_case("false"))
80}