Skip to main content

lean_ctx/tools/registered/
ctx_radar.rs

1use rmcp::model::Tool;
2use rmcp::ErrorData;
3use serde_json::{json, Map, Value};
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            "Full context budget breakdown: system prompt, messages, tools, reads, shell — all tracked token usage.",
19            json!({
20                "type": "object",
21                "properties": {
22                    "format": {
23                        "type": "string",
24                        "description": "Output format: 'display' (human-readable) or 'json' (structured)",
25                        "enum": ["display", "json"],
26                        "default": "display"
27                    }
28                }
29            }),
30        )
31    }
32
33    fn handle(
34        &self,
35        args: &Map<String, Value>,
36        ctx: &ToolContext,
37    ) -> Result<ToolOutput, ErrorData> {
38        let format = args
39            .get("format")
40            .and_then(|v| v.as_str())
41            .unwrap_or("display");
42
43        let data_dir = crate::core::data_dir::lean_ctx_data_dir().unwrap_or_else(|_| {
44            std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()))
45                .join(".lean-ctx")
46        });
47
48        let client_name = ctx
49            .client_name
50            .as_ref()
51            .and_then(|cn| tokio::task::block_in_place(|| cn.blocking_read().clone()).into())
52            .unwrap_or_else(|| "cursor".to_string());
53        let window_size = crate::core::context_radar::default_window_for_client(&client_name);
54
55        let radar = crate::core::context_radar::ContextRadar::load(&data_dir, window_size);
56
57        let output = match format {
58            "json" => {
59                let breakdown = radar.budget_breakdown();
60                serde_json::to_string_pretty(&breakdown).unwrap_or_default()
61            }
62            _ => radar.format_display(),
63        };
64
65        Ok(ToolOutput::simple(output))
66    }
67}