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