codex_memory/mcp_server/
handlers.rs1use crate::chunking::FileChunker;
3use crate::error::Result;
4use crate::storage::Storage;
5use serde_json::{json, Value};
6use std::path::Path;
7use std::sync::Arc;
8use uuid::Uuid;
9
10pub struct MCPHandlers {
12 storage: Arc<Storage>,
13}
14
15impl MCPHandlers {
16 pub fn new(storage: Arc<Storage>) -> Self {
18 Self { storage }
19 }
20
21 pub async fn handle_tool_call(&self, tool_name: &str, params: Value) -> Result<Value> {
23 match tool_name {
24 "store_memory" => self.handle_store_memory(params).await,
25 "get_memory" => self.handle_get_memory(params).await,
26 "delete_memory" => self.handle_delete_memory(params).await,
27 "get_statistics" => self.handle_get_statistics().await,
28 "store_file" => self.handle_store_file(params).await,
29 _ => Err(crate::error::Error::Other(format!(
30 "Unknown tool: {}",
31 tool_name
32 ))),
33 }
34 }
35
36 async fn handle_store_memory(&self, params: Value) -> Result<Value> {
37 let content = params["content"]
38 .as_str()
39 .ok_or_else(|| crate::error::Error::Other("Missing content parameter".to_string()))?;
40
41 let context = params["context"]
43 .as_str()
44 .ok_or_else(|| {
45 crate::error::Error::Other("Missing required context parameter".to_string())
46 })?
47 .to_string();
48
49 let summary = params["summary"]
51 .as_str()
52 .ok_or_else(|| {
53 crate::error::Error::Other("Missing required summary parameter".to_string())
54 })?
55 .to_string();
56
57 let tags = params["tags"]
59 .as_array()
60 .ok_or_else(|| {
61 crate::error::Error::Other("Missing required tags parameter".to_string())
62 })?
63 .iter()
64 .filter_map(|v| v.as_str().map(String::from))
65 .collect::<Vec<_>>();
66
67 let id = self
68 .storage
69 .store(content, context, summary, Some(tags))
70 .await?;
71
72 Ok(json!({
73 "id": id.to_string(),
74 "message": "Memory stored successfully"
75 }))
76 }
77
78 async fn handle_get_memory(&self, params: Value) -> Result<Value> {
79 let id_str = params["id"]
80 .as_str()
81 .ok_or_else(|| crate::error::Error::Other("Missing id parameter".to_string()))?;
82
83 let id = Uuid::parse_str(id_str)
84 .map_err(|e| crate::error::Error::Other(format!("Invalid UUID: {}", e)))?;
85
86 match self.storage.get(id).await? {
87 Some(memory) => Ok(serde_json::to_value(memory)?),
88 None => Err(crate::error::Error::Other(format!(
89 "Memory not found: {}",
90 id
91 ))),
92 }
93 }
94
95 async fn handle_delete_memory(&self, params: Value) -> Result<Value> {
96 let id_str = params["id"]
97 .as_str()
98 .ok_or_else(|| crate::error::Error::Other("Missing id parameter".to_string()))?;
99
100 let id = Uuid::parse_str(id_str)
101 .map_err(|e| crate::error::Error::Other(format!("Invalid UUID: {}", e)))?;
102
103 let deleted = self.storage.delete(id).await?;
104
105 Ok(json!({
106 "deleted": deleted,
107 "message": if deleted { "Memory deleted successfully" } else { "Memory not found" }
108 }))
109 }
110
111 async fn handle_get_statistics(&self) -> Result<Value> {
112 let stats = self.storage.stats().await?;
113 Ok(serde_json::to_value(stats)?)
114 }
115
116 async fn handle_store_file(&self, params: Value) -> Result<Value> {
117 let file_path = params["file_path"]
118 .as_str()
119 .ok_or_else(|| crate::error::Error::Other("Missing file_path parameter".to_string()))?;
120
121 let chunk_size = params
122 .get("chunk_size")
123 .and_then(|v| v.as_u64())
124 .unwrap_or(8000) as usize;
125
126 let overlap = params
127 .get("overlap")
128 .and_then(|v| v.as_u64())
129 .unwrap_or(200) as usize;
130
131 let tags = params.get("tags").and_then(|v| v.as_array()).map(|arr| {
136 arr.iter()
137 .filter_map(|v| v.as_str().map(String::from))
138 .collect::<Vec<_>>()
139 });
140
141 let content = tokio::fs::read_to_string(file_path)
143 .await
144 .map_err(|e| crate::error::Error::Other(format!("Failed to read file: {}", e)))?;
145
146 let filename = Path::new(file_path)
148 .file_name()
149 .and_then(|n| n.to_str())
150 .unwrap_or("unknown");
151
152 let content_len = content.len();
154 let mut stored_ids = Vec::new();
155
156 let chunker = FileChunker::new(chunk_size, overlap);
158 let chunks = chunker.chunk_content(&content)?;
159
160 if chunks.len() == 1 {
161 let context = format!("Content from file: {}", filename);
163 let summary = format!(
164 "Complete content of {} ({} characters)",
165 filename, content_len
166 );
167
168 let id = self
169 .storage
170 .store(&content, context, summary, tags.clone())
171 .await?;
172
173 stored_ids.push(id.to_string());
174 } else {
175 let parent_id = Uuid::new_v4();
177 let total_chunks = chunks.len();
178
179 for (index, chunk) in chunks.into_iter().enumerate() {
180 let chunk_num = index + 1;
181
182 let context = format!(
183 "Chunk {} of {} from file: {}",
184 chunk_num, total_chunks, filename
185 );
186
187 let summary = format!(
188 "Part {} of {} from {} (bytes {}-{} of {})",
189 chunk_num,
190 total_chunks,
191 filename,
192 chunk.start_byte,
193 chunk.end_byte,
194 content_len
195 );
196
197 let mut chunk_tags = tags.clone().unwrap_or_default();
198 chunk_tags.push(format!("chunk_{}", chunk_num));
199 chunk_tags.push(format!("file_{}", filename));
200 chunk_tags.push("byte_chunked".to_string());
201
202 let id = self
203 .storage
204 .store_chunk(
205 &chunk.content,
206 context,
207 summary,
208 Some(chunk_tags),
209 chunk_num as i32,
210 total_chunks as i32,
211 parent_id,
212 )
213 .await?;
214
215 stored_ids.push(id.to_string());
216 }
217 }
218
219 Ok(json!({
220 "file_path": file_path,
221 "file_size": content_len,
222 "chunks_created": stored_ids.len(),
223 "chunk_ids": stored_ids,
224 "message": format!("Successfully ingested {} as {} chunk(s)", filename, stored_ids.len())
225 }))
226 }
227}