lean_ctx/tools/registered/
ctx_overview.rs1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
4
5use crate::server::tool_trait::{get_str, McpTool, ToolContext, ToolOutput};
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 "Task-relevant project map — use at session start.",
19 json!({
20 "type": "object",
21 "properties": {
22 "task": {
23 "type": "string",
24 "description": "Task description for relevance scoring"
25 },
26 "path": {
27 "type": "string",
28 "description": "Project root directory (default: .)"
29 }
30 }
31 }),
32 )
33 }
34
35 fn handle(
36 &self,
37 args: &Map<String, Value>,
38 ctx: &ToolContext,
39 ) -> Result<ToolOutput, ErrorData> {
40 let task = get_str(args, "task");
41
42 let resolved_path = if get_str(args, "path").is_some() {
43 if let Some(p) = ctx.resolved_path("path") {
44 Some(p.to_string())
45 } else if let Some(err) = ctx.path_error("path") {
46 return Err(ErrorData::invalid_params(format!("path: {err}"), None));
47 } else {
48 None
49 }
50 } else if let Some(ref session) = ctx.session {
51 let guard = crate::server::bounded_lock::read(session, "ctx_overview:session");
52 guard.as_ref().and_then(|g| g.project_root.clone())
53 } else {
54 None
55 };
56
57 let cache = ctx
58 .cache
59 .as_ref()
60 .ok_or_else(|| ErrorData::internal_error("cache not available", None))?;
61 let Some(guard) = crate::server::bounded_lock::read(cache, "ctx_overview:cache") else {
62 return Ok(ToolOutput::simple(
63 "[overview temporarily unavailable — cache busy]".to_string(),
64 ));
65 };
66 let result = crate::tools::ctx_overview::handle(
67 &guard,
68 task.as_deref(),
69 resolved_path.as_deref(),
70 ctx.crp_mode,
71 );
72
73 Ok(ToolOutput {
74 text: result,
75 original_tokens: 0,
76 saved_tokens: 0,
77 mode: Some("overview".to_string()),
78 path: None,
79 changed: false,
80 })
81 }
82}