Skip to main content

do_memory_mcp/server/tools/
episode_get.rs

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