diff --git a/server/src/handlers/graph.rs b/server/src/handlers/graph.rs index 0e41ba5..b5361ef 100644 --- a/server/src/handlers/graph.rs +++ b/server/src/handlers/graph.rs @@ -409,30 +409,48 @@ impl McpTool for DeleteObservationsHandler { async fn execute(&self, args: Value, state: Arc) -> crate::error::Result { let req: DeleteObservationsTool = serde_json::from_value(args).map_err(|e| e.to_string())?; - let mut missing = Vec::new(); - state.read_graph(|g| { - for d in &req.deletions { - if !g.entities.contains_key(&d.entity_name) { - missing.push(d.entity_name.clone()); - } - } - }); - - if !missing.is_empty() { - return Err(crate::error::AppError::Internal(format!( - "Error: Entities not found: {}. Please use the search_nodes or read_graph tools to verify the exact entity names.", - missing.join(", ") - ))); - } - + + let mut missing_entities = Vec::new(); + let mut missing_observations = Vec::new(); + state.modify_graph(|master| { for d in req.deletions { if let Some(e) = master.entities.get_mut(&d.entity_name) { - let to_rem: HashSet<_> = d.observations.into_iter().collect(); - e.observations.retain(|o| !to_rem.contains(o)); + for target in d.observations { + let target_clean = target.trim(); + if target_clean.is_empty() { continue; } + + let initial_len = e.observations.len(); + e.observations.retain(|o| { + let o_clean = o.trim(); + // Exact match OR substantial substring match (to forgive LLM whitespace/newline errors) + !(o_clean == target_clean || (target_clean.len() >= 10 && o_clean.contains(target_clean))) + }); + + if e.observations.len() == initial_len { + missing_observations.push(target); + } + } + } else { + missing_entities.push(d.entity_name); } } }); + + if !missing_entities.is_empty() { + return Err(crate::error::AppError::Internal(format!( + "Error: Entities not found: {}. Please use search_nodes to verify the exact entity names.", + missing_entities.join(", ") + ))); + } + + if !missing_observations.is_empty() { + return Err(crate::error::AppError::Internal(format!( + "Error: Failed to find {} observation(s) to delete. Make sure you provide a sufficiently long exact substring match.", + missing_observations.len() + ))); + } + Ok("Observations deleted".to_string()) } }