agentic_memory_mcp/tools/
memory_evidence.rs1use 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
25pub 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
52pub 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 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}