lean_ctx/tools/ctx_cognitive/
mod.rs1use rmcp::ErrorData;
4use rmcp::model::Tool;
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value, json};
7
8use crate::core::config::CognitiveMode;
9use crate::server::tool_trait::{McpTool, ToolContext, ToolOutput};
10use crate::tool_defs::tool_def;
11
12#[derive(Debug, Deserialize)]
13pub struct CognitiveParams {
14 pub action: String,
15}
16
17#[derive(Debug, Serialize)]
18#[serde(rename_all = "camelCase")]
19struct FeatureStatus {
20 name: &'static str,
21 enabled: bool,
22}
23
24#[derive(Debug, Serialize)]
25#[serde(rename_all = "camelCase")]
26struct CognitiveStatus {
27 action: &'static str,
28 cognitive_mode: String,
29 features: Vec<&'static str>,
30}
31
32#[derive(Debug, Serialize)]
33#[serde(rename_all = "camelCase")]
34struct CognitiveFeatures {
35 action: &'static str,
36 cognitive_mode: String,
37 features: Vec<FeatureStatus>,
38}
39
40pub struct CtxCognitiveTool;
41
42impl McpTool for CtxCognitiveTool {
43 fn name(&self) -> &'static str {
44 "ctx_cognitive"
45 }
46
47 fn tool_def(&self) -> Tool {
48 tool_def(
49 "ctx_cognitive",
50 "Read science-driven context intelligence status and cognitive impact.\n\
51 action=status reports the active mode; impact reports interruption savings; \
52 features lists every science feature and whether it is enabled.",
53 json!({
54 "type": "object",
55 "properties": {
56 "action": {
57 "type": "string",
58 "enum": ["status", "impact", "features"],
59 "description": "Cognitive information to return"
60 }
61 },
62 "required": ["action"]
63 }),
64 )
65 }
66
67 fn handle(
68 &self,
69 args: &Map<String, Value>,
70 _ctx: &ToolContext,
71 ) -> Result<ToolOutput, ErrorData> {
72 let params: CognitiveParams = serde_json::from_value(Value::Object(args.clone()))
73 .map_err(|error| ErrorData::invalid_params(error.to_string(), None))?;
74 let mode = crate::core::config::Config::load().cognitive_mode;
75
76 let value = match params.action.as_str() {
77 "status" => serde_json::to_value(CognitiveStatus {
78 action: "status",
79 cognitive_mode: mode.to_string(),
80 features: feature_statuses(mode)
81 .into_iter()
82 .filter_map(|feature| feature.enabled.then_some(feature.name))
83 .collect(),
84 }),
85 "impact" => {
86 let report = crate::core::anti_interrupt::compute_impact();
87 Ok(json!({
88 "action": "impact",
89 "interruptionsPrevented": report.interruptions_prevented,
90 "contextSwitchesSaved": report.context_switches_saved,
91 "echoTokensSaved": report.echo_tokens_saved,
92 "cognitiveLoadReduction": report.cognitive_load_reduction,
93 "focusTimeSavedMinutes": report.focus_time_saved_minutes,
94 "score": report.score
95 }))
96 }
97 "features" => serde_json::to_value(CognitiveFeatures {
98 action: "features",
99 cognitive_mode: mode.to_string(),
100 features: feature_statuses(mode),
101 }),
102 _ => {
103 return Err(ErrorData::invalid_params(
104 "action must be one of: status, impact, features",
105 None,
106 ));
107 }
108 }
109 .map_err(|error| ErrorData::internal_error(error.to_string(), None))?;
110
111 let text = serde_json::to_string_pretty(&value)
112 .map_err(|error| ErrorData::internal_error(error.to_string(), None))?;
113 Ok(ToolOutput::simple(text))
114 }
115
116 fn produces_machine_readable(&self, _args: Option<&Map<String, Value>>) -> bool {
117 true
118 }
119}
120
121fn feature_statuses(mode: CognitiveMode) -> Vec<FeatureStatus> {
122 let basic = !matches!(mode, CognitiveMode::Off);
123 let full = matches!(mode, CognitiveMode::Full);
124
125 vec![
126 FeatureStatus {
127 name: "intent_classification",
128 enabled: basic,
129 },
130 FeatureStatus {
131 name: "semantic_chunking",
132 enabled: basic,
133 },
134 FeatureStatus {
135 name: "memory_scheduling",
136 enabled: full,
137 },
138 FeatureStatus {
139 name: "anti_interruption",
140 enabled: full,
141 },
142 FeatureStatus {
143 name: "optimal_transport_allocation",
144 enabled: full,
145 },
146 FeatureStatus {
147 name: "graph_expansion",
148 enabled: full,
149 },
150 FeatureStatus {
151 name: "structural_descriptions",
152 enabled: full,
153 },
154 FeatureStatus {
155 name: "verbosity_learning",
156 enabled: full,
157 },
158 FeatureStatus {
159 name: "context_prefetch",
160 enabled: full,
161 },
162 FeatureStatus {
163 name: "stigmergic_coordination",
164 enabled: full,
165 },
166 ]
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn basic_mode_enables_only_basic_features() {
175 let features = feature_statuses(CognitiveMode::Basic);
176 assert_eq!(features.iter().filter(|feature| feature.enabled).count(), 2);
177 assert!(features[0].enabled);
178 assert!(features[1].enabled);
179 }
180
181 #[test]
182 fn full_mode_enables_every_feature() {
183 let features = feature_statuses(CognitiveMode::Full);
184 assert_eq!(features.len(), 10);
185 assert!(features.iter().all(|feature| feature.enabled));
186 }
187}