Skip to main content

lean_ctx_server/tools/registered/
ctx_radar.rs

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