lean_ctx/tools/registered/
ctx_metrics.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 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
32 .cache
33 .as_ref()
34 .ok_or_else(|| ErrorData::internal_error("cache not available", None))?;
35 let Some(cache_guard) = crate::server::bounded_lock::read(cache, "ctx_metrics:cache")
36 else {
37 return Ok(ToolOutput::simple(
38 "[metrics unavailable — cache busy, retry]".to_string(),
39 ));
40 };
41 let calls = ctx
42 .tool_calls
43 .as_ref()
44 .ok_or_else(|| ErrorData::internal_error("tool_calls not available", None))?;
45 let Some(calls_guard) = crate::server::bounded_lock::read(calls, "ctx_metrics:calls")
46 else {
47 return Ok(ToolOutput::simple(
48 "[metrics unavailable — calls lock busy, retry]".to_string(),
49 ));
50 };
51 let mut result =
52 crate::tools::ctx_metrics::handle(&cache_guard, &calls_guard, ctx.crp_mode);
53 drop(cache_guard);
54 drop(calls_guard);
55
56 if let Some(ref ps) = ctx.pipeline_stats {
57 let Some(stats) = crate::server::bounded_lock::read(ps, "ctx_metrics:pipeline") else {
58 return Ok(ToolOutput::simple(result));
59 };
60 if stats.runs > 0 {
61 result.push_str("\n\n--- PIPELINE METRICS ---\n");
62 result.push_str(&stats.format_summary());
63 }
64 }
65
66 let (ts_hits, regex_hits) = crate::core::signatures::signature_backend_stats();
67 if ts_hits + regex_hits > 0 {
68 result.push_str("\n--- SIGNATURE BACKEND ---\n");
69 result.push_str(&format!(
70 "tree-sitter: {} | regex fallback: {} | ratio: {:.0}%\n",
71 ts_hits,
72 regex_hits,
73 if ts_hits + regex_hits > 0 {
74 ts_hits as f64 / (ts_hits + regex_hits) as f64 * 100.0
75 } else {
76 0.0
77 }
78 ));
79 }
80
81 Ok(ToolOutput::simple(result))
82 }
83}