use rmcp::model::Tool;
use rmcp::ErrorData;
use serde_json::{json, Map, Value};
use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput};
use crate::tool_defs::tool_def;
pub struct CtxRadarTool;
impl McpTool for CtxRadarTool {
fn name(&self) -> &'static str {
"ctx_radar"
}
fn tool_def(&self) -> Tool {
tool_def(
"ctx_radar",
"Full context budget breakdown: system prompt, messages, tools, reads, shell — all tracked token usage.",
json!({
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "Output format: 'display' (human-readable) or 'json' (structured)",
"enum": ["display", "json"],
"default": "display"
}
}
}),
)
}
fn handle(
&self,
args: &Map<String, Value>,
ctx: &ToolContext,
) -> Result<ToolOutput, ErrorData> {
let format = args
.get("format")
.and_then(|v| v.as_str())
.unwrap_or("display");
let data_dir = crate::core::data_dir::lean_ctx_data_dir().unwrap_or_else(|_| {
std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()))
.join(".lean-ctx")
});
let client_name = ctx
.client_name
.as_ref()
.and_then(|cn| tokio::task::block_in_place(|| cn.blocking_read().clone()).into())
.unwrap_or_else(|| "cursor".to_string());
let window_size = crate::core::context_radar::default_window_for_client(&client_name);
let radar = crate::core::context_radar::ContextRadar::load(&data_dir, window_size);
let output = match format {
"json" => {
let breakdown = radar.budget_breakdown();
serde_json::to_string_pretty(&breakdown).unwrap_or_default()
}
_ => radar.format_display(),
};
Ok(ToolOutput::simple(output))
}
}