lean_ctx/tools/registered/
ctx_overview.rs1use 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}