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