Skip to main content

lean_ctx/tools/registered/
ctx_metrics.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 CtxMetricsTool;
9
10impl McpTool for CtxMetricsTool {
11    fn name(&self) -> &'static str {
12        "ctx_metrics"
13    }
14
15    fn tool_def(&self) -> Tool {
16        tool_def(
17            "ctx_metrics",
18            "Session token stats, cache rates, per-tool savings.",
19            json!({
20                "type": "object",
21                "properties": {}
22            }),
23        )
24    }
25
26    fn handle(
27        &self,
28        _args: &Map<String, Value>,
29        ctx: &ToolContext,
30    ) -> Result<ToolOutput, ErrorData> {
31        let cache = ctx.cache.as_ref().unwrap();
32        let Some(cache_guard) = crate::server::bounded_lock::read(cache, "ctx_metrics:cache")
33        else {
34            return Ok(ToolOutput::simple(
35                "[metrics unavailable — cache busy, retry]".to_string(),
36            ));
37        };
38        let calls = ctx.tool_calls.as_ref().unwrap();
39        let Some(calls_guard) = crate::server::bounded_lock::read(calls, "ctx_metrics:calls")
40        else {
41            return Ok(ToolOutput::simple(
42                "[metrics unavailable — calls lock busy, retry]".to_string(),
43            ));
44        };
45        let mut result =
46            crate::tools::ctx_metrics::handle(&cache_guard, &calls_guard, ctx.crp_mode);
47        drop(cache_guard);
48        drop(calls_guard);
49
50        if let Some(ref ps) = ctx.pipeline_stats {
51            let Some(stats) = crate::server::bounded_lock::read(ps, "ctx_metrics:pipeline") else {
52                return Ok(ToolOutput::simple(result));
53            };
54            if stats.runs > 0 {
55                result.push_str("\n\n--- PIPELINE METRICS ---\n");
56                result.push_str(&stats.format_summary());
57            }
58        }
59
60        let (ts_hits, regex_hits) = crate::core::signatures::signature_backend_stats();
61        if ts_hits + regex_hits > 0 {
62            result.push_str("\n--- SIGNATURE BACKEND ---\n");
63            result.push_str(&format!(
64                "tree-sitter: {} | regex fallback: {} | ratio: {:.0}%\n",
65                ts_hits,
66                regex_hits,
67                if ts_hits + regex_hits > 0 {
68                    ts_hits as f64 / (ts_hits + regex_hits) as f64 * 100.0
69                } else {
70                    0.0
71                }
72            ));
73        }
74
75        Ok(ToolOutput::simple(result))
76    }
77}