Skip to main content

lean_ctx/tools/registered/
ctx_callgraph.rs

1use crate::core::ocla::cache_types::{CacheKeyBuilder, SearchQueryKey};
2use rmcp::ErrorData;
3use rmcp::model::Tool;
4use serde_json::{Map, Value, json};
5
6use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_int, get_str};
7use crate::tool_defs::tool_def;
8
9pub struct CtxCallgraphTool;
10
11impl McpTool for CtxCallgraphTool {
12    fn name(&self) -> &'static str {
13        "ctx_callgraph"
14    }
15
16    fn tool_def(&self) -> Tool {
17        tool_def(
18            "ctx_callgraph",
19            "Callers/callees for one symbol (function call edges, not const/var refs).\n\
20             action=callers|callees symbol='fn' → every call site with file:line.\n\
21             action=trace from→to finds the path between two symbols (depth=N).\n\
22             For end-to-end flow understanding use ctx_compose FIRST.",
23            json!({
24                "type": "object",
25                "properties": {
26                    "action": {
27                        "type": "string",
28                        "enum": ["callers", "callees", "trace", "risk"]
29                    },
30                    "symbol": { "type": "string" },
31                    "file": { "type": "string", "description": "Scope results to file" },
32                    "depth": { "type": "integer", "minimum": 1, "maximum": 5 },
33                    "from": { "type": "string" },
34                    "to": { "type": "string" }
35                },
36                "required": ["action"],
37                "allOf": [
38                    { "if": { "properties": { "action": { "enum": ["callers", "callees", "risk"] } }, "required": ["action"] }, "then": { "required": ["symbol"] } },
39                    { "if": { "properties": { "action": { "const": "trace" } }, "required": ["action"] }, "then": { "required": ["from", "to"] } }
40                ]
41            }),
42        )
43    }
44
45    fn handle(
46        &self,
47        args: &Map<String, Value>,
48        ctx: &ToolContext,
49    ) -> Result<ToolOutput, ErrorData> {
50        let action = get_str(args, "action").unwrap_or_else(|| "callers".to_string());
51
52        let action_normalized = match action.to_lowercase().as_str() {
53            "callers" | "caller" => "callers",
54            "callees" | "callee" => "callees",
55            "trace" => "trace",
56            "risk" => "risk",
57            _ => action.as_str(),
58        }
59        .to_string();
60
61        let symbol = get_str(args, "symbol");
62        let file = get_str(args, "file");
63        let depth = get_int(args, "depth").unwrap_or(1).clamp(1, 5) as usize;
64        let from = get_str(args, "from");
65        let to = get_str(args, "to");
66
67        let cache_input = format!(
68            "callgraph:{action_normalized}:{}:{}:{depth}",
69            symbol.as_deref().unwrap_or(""),
70            file.as_deref().unwrap_or("")
71        );
72        let builder = SearchQueryKey {
73            path: ctx.project_root.clone(),
74            index_rev: String::new(),
75            pattern: cache_input,
76            include: String::new(),
77            exclude: String::new(),
78        };
79        let key = builder.cache_key();
80        let validator = builder.validator();
81
82        if let Some(entry) =
83            crate::core::ocla::cache_delivery::check(&key, &validator, "ctx_callgraph")
84        {
85            let stub = crate::core::ocla::cache_delivery::stub(&entry, "callgraph");
86            return Ok(ToolOutput {
87                text: stub,
88                original_tokens: entry.token_count as usize,
89                saved_tokens: entry.token_count as usize,
90                mode: Some(action_normalized),
91                path: None,
92                changed: false,
93                shell_outcome: None,
94                content_blocks: None,
95            });
96        }
97
98        let result = crate::tools::ctx_callgraph::handle(
99            &action_normalized,
100            symbol.as_deref(),
101            file.as_deref(),
102            &ctx.project_root,
103            depth,
104            from.as_deref(),
105            to.as_deref(),
106        );
107
108        crate::core::ocla::cache_delivery::record(
109            key,
110            crate::core::ocla::cache_types::DeliveryKind::SearchQuery,
111            validator,
112            None,
113            &result,
114            "ctx_callgraph",
115        );
116
117        Ok(ToolOutput {
118            text: result,
119            original_tokens: 0,
120            saved_tokens: 0,
121            mode: Some(action_normalized),
122            path: None,
123            changed: false,
124            shell_outcome: None,
125            content_blocks: None,
126        })
127    }
128}