Skip to main content

agentic_memory_mcp/tools/
memory_evidence.rs

1//! Tool: memory_evidence — Get detailed evidence for a claim from memory.
2
3use std::sync::Arc;
4use tokio::sync::Mutex;
5
6use serde::Deserialize;
7use serde_json::{json, Value};
8
9use agentic_memory::TextSearchParams;
10
11use crate::session::SessionManager;
12use crate::types::{McpError, McpResult, ToolCallResult, ToolDefinition};
13
14#[derive(Debug, Deserialize)]
15struct EvidenceParams {
16    query: String,
17    #[serde(default = "default_max")]
18    max_results: usize,
19}
20
21fn default_max() -> usize {
22    10
23}
24
25/// Return the tool definition for memory_evidence.
26pub fn definition() -> ToolDefinition {
27    ToolDefinition {
28        name: "memory_evidence".to_string(),
29        description: Some(
30            "Get detailed evidence for a claim from stored memories. Returns matching \
31             memory nodes with full content, timestamps, sessions, and relationships."
32                .to_string(),
33        ),
34        input_schema: json!({
35            "type": "object",
36            "required": ["query"],
37            "properties": {
38                "query": {
39                    "type": "string",
40                    "description": "The query to search evidence for"
41                },
42                "max_results": {
43                    "type": "integer",
44                    "default": 10,
45                    "description": "Maximum number of evidence items to return"
46                }
47            }
48        }),
49    }
50}
51
52/// Execute the memory_evidence tool.
53pub async fn execute(
54    args: Value,
55    session: &Arc<Mutex<SessionManager>>,
56) -> McpResult<ToolCallResult> {
57    let params: EvidenceParams =
58        serde_json::from_value(args).map_err(|e| McpError::InvalidParams(e.to_string()))?;
59
60    if params.query.trim().is_empty() {
61        return Ok(ToolCallResult::json(&json!({
62            "count": 0,
63            "evidence": []
64        })));
65    }
66
67    let session = session.lock().await;
68    let graph = session.graph();
69
70    let results = session
71        .query_engine()
72        .text_search(
73            graph,
74            graph.term_index.as_ref(),
75            graph.doc_lengths.as_ref(),
76            TextSearchParams {
77                query: params.query.clone(),
78                max_results: params.max_results,
79                event_types: Vec::new(),
80                session_ids: Vec::new(),
81                min_score: 0.0,
82            },
83        )
84        .map_err(|e| McpError::AgenticMemory(format!("Evidence search failed: {e}")))?;
85
86    let evidence: Vec<Value> = results
87        .iter()
88        .filter_map(|m| {
89            graph.get_node(m.node_id).map(|node| {
90                // Get related edges
91                let outgoing: Vec<Value> = graph
92                    .edges_from(node.id)
93                    .iter()
94                    .map(|e| {
95                        json!({
96                            "target_id": e.target_id,
97                            "edge_type": e.edge_type.name(),
98                            "weight": e.weight,
99                        })
100                    })
101                    .collect();
102
103                let incoming: Vec<Value> = graph
104                    .edges_to(node.id)
105                    .iter()
106                    .map(|e| {
107                        json!({
108                            "source_id": e.source_id,
109                            "edge_type": e.edge_type.name(),
110                            "weight": e.weight,
111                        })
112                    })
113                    .collect();
114
115                json!({
116                    "node_id": node.id,
117                    "event_type": node.event_type.name(),
118                    "content": node.content,
119                    "confidence": node.confidence,
120                    "session_id": node.session_id,
121                    "created_at": node.created_at,
122                    "last_accessed": node.last_accessed,
123                    "access_count": node.access_count,
124                    "decay_score": node.decay_score,
125                    "score": m.score,
126                    "matched_terms": m.matched_terms,
127                    "outgoing_edges": outgoing,
128                    "incoming_edges": incoming,
129                    "source": format!("session:{}", node.session_id),
130                })
131            })
132        })
133        .collect();
134
135    Ok(ToolCallResult::json(&json!({
136        "query": params.query,
137        "count": evidence.len(),
138        "evidence": evidence
139    })))
140}