Skip to main content

do_memory_mcp/server/tools/
episode_create.rs

1//! Episode creation tool for MCP server
2//!
3//! This module provides the tool for creating new episodes to track
4//! task execution programmatically.
5
6use crate::server::MemoryMCPServer;
7use anyhow::{Result, anyhow};
8use do_memory_core::{TaskContext, TaskType};
9use serde_json::{Value, json};
10use tracing::debug;
11use tracing::info;
12
13/// Parse complexity string to enum
14fn parse_complexity(s: &str) -> do_memory_core::ComplexityLevel {
15    match s {
16        "simple" => do_memory_core::ComplexityLevel::Simple,
17        "moderate" => do_memory_core::ComplexityLevel::Moderate,
18        "complex" => do_memory_core::ComplexityLevel::Complex,
19        _ => do_memory_core::ComplexityLevel::Moderate,
20    }
21}
22
23/// Parse task type string to enum
24fn parse_task_type(task_type: &str) -> Result<TaskType> {
25    match task_type {
26        "code_generation" => Ok(TaskType::CodeGeneration),
27        "debugging" => Ok(TaskType::Debugging),
28        "refactoring" => Ok(TaskType::Refactoring),
29        "testing" => Ok(TaskType::Testing),
30        "analysis" => Ok(TaskType::Analysis),
31        "documentation" => Ok(TaskType::Documentation),
32        _ => Err(anyhow!(
33            "Invalid task_type: {}. Must be one of: code_generation, debugging, refactoring, testing, analysis, documentation",
34            task_type
35        )),
36    }
37}
38
39impl MemoryMCPServer {
40    /// Create a new episode
41    ///
42    /// This tool allows AI agents to programmatically create new episodes
43    /// for tracking task execution through the MCP interface.
44    ///
45    /// # Arguments (from JSON)
46    ///
47    /// * `task_description` - Clear description of the task
48    /// * `domain` - Task domain (e.g., "web-api", "cli")
49    /// * `task_type` - Type of task (e.g., "code_generation", "debugging")
50    /// * `language` - Optional programming language
51    /// * `framework` - Optional framework name
52    /// * `tags` - Optional array of tags
53    /// * `complexity` - Optional complexity level ("simple", "moderate", "complex")
54    pub async fn create_episode_tool(&self, args: Value) -> Result<Value> {
55        debug!("Creating episode with args: {}", args);
56
57        // Extract required fields
58        let task_description = args
59            .get("task_description")
60            .and_then(|v| v.as_str())
61            .ok_or_else(|| anyhow!("Missing required field: task_description"))?
62            .to_string();
63
64        let domain = args
65            .get("domain")
66            .and_then(|v| v.as_str())
67            .ok_or_else(|| anyhow!("Missing required field: domain"))?
68            .to_string();
69
70        let task_type_str = args
71            .get("task_type")
72            .and_then(|v| v.as_str())
73            .ok_or_else(|| anyhow!("Missing required field: task_type"))?;
74
75        // Parse task type
76        let task_type = parse_task_type(task_type_str)?;
77
78        // Extract optional fields
79        let language = args
80            .get("language")
81            .and_then(|v| v.as_str())
82            .map(|s| s.to_string());
83
84        let framework = args
85            .get("framework")
86            .and_then(|v| v.as_str())
87            .map(|s| s.to_string());
88
89        let tags = args
90            .get("tags")
91            .and_then(|v| v.as_array())
92            .map(|arr| {
93                arr.iter()
94                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
95                    .collect()
96            })
97            .unwrap_or_default();
98
99        let complexity = args
100            .get("complexity")
101            .and_then(|v| v.as_str())
102            .map(parse_complexity)
103            .unwrap_or(do_memory_core::ComplexityLevel::Moderate);
104
105        // Create task context
106        let context = TaskContext {
107            language,
108            framework,
109            complexity,
110            domain: domain.clone(),
111            tags,
112        };
113
114        // Start the episode
115        let episode_id = self
116            .memory
117            .start_episode(task_description.clone(), context, task_type)
118            .await;
119
120        info!(
121            episode_id = %episode_id,
122            task_description = %task_description,
123            domain = %domain,
124            "Created new episode via MCP"
125        );
126
127        Ok(json!({
128            "success": true,
129            "episode_id": episode_id.to_string(),
130            "task_description": task_description,
131            "domain": domain,
132            "task_type": task_type_str,
133            "message": "Episode created successfully"
134        }))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_parse_task_type_valid() {
144        assert!(parse_task_type("code_generation").is_ok());
145        assert!(parse_task_type("debugging").is_ok());
146        assert!(parse_task_type("refactoring").is_ok());
147        assert!(parse_task_type("testing").is_ok());
148        assert!(parse_task_type("analysis").is_ok());
149        assert!(parse_task_type("documentation").is_ok());
150    }
151
152    #[test]
153    fn test_parse_task_type_invalid() {
154        assert!(parse_task_type("invalid").is_err());
155        assert!(parse_task_type("").is_err());
156    }
157
158    #[test]
159    fn test_parse_complexity() {
160        assert_eq!(
161            parse_complexity("simple"),
162            do_memory_core::ComplexityLevel::Simple
163        );
164        assert_eq!(
165            parse_complexity("moderate"),
166            do_memory_core::ComplexityLevel::Moderate
167        );
168        assert_eq!(
169            parse_complexity("complex"),
170            do_memory_core::ComplexityLevel::Complex
171        );
172        assert_eq!(
173            parse_complexity("unknown"),
174            do_memory_core::ComplexityLevel::Moderate
175        );
176    }
177}