Skip to main content

lean_ctx/tools/registered/
ctx_overview.rs

1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput, get_str};
6use crate::tool_defs::tool_def;
7
8pub struct CtxOverviewTool;
9
10impl McpTool for CtxOverviewTool {
11    fn name(&self) -> &'static str {
12        "ctx_overview"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_overview",
18            "WORKFLOW: call at session START before ctx_compose/ctx_read.\n\
19            ANTIPATTERN: NOT for source code — structure only. Use ctx_compose for code understanding.\n\
20            Project map — task='your goal' scopes files by relevance (PageRank on symbol graph).\n\
21            High-level structure only, no source body. ~10x cheaper than ctx_compose.",
22            json!({
23                "type": "object",
24                "properties": {
25                    "task": {
26                        "type": "string",
27                        "description": "Short English task for relevance scoring"
28                    },
29                    "path": {
30                        "type": "string",
31                        "description": "Project root"
32                    }
33                }
34            }),
35        )
36    }
37
38    fn handle(
39        &self,
40        args: &Map<String, Value>,
41        ctx: &ToolContext,
42    ) -> Result<ToolOutput, ErrorData> {
43        let task = get_str(args, "task");
44
45        let resolved_path = if get_str(args, "path").is_some() {
46            if let Some(p) = ctx.resolved_path("path") {
47                Some(p.to_string())
48            } else if let Some(err) = ctx.path_error("path") {
49                return Err(ErrorData::invalid_params(format!("path: {err}"), None));
50            } else {
51                None
52            }
53        } else if let Some(ref session) = ctx.session {
54            let guard = crate::server::bounded_lock::read(session, "ctx_overview:session");
55            guard.as_ref().and_then(|g| g.project_root.clone())
56        } else {
57            None
58        };
59
60        let cache = ctx
61            .cache
62            .as_ref()
63            .ok_or_else(|| ErrorData::internal_error("cache not available", None))?;
64        let Some(guard) = crate::server::bounded_lock::read(cache, "ctx_overview:cache") else {
65            return Ok(ToolOutput::simple(
66                "[overview temporarily unavailable — cache busy]".to_string(),
67            ));
68        };
69        let (result, _complete) = crate::tools::ctx_overview::handle(
70            &guard,
71            task.as_deref(),
72            resolved_path.as_deref(),
73            ctx.crp_mode,
74        );
75
76        Ok(ToolOutput {
77            text: result,
78            original_tokens: 0,
79            saved_tokens: 0,
80            mode: Some("overview".to_string()),
81            path: None,
82            changed: false,
83            shell_outcome: None,
84            content_blocks: None,
85        })
86    }
87}