Skip to main content

locus_mcp/tools/
list_nodes.rs

1use locus_sdk::application::memory_find::MemoryFindService;
2use locus_sdk::domain::memory::{MemoryFilter, MemoryFindRequest, MemoryPage, MemoryScope};
3use serde_json::json;
4use tracing::error;
5
6use crate::{
7    ListNodesRequest, SttpMcpServer, expanded_limit, filter_nodes_by_context_keywords,
8    normalize_context_keywords, sttp_node_to_json, to_json_string, tool_error, validate_limit,
9};
10
11pub(crate) async fn execute(server: &SttpMcpServer, request: ListNodesRequest) -> String {
12    let limit = match validate_limit(request.limit, "limit") {
13        Ok(value) => value,
14        Err(message) => return tool_error("InvalidArgument", &message),
15    };
16    let context_keywords = normalize_context_keywords(request.context_keywords.as_deref());
17    let query_limit = if context_keywords.is_empty() {
18        limit
19    } else {
20        expanded_limit(limit)
21    };
22
23    let find_service = MemoryFindService::new(server.node_store.clone())
24        .with_semantic_index(server.semantic_index.clone());
25    let find_result = match find_service
26        .execute(&MemoryFindRequest {
27            scope: MemoryScope {
28                tenant_id: None,
29                session_ids: request.session_id.map(|session| vec![session]),
30                tiers: None,
31                from_utc: None,
32                to_utc: None,
33            },
34            filter: MemoryFilter {
35                indexed_tags: request.semantic_tags,
36                link_rel: request.link_rel,
37                link_target: request.link_target,
38                ..Default::default()
39            },
40            page: MemoryPage {
41                limit: query_limit,
42                cursor: None,
43            },
44            ..Default::default()
45        })
46        .await
47    {
48        Ok(result) => result,
49        Err(err) => {
50            error!(error = %err, "list_nodes failed");
51            return tool_error("ListNodesFailure", &err.to_string());
52        }
53    };
54
55    let nodes = if context_keywords.is_empty() {
56        find_result
57            .nodes
58            .into_iter()
59            .take(limit)
60            .collect::<Vec<_>>()
61    } else {
62        filter_nodes_by_context_keywords(&find_result.nodes, &context_keywords, limit)
63    };
64
65    to_json_string(json!({
66        "retrieved": nodes.len(),
67        "nodes": nodes
68            .iter()
69            .map(sttp_node_to_json)
70            .collect::<Vec<_>>()
71    }))
72}