1use serde_json::{json, Value};
7
8pub struct MCPTools;
10
11impl MCPTools {
12 pub fn get_tools_list() -> Value {
14 json!({
15 "tools": [
16 {
17 "name": "store_memory",
18 "description": "Store a memory in the hierarchical memory system",
19 "inputSchema": {
20 "type": "object",
21 "properties": {
22 "content": {
23 "type": "string",
24 "description": "The content to store as a memory"
25 },
26 "tier": {
27 "type": "string",
28 "enum": ["working", "warm", "cold"],
29 "description": "The tier to store the memory in (defaults to automatic placement)",
30 "default": "working"
31 },
32 "tags": {
33 "type": "array",
34 "items": {"type": "string"},
35 "description": "Optional tags to associate with the memory for categorization"
36 },
37 "importance_score": {
38 "type": "number",
39 "minimum": 0.0,
40 "maximum": 1.0,
41 "description": "Optional importance score (0.0 to 1.0) for the memory"
42 },
43 "metadata": {
44 "type": "object",
45 "description": "Optional additional metadata to store with the memory"
46 }
47 },
48 "required": ["content"]
49 }
50 },
51 {
52 "name": "search_memory",
53 "description": "Search memories using semantic similarity",
54 "inputSchema": {
55 "type": "object",
56 "properties": {
57 "query": {
58 "type": "string",
59 "description": "The search query text"
60 },
61 "limit": {
62 "type": "integer",
63 "default": 10,
64 "minimum": 1,
65 "maximum": 100,
66 "description": "Maximum number of results to return"
67 },
68 "similarity_threshold": {
69 "type": "number",
70 "minimum": 0.0,
71 "maximum": 1.0,
72 "default": 0.5,
73 "description": "Minimum similarity score for results"
74 },
75 "tier": {
76 "type": "string",
77 "enum": ["working", "warm", "cold"],
78 "description": "Optional tier filter to search within"
79 },
80 "tags": {
81 "type": "array",
82 "items": {"type": "string"},
83 "description": "Optional tags to filter results by"
84 },
85 "include_metadata": {
86 "type": "boolean",
87 "default": true,
88 "description": "Whether to include metadata in results"
89 }
90 },
91 "required": ["query"]
92 }
93 },
94 {
95 "name": "get_statistics",
96 "description": "Get memory system statistics and metrics",
97 "inputSchema": {
98 "type": "object",
99 "properties": {
100 "detailed": {
101 "type": "boolean",
102 "default": false,
103 "description": "Whether to include detailed metrics"
104 }
105 },
106 "required": []
107 }
108 },
109 {
110 "name": "what_did_you_remember",
111 "description": "Query what the system has remembered about recent interactions",
112 "inputSchema": {
113 "type": "object",
114 "properties": {
115 "context": {
116 "type": "string",
117 "description": "Optional context to filter memories by (e.g., 'conversation', 'project')",
118 "default": "conversation"
119 },
120 "time_range": {
121 "type": "string",
122 "enum": ["last_hour", "last_day", "last_week", "last_month"],
123 "description": "Time range to search within",
124 "default": "last_day"
125 },
126 "limit": {
127 "type": "integer",
128 "minimum": 1,
129 "maximum": 50,
130 "default": 10,
131 "description": "Maximum number of memories to return"
132 }
133 },
134 "required": []
135 }
136 },
137 {
138 "name": "harvest_conversation",
139 "description": "Trigger the silent harvester to process current conversation context",
140 "inputSchema": {
141 "type": "object",
142 "properties": {
143 "message": {
144 "type": "string",
145 "description": "Message content to harvest"
146 },
147 "context": {
148 "type": "string",
149 "description": "Context for the message (e.g., 'conversation', 'project')",
150 "default": "conversation"
151 },
152 "role": {
153 "type": "string",
154 "enum": ["user", "assistant", "system"],
155 "description": "Role of the message sender",
156 "default": "user"
157 },
158 "force_harvest": {
159 "type": "boolean",
160 "default": false,
161 "description": "Force immediate harvest instead of queuing"
162 },
163 "silent_mode": {
164 "type": "boolean",
165 "default": true,
166 "description": "Run in silent mode (minimal output)"
167 }
168 },
169 "required": []
170 }
171 },
172 {
173 "name": "get_harvester_metrics",
174 "description": "Get metrics and status from the silent harvester service",
175 "inputSchema": {
176 "type": "object",
177 "properties": {},
178 "required": []
179 }
180 },
181 {
182 "name": "migrate_memory",
183 "description": "Move a memory between tiers in the hierarchical system",
184 "inputSchema": {
185 "type": "object",
186 "properties": {
187 "memory_id": {
188 "type": "string",
189 "description": "UUID of the memory to migrate"
190 },
191 "target_tier": {
192 "type": "string",
193 "enum": ["working", "warm", "cold", "frozen"],
194 "description": "Target tier for migration"
195 },
196 "reason": {
197 "type": "string",
198 "description": "Optional reason for migration"
199 }
200 },
201 "required": ["memory_id", "target_tier"]
202 }
203 },
204 {
205 "name": "delete_memory",
206 "description": "Delete a specific memory from the system",
207 "inputSchema": {
208 "type": "object",
209 "properties": {
210 "memory_id": {
211 "type": "string",
212 "description": "UUID of the memory to delete"
213 },
214 "confirm": {
215 "type": "boolean",
216 "default": false,
217 "description": "Confirmation flag to prevent accidental deletions"
218 }
219 },
220 "required": ["memory_id", "confirm"]
221 }
222 }
223 ]
224 })
225 }
226
227 pub fn get_resources_list() -> Value {
229 json!({
230 "resources": []
231 })
232 }
233
234 pub fn get_prompts_list() -> Value {
236 json!({
237 "prompts": []
238 })
239 }
240
241 pub fn get_server_capabilities() -> Value {
243 json!({
244 "protocolVersion": "2025-06-18",
245 "capabilities": {
246 "tools": {
247 "listChanged": false
248 },
249 "resources": {
250 "listChanged": false
251 },
252 "prompts": {
253 "listChanged": false
254 }
255 },
256 "serverInfo": {
257 "name": "codex-memory",
258 "version": env!("CARGO_PKG_VERSION"),
259 "description": "Hierarchical memory system with semantic search and automated consolidation"
260 }
261 })
262 }
263
264 pub fn validate_tool_args(tool_name: &str, args: &Value) -> Result<(), String> {
266 match tool_name {
267 "store_memory" => {
268 if args
269 .get("content")
270 .and_then(|c| c.as_str())
271 .is_none_or(|s| s.is_empty())
272 {
273 return Err("Content is required and cannot be empty".to_string());
274 }
275
276 if let Some(tier) = args.get("tier").and_then(|t| t.as_str()) {
278 if !["working", "warm", "cold"].contains(&tier) {
279 return Err(
280 "Invalid tier. Must be 'working', 'warm', or 'cold'".to_string()
281 );
282 }
283 }
284
285 if let Some(score) = args.get("importance_score").and_then(|s| s.as_f64()) {
287 if !(0.0..=1.0).contains(&score) {
288 return Err("Importance score must be between 0.0 and 1.0".to_string());
289 }
290 }
291 }
292 "search_memory" => {
293 if args
294 .get("query")
295 .and_then(|q| q.as_str())
296 .is_none_or(|s| s.is_empty())
297 {
298 return Err("Query is required and cannot be empty".to_string());
299 }
300
301 if let Some(limit) = args.get("limit").and_then(|l| l.as_i64()) {
303 if !(1..=100).contains(&limit) {
304 return Err("Limit must be between 1 and 100".to_string());
305 }
306 }
307
308 if let Some(threshold) = args.get("similarity_threshold").and_then(|t| t.as_f64()) {
310 if !(0.0..=1.0).contains(&threshold) {
311 return Err("Similarity threshold must be between 0.0 and 1.0".to_string());
312 }
313 }
314 }
315 "migrate_memory" => {
316 if args
317 .get("memory_id")
318 .and_then(|id| id.as_str())
319 .is_none_or(|s| s.is_empty())
320 {
321 return Err("Memory ID is required".to_string());
322 }
323
324 if let Some(tier) = args.get("target_tier").and_then(|t| t.as_str()) {
325 if !["working", "warm", "cold", "frozen"].contains(&tier) {
326 return Err("Invalid target tier".to_string());
327 }
328 } else {
329 return Err("Target tier is required".to_string());
330 }
331 }
332 "delete_memory" => {
333 if args
334 .get("memory_id")
335 .and_then(|id| id.as_str())
336 .is_none_or(|s| s.is_empty())
337 {
338 return Err("Memory ID is required".to_string());
339 }
340
341 if !args
342 .get("confirm")
343 .and_then(|c| c.as_bool())
344 .unwrap_or(false)
345 {
346 return Err("Confirmation required for deletion".to_string());
347 }
348 }
349 "what_did_you_remember" => {
350 if let Some(range) = args.get("time_range").and_then(|r| r.as_str()) {
352 if !["last_hour", "last_day", "last_week", "last_month"].contains(&range) {
353 return Err("Invalid time range".to_string());
354 }
355 }
356 }
357 "harvest_conversation" => {
358 if let Some(role) = args.get("role").and_then(|r| r.as_str()) {
360 if !["user", "assistant", "system"].contains(&role) {
361 return Err(
362 "Invalid role. Must be 'user', 'assistant', or 'system'".to_string()
363 );
364 }
365 }
366 }
367 "get_statistics" | "get_harvester_metrics" => {
368 }
370 _ => return Err(format!("Unknown tool: {tool_name}")),
371 }
372
373 Ok(())
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_tools_list_structure() {
383 let tools = MCPTools::get_tools_list();
384 let tools_array = tools["tools"].as_array().unwrap();
385
386 assert!(!tools_array.is_empty());
387
388 let store_memory = tools_array
390 .iter()
391 .find(|t| t["name"] == "store_memory")
392 .unwrap();
393
394 assert_eq!(store_memory["name"], "store_memory");
395 assert!(store_memory["inputSchema"]["properties"]["content"].is_object());
396 assert_eq!(store_memory["inputSchema"]["required"][0], "content");
397 }
398
399 #[test]
400 fn test_tool_validation() {
401 let valid_args = json!({
403 "content": "Test memory",
404 "tier": "working",
405 "importance_score": 0.8
406 });
407 assert!(MCPTools::validate_tool_args("store_memory", &valid_args).is_ok());
408
409 let invalid_args = json!({
411 "content": "",
412 "tier": "working"
413 });
414 assert!(MCPTools::validate_tool_args("store_memory", &invalid_args).is_err());
415
416 let invalid_tier = json!({
418 "content": "Test",
419 "tier": "invalid"
420 });
421 assert!(MCPTools::validate_tool_args("store_memory", &invalid_tier).is_err());
422
423 assert!(MCPTools::validate_tool_args("unknown_tool", &valid_args).is_err());
425 }
426
427 #[test]
428 fn test_server_capabilities() {
429 let capabilities = MCPTools::get_server_capabilities();
430 assert_eq!(capabilities["protocolVersion"], "2025-06-18");
431 assert_eq!(capabilities["serverInfo"]["name"], "codex-memory");
432 assert!(capabilities["capabilities"]["tools"]["listChanged"] == false);
433 }
434}