do_memory_mcp/server/tools/
episode_relationships.rs1use crate::mcp::tools::episode_relationships::{
14 AddEpisodeRelationshipInput, CheckRelationshipExistsInput, DependencyGraphInput,
15 EpisodeRelationshipTools, FindRelatedEpisodesInput, GetEpisodeRelationshipsInput,
16 GetTopologicalOrderInput, RemoveEpisodeRelationshipInput, ValidateNoCyclesInput,
17};
18use crate::server::MemoryMCPServer;
19use anyhow::Result;
20use serde_json::Value;
21use tracing::{debug, info};
22
23impl MemoryMCPServer {
24 pub async fn add_episode_relationship_tool(&self, args: Value) -> Result<Value> {
39 debug!("Adding episode relationship with args: {}", args);
40
41 let input: AddEpisodeRelationshipInput = serde_json::from_value(args)?;
42 let from_id = input.from_episode_id.clone();
43 let to_id = input.to_episode_id.clone();
44 let rel_type = input.relationship_type.clone();
45 let client_id = input
46 .created_by
47 .clone()
48 .unwrap_or_else(|| "unknown".to_string());
49
50 let tools = EpisodeRelationshipTools::new(self.memory());
51 let result = tools.add_relationship(input).await;
52
53 let audit_logger = self.audit_logger();
55 match &result {
56 Ok(r) => {
57 audit_logger
58 .log_add_relationship(
59 &client_id,
60 &from_id,
61 &to_id,
62 &rel_type,
63 &r.relationship_id,
64 true,
65 )
66 .await;
67
68 info!(
69 relationship_id = %r.relationship_id,
70 from_episode_id = %from_id,
71 to_episode_id = %to_id,
72 relationship_type = %rel_type,
73 "Created episode relationship via MCP"
74 );
75 }
76 Err(e) => {
77 audit_logger
78 .log_add_relationship(&client_id, &from_id, &to_id, &rel_type, "none", false)
79 .await;
80
81 debug!("Failed to create episode relationship: {}", e);
82 }
83 }
84
85 let value = result?;
86 serde_json::to_value(value).map_err(anyhow::Error::from)
87 }
88
89 pub async fn remove_episode_relationship_tool(&self, args: Value) -> Result<Value> {
97 debug!("Removing episode relationship with args: {}", args);
98
99 let input: RemoveEpisodeRelationshipInput = serde_json::from_value(args)?;
100 let relationship_id = input.relationship_id.clone();
101 let client_id = "mcp_client".to_string();
102
103 let tools = EpisodeRelationshipTools::new(self.memory());
104 let result = tools.remove_relationship(input).await;
105
106 let audit_logger = self.audit_logger();
108 match &result {
109 Ok(_) => {
110 audit_logger
111 .log_remove_relationship(&client_id, &relationship_id, true)
112 .await;
113
114 info!(relationship_id = %relationship_id, "Removed episode relationship via MCP");
115 }
116 Err(e) => {
117 audit_logger
118 .log_remove_relationship(&client_id, &relationship_id, false)
119 .await;
120
121 debug!("Failed to remove episode relationship: {}", e);
122 }
123 }
124
125 let value = result?;
126 serde_json::to_value(value).map_err(anyhow::Error::from)
127 }
128
129 pub async fn get_episode_relationships_tool(&self, args: Value) -> Result<Value> {
140 debug!("Getting episode relationships with args: {}", args);
141
142 let input: GetEpisodeRelationshipsInput = serde_json::from_value(args)?;
143 let episode_id = input.episode_id.clone();
144 let client_id = "mcp_client".to_string();
145
146 let tools = EpisodeRelationshipTools::new(self.memory());
147 let result = tools.get_relationships(input).await;
148
149 let audit_logger = self.audit_logger();
151 match &result {
152 Ok(r) => {
153 let total_count = r.outgoing.len() + r.incoming.len();
154 audit_logger
155 .log_get_relationships(&client_id, &episode_id, total_count, true)
156 .await;
157
158 info!(
159 episode_id = %episode_id,
160 outgoing_count = r.outgoing.len(),
161 incoming_count = r.incoming.len(),
162 "Retrieved episode relationships via MCP"
163 );
164 }
165 Err(e) => {
166 audit_logger
167 .log_get_relationships(&client_id, &episode_id, 0, false)
168 .await;
169
170 debug!("Failed to get episode relationships: {}", e);
171 }
172 }
173
174 let value = result?;
175 serde_json::to_value(value).map_err(anyhow::Error::from)
176 }
177
178 pub async fn find_related_episodes_tool(&self, args: Value) -> Result<Value> {
190 debug!("Finding related episodes with args: {}", args);
191
192 let input: FindRelatedEpisodesInput = serde_json::from_value(args)?;
193 let episode_id = input.episode_id.clone();
194 let client_id = "mcp_client".to_string();
195
196 let tools = EpisodeRelationshipTools::new(self.memory());
197 let result = tools.find_related(input).await;
198
199 let audit_logger = self.audit_logger();
201 match &result {
202 Ok(r) => {
203 audit_logger
204 .log_find_related(&client_id, &episode_id, r.count, true)
205 .await;
206
207 info!(episode_id = %episode_id, related_count = r.count, "Found related episodes via MCP");
208 }
209 Err(e) => {
210 audit_logger
211 .log_find_related(&client_id, &episode_id, 0, false)
212 .await;
213
214 debug!("Failed to find related episodes: {}", e);
215 }
216 }
217
218 let value = result?;
219 serde_json::to_value(value).map_err(anyhow::Error::from)
220 }
221
222 pub async fn check_relationship_exists_tool(&self, args: Value) -> Result<Value> {
233 debug!("Checking relationship exists with args: {}", args);
234
235 let input: CheckRelationshipExistsInput = serde_json::from_value(args)?;
236 let from_id = input.from_episode_id.clone();
237 let to_id = input.to_episode_id.clone();
238 let rel_type = input.relationship_type.clone();
239 let client_id = "mcp_client".to_string();
240
241 let tools = EpisodeRelationshipTools::new(self.memory());
242 let result = tools.check_exists(input).await;
243
244 let audit_logger = self.audit_logger();
246 match &result {
247 Ok(c) => {
248 audit_logger
249 .log_check_relationship(&client_id, &from_id, &to_id, &rel_type, c.exists, true)
250 .await;
251
252 info!(
253 from_episode_id = %from_id,
254 to_episode_id = %to_id,
255 relationship_type = %rel_type,
256 exists = c.exists,
257 "Checked relationship existence via MCP"
258 );
259 }
260 Err(e) => {
261 audit_logger
262 .log_check_relationship(&client_id, &from_id, &to_id, &rel_type, false, false)
263 .await;
264
265 debug!("Failed to check relationship exists: {}", e);
266 }
267 }
268
269 let value = result?;
270 serde_json::to_value(value).map_err(anyhow::Error::from)
271 }
272
273 pub async fn get_dependency_graph_tool(&self, args: Value) -> Result<Value> {
284 debug!("Getting dependency graph with args: {}", args);
285
286 let input: DependencyGraphInput = serde_json::from_value(args)?;
287 let episode_id = input.episode_id.clone();
288 let format = input.format.clone().unwrap_or_else(|| "json".to_string());
289 let client_id = "mcp_client".to_string();
290
291 let tools = EpisodeRelationshipTools::new(self.memory());
292 let result = tools.get_dependency_graph(input).await;
293
294 let audit_logger = self.audit_logger();
296 match &result {
297 Ok(g) => {
298 audit_logger
299 .log_dependency_graph(&client_id, &episode_id, g.node_count, g.edge_count, true)
300 .await;
301
302 info!(
303 episode_id = %episode_id,
304 node_count = g.node_count,
305 edge_count = g.edge_count,
306 format = %format,
307 "Retrieved dependency graph via MCP"
308 );
309 }
310 Err(e) => {
311 audit_logger
312 .log_dependency_graph(&client_id, &episode_id, 0, 0, false)
313 .await;
314
315 debug!("Failed to get dependency graph: {}", e);
316 }
317 }
318
319 let value = result?;
320 serde_json::to_value(value).map_err(anyhow::Error::from)
321 }
322
323 pub async fn validate_no_cycles_tool(&self, args: Value) -> Result<Value> {
335 debug!("Validating no cycles with args: {}", args);
336
337 let input: ValidateNoCyclesInput = serde_json::from_value(args)?;
338 let from_id = input.from_episode_id.clone();
339 let to_id = input.to_episode_id.clone();
340 let rel_type = input.relationship_type.clone();
341 let client_id = "mcp_client".to_string();
342
343 let tools = EpisodeRelationshipTools::new(self.memory());
344 let result = tools.validate_no_cycles(input).await;
345
346 let audit_logger = self.audit_logger();
348 match &result {
349 Ok(v) => {
350 audit_logger
351 .log_validate_cycles(
352 &client_id,
353 &from_id,
354 &to_id,
355 &rel_type,
356 v.would_create_cycle,
357 true,
358 )
359 .await;
360
361 info!(
362 from_episode_id = %from_id,
363 to_episode_id = %to_id,
364 relationship_type = %rel_type,
365 would_create_cycle = v.would_create_cycle,
366 is_valid = v.is_valid,
367 "Validated cycle absence via MCP"
368 );
369 }
370 Err(e) => {
371 audit_logger
372 .log_validate_cycles(&client_id, &from_id, &to_id, &rel_type, false, false)
373 .await;
374
375 debug!("Failed to validate no cycles: {}", e);
376 }
377 }
378
379 let value = result?;
380 serde_json::to_value(value).map_err(anyhow::Error::from)
381 }
382
383 pub async fn get_topological_order_tool(&self, args: Value) -> Result<Value> {
392 debug!("Getting topological order with args: {}", args);
393
394 let input: GetTopologicalOrderInput = serde_json::from_value(args)?;
395 let episode_count = input.episode_ids.len();
396 let client_id = "mcp_client".to_string();
397
398 let tools = EpisodeRelationshipTools::new(self.memory());
399 let result = tools.get_topological_order(input).await;
400
401 let audit_logger = self.audit_logger();
403 match &result {
404 Ok(o) => {
405 audit_logger
406 .log_topological_order(&client_id, episode_count, o.count, o.has_cycles, true)
407 .await;
408
409 info!(
410 input_count = episode_count,
411 output_count = o.count,
412 has_cycles = o.has_cycles,
413 "Computed topological order via MCP"
414 );
415 }
416 Err(e) => {
417 audit_logger
418 .log_topological_order(&client_id, episode_count, 0, false, false)
419 .await;
420
421 debug!("Failed to get topological order: {}", e);
422 }
423 }
424
425 let value = result?;
426 serde_json::to_value(value).map_err(anyhow::Error::from)
427 }
428}
429
430#[cfg(test)]
431mod tests;