Skip to main content

lean_ctx/tools/registered/
ctx_radar.rs

1use rmcp::ErrorData;
2use rmcp::model::Tool;
3use serde_json::{Map, Value, json};
4
5use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput};
6use crate::tool_defs::tool_def;
7
8pub struct CtxRadarTool;
9
10impl McpTool for CtxRadarTool {
11    fn name(&self) -> &'static str {
12        "ctx_radar"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_radar",
18            "Context budget breakdown — system prompt, messages, tools, reads, shell.\n\
19             WORKFLOW: call when context window tight to find biggest consumers.\n\
20             ANTIPATTERN: not for per-call timing — use ctx_metrics instead.\n\
21             format=display (human-readable) or json (structured). Complements ctx_metrics\n\
22             for comprehensive budget analysis. Saves tokens vs manual budget estimation.",
23            json!({
24                "type": "object",
25                "properties": {
26                    "format": {
27                        "type": "string",
28                        "description": "display|json",
29                        "enum": ["display", "json"],
30                        "default": "display"
31                    }
32                }
33            }),
34        )
35    }
36
37    fn handle(
38        &self,
39        args: &Map<String, Value>,
40        ctx: &ToolContext,
41    ) -> Result<ToolOutput, ErrorData> {
42        let format = args
43            .get("format")
44            .and_then(|v| v.as_str())
45            .unwrap_or("display");
46
47        let data_dir = crate::core::data_dir::lean_ctx_data_dir().unwrap_or_else(|_| {
48            std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()))
49                .join(".lean-ctx")
50        });
51
52        let client_name = ctx
53            .client_name
54            .as_ref()
55            .and_then(|cn| tokio::task::block_in_place(|| cn.blocking_read().clone()).into())
56            .unwrap_or_else(|| "cursor".to_string());
57        let window_size = crate::core::context_radar::default_window_for_client(&client_name);
58
59        let radar = crate::core::context_radar::ContextRadar::load(&data_dir, window_size);
60
61        let output = match format {
62            "json" => {
63                let breakdown = radar.budget_breakdown();
64                serde_json::to_string_pretty(&breakdown).unwrap_or_default()
65            }
66            _ => radar.format_display(),
67        };
68
69        Ok(ToolOutput::simple(output))
70    }
71}