Skip to main content

locus_mcp/tools/
evict_nodes.rs

1use 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}