1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! Episode retrieval and deletion tools for MCP server
//!
//! This module provides tools for getting episode details and deleting
//! episodes permanently.
use crate::server::MemoryMCPServer;
use anyhow::{Result, anyhow};
use serde_json::{Value, json};
use tracing::debug;
use tracing::warn;
use uuid::Uuid;
impl MemoryMCPServer {
/// Get episode details by ID
///
/// This tool retrieves complete details of an episode including
/// all steps, outcome, reflection, and extracted patterns.
///
/// # Arguments (from JSON)
///
/// * `episode_id` - UUID of the episode to retrieve
/// * `fields` - Optional array of field paths to return (e.g., ["episode.id", "episode.task_description"])
///
/// # Field Selection
///
/// Clients can request specific fields to reduce token usage:
/// ```json
/// {
/// "episode_id": "123e4567-e89b-12d3-a456-426614174000",
/// "fields": ["episode.id", "episode.task_description", "episode.outcome"]
/// }
/// ```
pub async fn get_episode_tool(&self, args: Value) -> Result<Value> {
debug!("Getting episode with args: {}", args);
let episode_id_str = args
.get("episode_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("Missing required field: episode_id"))?;
let episode_id = Uuid::parse_str(episode_id_str)
.map_err(|e| anyhow!("Invalid episode_id format: {}", e))?;
let episode = self
.memory
.get_episode(episode_id)
.await
.map_err(|e| anyhow!("Failed to get episode: {}", e))?;
// Convert episode to JSON
let episode_json = serde_json::to_value(&episode)
.map_err(|e| anyhow!("Failed to serialize episode: {}", e))?;
let result = json!({
"success": true,
"episode": episode_json
});
// Apply field projection if requested
if let Some(fields_value) = args.get("fields") {
if let Some(field_array) = fields_value.as_array() {
let field_list: Vec<String> = field_array
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
if !field_list.is_empty() {
use crate::server::tools::field_projection::FieldSelector;
let selector = FieldSelector::new(field_list.into_iter().collect());
return selector.apply(&result);
}
}
}
Ok(result)
}
/// Delete an episode permanently
///
/// This tool removes an episode from all storage backends.
/// **Warning**: This operation cannot be undone.
///
/// # Arguments (from JSON)
///
/// * `episode_id` - UUID of the episode to delete
/// * `confirm` - Must be set to true to confirm deletion
pub async fn delete_episode_tool(&self, args: Value) -> Result<Value> {
debug!("Deleting episode with args: {}", args);
let episode_id_str = args
.get("episode_id")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow!("Missing required field: episode_id"))?;
let episode_id = Uuid::parse_str(episode_id_str)
.map_err(|e| anyhow!("Invalid episode_id format: {}", e))?;
// Require explicit confirmation
let confirm = args
.get("confirm")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !confirm {
return Err(anyhow!(
"Deletion requires explicit confirmation. Set 'confirm' to true."
));
}
self.memory
.delete_episode(episode_id)
.await
.map_err(|e| anyhow!("Failed to delete episode: {}", e))?;
warn!(
episode_id = %episode_id,
"Deleted episode via MCP (permanent)"
);
Ok(json!({
"success": true,
"episode_id": episode_id.to_string(),
"message": "Episode deleted permanently"
}))
}
}