1use crate::chunking::FileChunker;
3use crate::error::Result;
4use crate::models::{SearchParams, SearchStrategy};
5use crate::storage::Storage;
6use serde_json::{json, Value};
7use std::path::Path;
8use std::sync::Arc;
9use uuid::Uuid;
10
11pub struct MCPHandlers {
13 storage: Arc<Storage>,
14}
15
16impl MCPHandlers {
17 pub fn new(storage: Arc<Storage>) -> Self {
19 Self { storage }
20 }
21
22 pub async fn handle_tool_call(&self, tool_name: &str, params: Value) -> Result<Value> {
24 match tool_name {
25 "store_memory" => self.handle_store_memory(params).await,
26 "get_memory" => self.handle_get_memory(params).await,
27 "delete_memory" => self.handle_delete_memory(params).await,
28 "get_statistics" => self.handle_get_statistics().await,
29 "store_file" => self.handle_store_file(params).await,
30 "search_memory" => self.handle_search_memory(params).await,
31 _ => Err(crate::error::Error::Other(format!(
32 "Unknown tool: {}",
33 tool_name
34 ))),
35 }
36 }
37
38 async fn handle_store_memory(&self, params: Value) -> Result<Value> {
39 let content = params["content"]
40 .as_str()
41 .ok_or_else(|| crate::error::Error::Other("Missing content parameter".to_string()))?;
42
43 let context = params["context"]
45 .as_str()
46 .ok_or_else(|| {
47 crate::error::Error::Other("Missing required context parameter".to_string())
48 })?
49 .to_string();
50
51 let summary = params["summary"]
53 .as_str()
54 .ok_or_else(|| {
55 crate::error::Error::Other("Missing required summary parameter".to_string())
56 })?
57 .to_string();
58
59 let tags = params["tags"]
61 .as_array()
62 .ok_or_else(|| {
63 crate::error::Error::Other("Missing required tags parameter".to_string())
64 })?
65 .iter()
66 .filter_map(|v| v.as_str().map(String::from))
67 .collect::<Vec<_>>();
68
69 let id = self
70 .storage
71 .store(content, context, summary, Some(tags))
72 .await?;
73
74 Ok(json!({
75 "id": id.to_string(),
76 "message": "Memory stored successfully"
77 }))
78 }
79
80 async fn handle_get_memory(&self, params: Value) -> Result<Value> {
81 let id_str = params["id"]
82 .as_str()
83 .ok_or_else(|| crate::error::Error::Other("Missing id parameter".to_string()))?;
84
85 let id = Uuid::parse_str(id_str)
86 .map_err(|e| crate::error::Error::Other(format!("Invalid UUID: {}", e)))?;
87
88 match self.storage.get(id).await? {
89 Some(memory) => Ok(serde_json::to_value(memory)?),
90 None => Err(crate::error::Error::Other(format!(
91 "Memory not found: {}",
92 id
93 ))),
94 }
95 }
96
97 async fn handle_delete_memory(&self, params: Value) -> Result<Value> {
98 let id_str = params["id"]
99 .as_str()
100 .ok_or_else(|| crate::error::Error::Other("Missing id parameter".to_string()))?;
101
102 let id = Uuid::parse_str(id_str)
103 .map_err(|e| crate::error::Error::Other(format!("Invalid UUID: {}", e)))?;
104
105 let deleted = self.storage.delete(id).await?;
106
107 Ok(json!({
108 "deleted": deleted,
109 "message": if deleted { "Memory deleted successfully" } else { "Memory not found" }
110 }))
111 }
112
113 async fn handle_get_statistics(&self) -> Result<Value> {
114 let stats = self.storage.stats().await?;
115 Ok(serde_json::to_value(stats)?)
116 }
117
118 async fn handle_store_file(&self, params: Value) -> Result<Value> {
119 let file_path = params["file_path"]
120 .as_str()
121 .ok_or_else(|| crate::error::Error::Other("Missing file_path parameter".to_string()))?;
122
123 let chunk_size = params
124 .get("chunk_size")
125 .and_then(|v| v.as_u64())
126 .unwrap_or(8000) as usize;
127
128 let overlap = params
129 .get("overlap")
130 .and_then(|v| v.as_u64())
131 .unwrap_or(200) as usize;
132
133 let tags = params.get("tags").and_then(|v| v.as_array()).map(|arr| {
138 arr.iter()
139 .filter_map(|v| v.as_str().map(String::from))
140 .collect::<Vec<_>>()
141 });
142
143 let content = tokio::fs::read_to_string(file_path)
145 .await
146 .map_err(|e| crate::error::Error::Other(format!("Failed to read file: {}", e)))?;
147
148 let filename = Path::new(file_path)
150 .file_name()
151 .and_then(|n| n.to_str())
152 .unwrap_or("unknown");
153
154 let content_len = content.len();
156 let mut stored_ids = Vec::new();
157
158 let chunker = FileChunker::new(chunk_size, overlap);
160 let chunks = chunker.chunk_content(&content)?;
161
162 if chunks.len() == 1 {
163 let context = format!("Content from file: {}", filename);
165 let summary = format!(
166 "Complete content of {} ({} characters)",
167 filename, content_len
168 );
169
170 let id = self
171 .storage
172 .store(&content, context, summary, tags.clone())
173 .await?;
174
175 stored_ids.push(id.to_string());
176 } else {
177 let parent_id = Uuid::new_v4();
179 let total_chunks = chunks.len();
180
181 for (index, chunk) in chunks.into_iter().enumerate() {
182 let chunk_num = index + 1;
183
184 let context = format!(
185 "Chunk {} of {} from file: {}",
186 chunk_num, total_chunks, filename
187 );
188
189 let summary = format!(
190 "Part {} of {} from {} (bytes {}-{} of {})",
191 chunk_num,
192 total_chunks,
193 filename,
194 chunk.start_byte,
195 chunk.end_byte,
196 content_len
197 );
198
199 let mut chunk_tags = tags.clone().unwrap_or_default();
200 chunk_tags.push(format!("chunk_{}", chunk_num));
201 chunk_tags.push(format!("file_{}", filename));
202 chunk_tags.push("byte_chunked".to_string());
203
204 let id = self
205 .storage
206 .store_chunk(
207 &chunk.content,
208 context,
209 summary,
210 Some(chunk_tags),
211 chunk_num as i32,
212 total_chunks as i32,
213 parent_id,
214 )
215 .await?;
216
217 stored_ids.push(id.to_string());
218 }
219 }
220
221 Ok(json!({
222 "file_path": file_path,
223 "file_size": content_len,
224 "chunks_created": stored_ids.len(),
225 "chunk_ids": stored_ids,
226 "message": format!("Successfully ingested {} as {} chunk(s)", filename, stored_ids.len())
227 }))
228 }
229
230 async fn handle_search_memory(&self, params: Value) -> Result<Value> {
231 let query = params["query"]
232 .as_str()
233 .ok_or_else(|| crate::error::Error::Other("Missing query parameter".to_string()))?
234 .to_string();
235
236 let tag_filter = params
238 .get("tag_filter")
239 .and_then(|v| v.as_array())
240 .map(|arr| {
241 arr.iter()
242 .filter_map(|v| v.as_str().map(String::from))
243 .collect::<Vec<_>>()
244 });
245
246 let use_tag_embedding = params
247 .get("use_tag_embedding")
248 .and_then(|v| v.as_bool())
249 .unwrap_or(true);
250
251 let use_content_embedding = params
252 .get("use_content_embedding")
253 .and_then(|v| v.as_bool())
254 .unwrap_or(true);
255
256 let similarity_threshold = params
257 .get("similarity_threshold")
258 .and_then(|v| v.as_f64())
259 .unwrap_or(0.7)
260 .clamp(0.0, 1.0);
261
262 let max_results = params
263 .get("max_results")
264 .and_then(|v| v.as_u64())
265 .unwrap_or(10)
266 .clamp(1, 100) as usize;
267
268 let search_strategy = params
269 .get("search_strategy")
270 .and_then(|v| v.as_str())
271 .map(|s| match s {
272 "tags_first" => SearchStrategy::TagsFirst,
273 "content_first" => SearchStrategy::ContentFirst,
274 _ => SearchStrategy::Hybrid,
275 })
276 .unwrap_or(SearchStrategy::Hybrid);
277
278 let boost_recent = params
279 .get("boost_recent")
280 .and_then(|v| v.as_bool())
281 .unwrap_or(false);
282
283 let tag_weight = params
284 .get("tag_weight")
285 .and_then(|v| v.as_f64())
286 .unwrap_or(0.4)
287 .clamp(0.0, 1.0);
288
289 let content_weight = params
290 .get("content_weight")
291 .and_then(|v| v.as_f64())
292 .unwrap_or(0.6)
293 .clamp(0.0, 1.0);
294
295 let search_params = SearchParams {
297 query,
298 tag_filter,
299 use_tag_embedding,
300 use_content_embedding,
301 similarity_threshold,
302 max_results,
303 search_strategy,
304 boost_recent,
305 tag_weight,
306 content_weight,
307 };
308
309 let search_start = std::time::Instant::now();
311 let search_result_with_metadata = self
312 .storage
313 .search_memories_progressive_with_metadata(search_params.clone())
314 .await?;
315 let search_duration = search_start.elapsed();
316
317 let formatted_results: Vec<Value> = search_result_with_metadata
319 .results
320 .iter()
321 .map(|result| {
322 json!({
323 "id": result.memory.id,
324 "content": result.memory.content,
325 "context": result.memory.context,
326 "summary": result.memory.summary,
327 "tags": result.memory.tags,
328 "chunk_index": result.memory.chunk_index,
329 "total_chunks": result.memory.total_chunks,
330 "parent_id": result.memory.parent_id,
331 "created_at": result.memory.created_at,
332 "updated_at": result.memory.updated_at,
333 "tag_similarity": result.tag_similarity,
334 "content_similarity": result.content_similarity,
335 "combined_score": result.combined_score,
336 "semantic_cluster": result.semantic_cluster
337 })
338 })
339 .collect();
340
341 Ok(json!({
342 "results": formatted_results,
343 "search_metadata": {
344 "query": search_params.query,
345 "total_results": search_result_with_metadata.results.len(),
346 "search_strategy": match search_params.search_strategy {
347 SearchStrategy::TagsFirst => "tags_first",
348 SearchStrategy::ContentFirst => "content_first",
349 SearchStrategy::Hybrid => "hybrid",
350 },
351 "similarity_threshold": search_params.similarity_threshold,
352 "max_results": search_params.max_results,
353 "tag_filter": search_params.tag_filter,
354 "use_tag_embedding": search_params.use_tag_embedding,
355 "use_content_embedding": search_params.use_content_embedding,
356 "boost_recent": search_params.boost_recent,
357 "tag_weight": search_params.tag_weight,
358 "content_weight": search_params.content_weight,
359 "search_time_ms": search_duration.as_millis(),
360 "progressive_search": {
361 "stage_used": search_result_with_metadata.metadata.stage_used,
362 "stage_description": search_result_with_metadata.metadata.stage_description,
363 "threshold_used": search_result_with_metadata.metadata.threshold_used
364 }
365 }
366 }))
367 }
368}