locus_mcp/tools/
evict_nodes.rs1use locus_sdk::application::memory_evict::MemoryEvictService;
2use locus_sdk::domain::evict::{MemoryEvictMode, MemoryEvictRequest};
3use locus_sdk::domain::memory::{MemoryFilter, MemoryScope};
4use serde_json::json;
5use tracing::error;
6
7use crate::{EvictNodesRequest, SttpMcpServer, to_json_string, tool_error};
8
9pub(crate) async fn execute(server: &SttpMcpServer, request: EvictNodesRequest) -> String {
10 let session_id = request.session_id.trim();
11 if session_id.is_empty() {
12 return tool_error("InvalidArgument", "session_id is required");
13 }
14
15 let mode = if request.purge_session.unwrap_or(false) {
16 MemoryEvictMode::PurgeSession
17 } else if request
18 .sync_keys
19 .as_ref()
20 .is_some_and(|keys| !keys.is_empty())
21 {
22 MemoryEvictMode::BySyncKeys
23 } else if request
24 .node_ids
25 .as_ref()
26 .is_some_and(|ids| !ids.is_empty())
27 {
28 MemoryEvictMode::ByNodeIds
29 } else if request.semantic_tags.is_some()
30 || request.link_rel.is_some()
31 || request.link_target.is_some()
32 || request.links_to_ref.is_some()
33 || request.tag_prefix.is_some()
34 || request.has_semantic_links.is_some()
35 {
36 MemoryEvictMode::ByFilter
37 } else {
38 return tool_error(
39 "InvalidArgument",
40 "provide sync_keys, node_ids, semantic filter fields, or purge_session=true",
41 );
42 };
43
44 let purge_session = matches!(mode, MemoryEvictMode::PurgeSession);
45 let include_calibration = request.include_calibration.unwrap_or(purge_session);
46 let include_checkpoints = request.include_checkpoints.unwrap_or(purge_session);
47
48 let evict_service = MemoryEvictService::new(server.node_store.clone())
49 .with_semantic_index(server.semantic_index.clone());
50
51 let result = match evict_service
52 .execute(&MemoryEvictRequest {
53 mode,
54 scope: MemoryScope {
55 session_ids: Some(vec![session_id.to_string()]),
56 ..Default::default()
57 },
58 filter: MemoryFilter {
59 indexed_tags: request.semantic_tags,
60 link_rel: request.link_rel,
61 link_target: request.link_target,
62 links_to_ref: request.links_to_ref,
63 tag_prefix: request.tag_prefix,
64 has_semantic_links: request.has_semantic_links,
65 ..Default::default()
66 },
67 sync_keys: request.sync_keys,
68 node_ids: request.node_ids,
69 dry_run: request.dry_run.unwrap_or(false),
70 force: request.force.unwrap_or(false),
71 max_nodes: request.max_nodes.unwrap_or(5000),
72 include_calibration,
73 include_checkpoints,
74 })
75 .await
76 {
77 Ok(result) => result,
78 Err(err) => {
79 error!(error = %err, "evict_nodes failed");
80 return tool_error("EvictNodesFailure", &err.to_string());
81 }
82 };
83
84 to_json_string(json!({
85 "dryRun": result.dry_run,
86 "deleted": result.deleted,
87 "blocked": result.blocked,
88 "notFound": result.not_found,
89 "skipped": result.skipped,
90 "wouldDelete": result.would_delete,
91 "calibrationsDeleted": result.calibrations_deleted,
92 "checkpointsDeleted": result.checkpoints_deleted,
93 "records": result.records,
94 }))
95}