use crate::models::*; use crate::router::McpTool; use crate::state::MemoryState; use crate::tools::*; use async_trait::async_trait; use serde_json::Value; use std::collections::HashSet; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; pub struct AddStickyNoteHandler; #[async_trait] impl McpTool for AddStickyNoteHandler { fn name(&self) -> &'static str { "add_sticky_note" } fn schema(&self) -> Value { crate::mcp::tool_def::("add_sticky_note", "Execute add_sticky_note") } async fn execute(&self, args: Value, state: Arc) -> Result { let req: AddStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?; state.sticky.modify(|notes| { notes.push(StickyNote { timestamp: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs(), content: req.content, }); }); Ok("Sticky note added.".to_string()) } } pub struct ReadStickyNotesHandler; #[async_trait] impl McpTool for ReadStickyNotesHandler { fn name(&self) -> &'static str { "read_sticky_notes" } fn schema(&self) -> Value { crate::mcp::tool_def::( "read_sticky_notes", "Execute read_sticky_notes", ) } async fn execute(&self, _args: Value, state: Arc) -> Result { let data = state.sticky.read_with(|s| serde_json::to_string(s).unwrap_or_else(|_| "[]".to_string())); Ok(data.to_string()) } } pub struct DeleteStickyNoteHandler; #[async_trait] impl McpTool for DeleteStickyNoteHandler { fn name(&self) -> &'static str { "delete_sticky_note" } fn schema(&self) -> Value { crate::mcp::tool_def::( "delete_sticky_note", "Execute delete_sticky_note", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: DeleteStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let mut success = false; state.sticky.modify(|notes| { if req.index > 0 && req.index <= notes.len() { notes.remove(req.index - 1); success = true; } }); if success { Ok("Sticky note deleted.".to_string()) } else { Err("Invalid sticky note index.".to_string()) } } } pub struct ClearStickyNotesHandler; #[async_trait] impl McpTool for ClearStickyNotesHandler { fn name(&self) -> &'static str { "clear_sticky_notes" } fn schema(&self) -> Value { crate::mcp::tool_def::( "clear_sticky_notes", "Execute clear_sticky_notes", ) } async fn execute(&self, _args: Value, state: Arc) -> Result { state.sticky.modify(|notes| { notes.clear(); }); Ok("All sticky notes cleared.".to_string()) } } pub struct LeaveHandoffMemoHandler; #[async_trait] impl McpTool for LeaveHandoffMemoHandler { fn name(&self) -> &'static str { "leave_handoff_memo" } fn schema(&self) -> Value { crate::mcp::tool_def::( "leave_handoff_memo", "Execute leave_handoff_memo", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: LeaveHandoffMemoTool = serde_json::from_value(args).map_err(|e| e.to_string())?; state.handoff_memos.modify(|memos| { memos.push(crate::models::HandoffMemo { id: uuid::Uuid::new_v4().to_string(), author: "agy".to_string(), content: req.content, namespace: req.namespace, timestamp: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs(), }) }); Ok("Handoff memo left".to_string()) } } pub struct ReadHandoffMemosHandler; #[async_trait] impl McpTool for ReadHandoffMemosHandler { fn name(&self) -> &'static str { "read_handoff_memos" } fn schema(&self) -> Value { crate::mcp::tool_def::( "read_handoff_memos", "Execute read_handoff_memos", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: ReadHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let data = state.handoff_memos.read_with(|items| { let filtered: Vec<_> = items.iter().filter(|i| { if let Some(ns) = &req.namespace { &i.namespace == ns } else { true } }).collect(); serde_json::to_string(&filtered).unwrap_or_default() }); Ok(data.to_string()) } } pub struct ClearHandoffMemosHandler; #[async_trait] impl McpTool for ClearHandoffMemosHandler { fn name(&self) -> &'static str { "clear_handoff_memos" } fn schema(&self) -> Value { crate::mcp::tool_def::( "clear_handoff_memos", "Execute clear_handoff_memos", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: ClearHandoffMemosTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let ids: HashSet<_> = req.ids.into_iter().collect(); state .handoff_memos .modify(|memos| memos.retain(|m| !ids.contains(&m.id))); Ok("Handoff memos cleared".to_string()) } } pub struct AddSessionSummaryHandler; #[async_trait] impl McpTool for AddSessionSummaryHandler { fn name(&self) -> &'static str { "add_session_summary" } fn schema(&self) -> Value { crate::mcp::tool_def::( "add_session_summary", "Execute add_session_summary", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: AddSessionSummaryTool = serde_json::from_value(args).map_err(|e| e.to_string())?; state.session_summaries.modify(|summaries| { summaries.push(crate::models::SessionSummary { summary: req.summary, namespace: req.namespace, timestamp: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs(), }) }); Ok("Session summary added".to_string()) } } pub struct GenerateStandupReportHandler; #[async_trait] impl McpTool for GenerateStandupReportHandler { fn name(&self) -> &'static str { "generate_standup_report" } fn schema(&self) -> Value { crate::mcp::tool_def::( "generate_standup_report", "Execute generate_standup_report", ) } async fn execute(&self, args: Value, state: Arc) -> Result { let req: GenerateStandupReportTool = serde_json::from_value(args).map_err(|e| e.to_string())?; let cutoff = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs() .saturating_sub(req.hours_lookback * 3600); let tasks = state .tasks .read() .into_iter() .filter(|t| t.updated_at >= cutoff) .collect::>(); let changes = state .ledger .read() .into_iter() .filter(|c| c.timestamp >= cutoff) .collect::>(); let summaries = state .session_summaries .read() .into_iter() .filter(|s| s.namespace == req.namespace && s.timestamp >= cutoff) .collect::>(); let report = serde_json::json!({ "tasks_updated": tasks, "code_changes": changes, "session_summaries": summaries }); Ok(report.to_string()) } }