Skip to main content

trusty_memory/tools/
mod.rs

1//! MCP tool surface for trusty-memory.
2//!
3//! Why: Concentrates the public tool contract in one file so changes are
4//! auditable and the MCP schema stays in sync with the implementation.
5//! What: Defines `MemoryMcpServer`, `tool_definitions()` (the MCP
6//! `tools/list` payload), and the in-process tool dispatcher wired to the
7//! real `PalaceRegistry` + retrieval / KG APIs.
8//! Test: `cargo test -p trusty-memory-mcp` validates the schema and dispatch.
9//!
10//! Tools exposed:
11//! - `memory_remember(palace, text, room?, tags?)` -> drawer_id
12//! - `memory_recall(palace, query, top_k?)`        -> Vec<Drawer> (L0+L1+L2)
13//! - `memory_recall_deep(palace, query, top_k?)`   -> Vec<Drawer> (L3 deep)
14//! - `memory_list(palace, room?, tag?, limit?)`    -> Vec<Drawer>
15//! - `memory_forget(palace, drawer_id)`            -> ()
16//! - `palace_create(name, description?)`           -> PalaceId
17//! - `palace_list()`                                -> Vec<PalaceId>
18//! - `palace_info(palace)`                          -> palace metadata + stats
19//! - `kg_assert(palace, subject, predicate, object, confidence?, provenance?)` -> ()
20//! - `kg_query(palace, subject)`                    -> Vec<Triple>
21
22pub mod bm25;
23pub mod definitions;
24pub mod helpers;
25pub mod kg_ops;
26pub mod memory_ops;
27pub mod palace_ops;
28
29// Re-export the public + cross-module surface so external call sites
30// (`crate::tools::X`) and the `super::*` glob in `tools::tests` keep
31// resolving exactly as they did against the former monolithic module.
32pub use bm25::{spawn_bm25_index_worker, Bm25IndexRequest, BM25_INDEX_QUEUE_CAPACITY};
33pub use definitions::{tool_definitions, tool_definitions_with, MemoryMcpServer};
34pub(crate) use helpers::{auto_extract_and_assert, room_label};
35
36// Re-exports used only by the in-crate test module (`super::*`).
37#[cfg(test)]
38pub(crate) use bm25::bm25_index_enqueue;
39#[cfg(test)]
40pub(crate) use helpers::{blocklist_gate, content_gate, dedup_gate, open_palace_handle};
41
42use crate::AppState;
43use anyhow::Result;
44use serde_json::Value;
45
46use kg_ops::{
47    handle_add_alias, handle_discover_aliases, handle_get_prompt_context, handle_kg_assert,
48    handle_kg_bootstrap, handle_kg_gaps, handle_kg_query, handle_list_prompt_facts,
49    handle_remove_prompt_fact, handle_upgrade_tool,
50};
51use memory_ops::{
52    handle_memory_forget, handle_memory_list, handle_memory_note, handle_memory_recall,
53    handle_memory_recall_all, handle_memory_recall_deep, handle_memory_remember,
54    handle_memory_send_message,
55};
56use palace_ops::{
57    handle_palace_compact, handle_palace_create, handle_palace_delete, handle_palace_info,
58    handle_palace_list, handle_palace_update,
59};
60
61/// Dispatch a tool call by name to its real handler.
62///
63/// Why: Centralises the name → handler mapping; every handler now performs a
64/// real read/write against the live `PalaceRegistry` instead of returning a
65/// stub. After issue #227 the body is a thin router — every tool's logic
66/// lives in its own `handle_*` function above so the dispatcher itself is
67/// auditable at a glance.
68/// What: Returns `Ok(Value)` on success, `Err` on unknown tool / bad args /
69/// underlying failure.
70/// Test: `dispatch_palace_create_persists`, `dispatch_remember_then_recall`,
71/// `dispatch_kg_assert_then_query`, `dispatch_unknown_tool_errors`.
72pub async fn dispatch_tool(state: &AppState, name: &str, args: Value) -> Result<Value> {
73    match name {
74        "memory_remember" => handle_memory_remember(state, args).await,
75        "memory_note" => handle_memory_note(state, args).await,
76        "memory_recall" => handle_memory_recall(state, args).await,
77        "memory_recall_deep" => handle_memory_recall_deep(state, args).await,
78        "palace_create" => handle_palace_create(state, args).await,
79        "palace_list" => handle_palace_list(state, args).await,
80        "palace_delete" => handle_palace_delete(state, args).await,
81        "palace_update" => handle_palace_update(state, args).await,
82        "kg_assert" => handle_kg_assert(state, args).await,
83        "add_alias" => handle_add_alias(state, args).await,
84        "list_prompt_facts" => handle_list_prompt_facts(state, args).await,
85        "remove_prompt_fact" => handle_remove_prompt_fact(state, args).await,
86        "kg_query" => handle_kg_query(state, args).await,
87        "memory_list" => handle_memory_list(state, args).await,
88        "memory_forget" => handle_memory_forget(state, args).await,
89        "palace_info" => handle_palace_info(state, args).await,
90        "palace_compact" => handle_palace_compact(state, args).await,
91        "kg_gaps" => handle_kg_gaps(state, args).await,
92        "memory_recall_all" => handle_memory_recall_all(state, args).await,
93        "get_prompt_context" => handle_get_prompt_context(state, args).await,
94        "discover_aliases" => handle_discover_aliases(state, args).await,
95        "kg_bootstrap" => handle_kg_bootstrap(state, args).await,
96        "memory_send_message" => handle_memory_send_message(state, args).await,
97        "upgrade" => handle_upgrade_tool(state, args).await,
98        "console_metrics" => crate::console_metrics::handle_console_metrics(state, args).await,
99        other => anyhow::bail!("unknown tool: {other}"),
100    }
101}
102
103#[cfg(test)]
104mod tests;