Skip to main content

mneme/mcp/
tools.rs

1use std::str::FromStr;
2use std::sync::Arc;
3
4use chrono::Utc;
5use rmcp::handler::server::tool::schema_for_type;
6use rmcp::model::{CallToolResult, Content, JsonObject, Tool};
7use schemars::JsonSchema;
8use serde::Deserialize;
9use serde_json::json;
10use uuid::Uuid;
11
12use crate::config::settings::Settings;
13use crate::store::db::Database;
14use crate::store::memory::{
15    CreateMemoryInput, CreatePromptInput, Scope, SearchQuery, UpdateMemoryInput,
16};
17
18/// Builds the JSON envelope response for MCP tools.
19fn tool_response<T: serde::Serialize>(
20    result: crate::error::Result<T>,
21    project: &str,
22) -> CallToolResult {
23    let meta = json!({
24        "project": project,
25        "timestamp": Utc::now().to_rfc3339(),
26        "version": env!("CARGO_PKG_VERSION")
27    });
28
29    match result {
30        Ok(data) => {
31            let envelope = json!({
32                "success": true,
33                "data": data,
34                "error": null,
35                "meta": meta
36            });
37            match Content::json(envelope) {
38                Ok(content) => CallToolResult::success(vec![content]),
39                Err(e) => {
40                    let err_envelope = json!({
41                        "success": false,
42                        "data": null,
43                        "error": {
44                            "code": "SERIALIZATION_ERROR",
45                            "message": e.to_string()
46                        },
47                        "meta": meta
48                    });
49                    CallToolResult::error(vec![Content::text(err_envelope.to_string())])
50                }
51            }
52        }
53        Err(e) => {
54            let code = match e {
55                crate::error::MnemeError::NotFound(_) => "NOT_FOUND",
56                crate::error::MnemeError::ProjectRequired => "PROJECT_REQUIRED",
57                crate::error::MnemeError::EmptyQuery => "EMPTY_QUERY",
58                crate::error::MnemeError::InvalidMemoryType(_) => "INVALID_MEMORY_TYPE",
59                crate::error::MnemeError::InvalidImportance(_) => "INVALID_IMPORTANCE",
60                crate::error::MnemeError::InvalidScope(_) => "INVALID_SCOPE",
61                crate::error::MnemeError::InvalidRelationType(_) => "INVALID_RELATION_TYPE",
62                crate::error::MnemeError::RelationAlreadyExists(_, _) => "RELATION_EXISTS",
63                crate::error::MnemeError::SelfRelation(_) => "SELF_RELATION",
64                crate::error::MnemeError::DuplicateDetected(_) => "DUPLICATE",
65                crate::error::MnemeError::Database(_) => "DATABASE_ERROR",
66                crate::error::MnemeError::Migration(_) => "MIGRATION_ERROR",
67                crate::error::MnemeError::Io(_) => "IO_ERROR",
68                crate::error::MnemeError::Serialization(_) => "SERIALIZATION_ERROR",
69                crate::error::MnemeError::Config(_) => "CONFIG_ERROR",
70                crate::error::MnemeError::Http(_) => "HTTP_ERROR",
71                crate::error::MnemeError::Mcp(_) => "MCP_ERROR",
72                crate::error::MnemeError::Embeddings(_) => "EMBEDDINGS_ERROR",
73                crate::error::MnemeError::EmbeddingsDisabled => "EMBEDDINGS_DISABLED",
74                crate::error::MnemeError::PeerNotFound(_) => "PEER_NOT_FOUND",
75                crate::error::MnemeError::SyncFailed { .. } => "SYNC_FAILED",
76                crate::error::MnemeError::SyncDisabled => "SYNC_DISABLED",
77                crate::error::MnemeError::UnsupportedTransport(_) => "UNSUPPORTED_TRANSPORT",
78                crate::error::MnemeError::InvalidSyncFile(_) => "INVALID_SYNC_FILE",
79                crate::error::MnemeError::Compression(_) => "COMPRESSION_ERROR",
80                crate::error::MnemeError::NoRecipientsConfigured => "NO_RECIPIENTS",
81                crate::error::MnemeError::DecryptionFailed => "DECRYPTION_FAILED",
82                crate::error::MnemeError::IdentityNotLoaded => "IDENTITY_NOT_LOADED",
83                crate::error::MnemeError::AlreadyEncrypted(_) => "ALREADY_ENCRYPTED",
84                crate::error::MnemeError::NotEncrypted(_) => "NOT_ENCRYPTED",
85                crate::error::MnemeError::KeyNotFound(_) => "KEY_NOT_FOUND",
86                crate::error::MnemeError::Plugin(_) => "PLUGIN_ERROR",
87            };
88            let envelope = json!({
89                "success": false,
90                "data": null,
91                "error": {
92                    "code": code,
93                    "message": e.to_string()
94                },
95                "meta": meta
96            });
97            CallToolResult::error(vec![Content::text(envelope.to_string())])
98        }
99    }
100}
101
102/// Executes an MCP tool by name, with optional plugin dispatch.
103pub async fn execute_tool(
104    db: &Database,
105    name: &str,
106    arguments: Option<JsonObject>,
107    project: &str,
108    embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
109    plugins: Option<&crate::plugins::PluginManager>,
110) -> CallToolResult {
111    tracing::debug!("MCP tool call: {} with args: {:?}", name, arguments);
112
113    let args = arguments.unwrap_or_default();
114
115    // Check plugin dispatch first for unknown built-in tools.
116    if let Some(pm) = plugins {
117        if pm.owns_tool(name) {
118            let args_value = serde_json::Value::Object(args);
119            let result = pm.call_tool(name, args_value, project);
120            return tool_response(result, project);
121        }
122    }
123
124    let result = match name {
125        "mem_save" => mem_save(db, args, project, embeddings),
126        "mem_update" => mem_update(db, args, project),
127        "mem_delete" => mem_delete(db, args, project),
128        "mem_restore" => mem_restore(db, args, project),
129        "mem_search" => mem_search(db, args, project),
130        "mem_similar" => mem_similar(db, args, project, embeddings).await,
131        "mem_get" => mem_get(db, args, project),
132        "mem_list" => mem_list(db, args, project),
133        "mem_context" => mem_context(db, args, project),
134        "mem_timeline" => mem_timeline(db, args, project),
135        "mem_session_start" => mem_session_start(db, args, project),
136        "mem_session_end" => mem_session_end(db, args, project),
137        "mem_session_summary" => mem_session_summary(db, args, project),
138        "mem_stats" => mem_stats(db, args, project),
139        "mem_projects" => mem_projects(db, args, project),
140        "mem_conflicts" => mem_conflicts(db, args, project),
141        "mem_save_prompt" => mem_save_prompt(db, args, project),
142        "mem_suggest_topic_key" => mem_suggest_topic_key(db, args, project),
143        "mem_current_project" => mem_current_project(db, args, project),
144        "mem_doctor" => mem_doctor(db, args, project),
145        "mem_save_batch" => mem_save_batch(db, args, project, embeddings),
146        "mem_delete_relation" => mem_delete_relation(db, args, project),
147        "mem_audit" => mem_audit(db, args, project),
148        "mem_deduplicate" => mem_deduplicate(db, args, project),
149        "mem_feedback" => mem_feedback(db, args, project),
150        "mem_deprecate" => mem_deprecate(db, args, project),
151        "mem_graph" => mem_graph(db, args, project),
152        "mem_summarize" => mem_summarize(db, args, project),
153        "mem_inject_context" => mem_inject_context(db, args, project),
154        "mem_forget_project" => mem_forget_project(db, args, project),
155        "mem_health" => mem_health(db, args, project),
156        "mem_remind" => mem_remind(db, args, project),
157        "mem_tag_suggest" => mem_tag_suggest(db, args, project),
158        "mem_knowledge_gaps" => mem_knowledge_gaps(db, args, project),
159        "mem_sync_status" => mem_sync_status(db, args, project),
160        "mem_sync_now" => mem_sync_now(db, args, project),
161        "mem_sync_export" => mem_sync_export(db, args, project),
162        "mem_encrypt" => mem_encrypt(db, args, project),
163        "mem_decrypt" => mem_decrypt(db, args, project),
164        "keys_list" => keys_list(db, args, project),
165        "keys_status" => keys_status(db, args, project),
166        "mem_entity_extract" => mem_entity_extract(db, args, project),
167        "mem_entity_search" => mem_entity_search(db, args, project),
168        "mem_entity_links" => mem_entity_links(db, args, project),
169        "mem_cloud_enroll" => mem_cloud_enroll(db, args, project),
170        "mem_cloud_sync" => mem_cloud_sync(db, args, project),
171        "mem_cloud_status" => mem_cloud_status(db, args, project),
172        "mem_consolidate" => mem_consolidate(db, args, project),
173        "mem_block_set" => mem_block_set(db, args, project),
174        "mem_block_get" => mem_block_get(db, args, project),
175        "mem_block_list" => mem_block_list(db, args, project),
176        "mem_learn_failures" => mem_learn_failures(db, args, project),
177        "mem_session_outcome" => mem_session_outcome(db, args, project),
178        "mem_obsidian_export" => mem_obsidian_export(db, args, project),
179        "mem_watch_scan" => mem_watch_scan(db, args, project),
180        "mem_compress" => mem_compress(db, args, project),
181        "mem_compress_batch" => mem_compress_batch(db, args, project),
182        "mem_compress_context" => mem_compress_context(db, args, project),
183        "mem_temporal_query" => mem_temporal_query(db, args, project),
184        "mem_expand" => mem_expand(db, args, project),
185        "mem_transcript" => mem_transcript(db, args, project),
186        "mem_capture_passive" => mem_capture_passive(db, args, project),
187        "mem_entity_frequent" => mem_entity_frequent(db, args, project),
188        "mem_judge" => mem_judge(db, args, project),
189        "mem_compare" => mem_compare(db, args, project),
190        "mem_conflict_candidates" => mem_conflict_candidates(db, args, project),
191        _ => Err(crate::error::MnemeError::Mcp(format!(
192            "Unknown tool: {}",
193            name
194        ))),
195    };
196
197    tool_response(result, project)
198}
199
200// --- Tool Parameter Schemas ---
201
202#[derive(Debug, Clone, Deserialize, JsonSchema)]
203struct MemSaveParams {
204    title: String,
205    content: String,
206    #[serde(default)]
207    project: Option<String>,
208    #[serde(rename = "type", default = "default_note")]
209    r#type: String,
210    #[serde(default = "default_medium")]
211    importance: String,
212    #[serde(default)]
213    tags: Vec<String>,
214    #[serde(default)]
215    what: Option<String>,
216    #[serde(default)]
217    why: Option<String>,
218    #[serde(default)]
219    context: Option<String>,
220    #[serde(default)]
221    learned: Option<String>,
222    #[serde(default)]
223    scope: Option<String>,
224    #[serde(default)]
225    topic_key: Option<String>,
226    /// RFC3339 timestamp: when this memory's fact became valid.
227    #[serde(default)]
228    valid_from: Option<String>,
229    /// RFC3339 timestamp: when this memory's fact stopped being valid.
230    #[serde(default)]
231    valid_until: Option<String>,
232    /// Provenance JSON: array of {agent, action, timestamp}.
233    #[serde(default)]
234    provenance: Option<String>,
235}
236
237fn default_note() -> String {
238    "note".into()
239}
240fn default_medium() -> String {
241    "medium".into()
242}
243
244#[derive(Debug, Clone, Deserialize, JsonSchema)]
245struct MemUpdateParams {
246    id: String,
247    #[serde(default)]
248    title: Option<String>,
249    #[serde(default)]
250    content: Option<String>,
251    #[serde(default)]
252    what: Option<String>,
253    #[serde(default)]
254    why: Option<String>,
255    #[serde(default)]
256    context: Option<String>,
257    #[serde(default)]
258    learned: Option<String>,
259    #[serde(rename = "type", default)]
260    r#type: Option<String>,
261    #[serde(default)]
262    importance: Option<String>,
263    #[serde(default)]
264    tags: Option<Vec<String>>,
265    #[serde(default)]
266    scope: Option<String>,
267    #[serde(default)]
268    topic_key: Option<String>,
269}
270
271#[derive(Debug, Clone, Deserialize, JsonSchema)]
272struct MemDeleteParams {
273    id: String,
274    #[serde(default)]
275    hard: bool,
276}
277
278#[derive(Debug, Clone, Deserialize, JsonSchema)]
279struct MemRestoreParams {
280    id: String,
281}
282
283#[derive(Debug, Clone, Deserialize, JsonSchema)]
284struct MemSearchParams {
285    query: String,
286    #[serde(default)]
287    project: Option<String>,
288    #[serde(rename = "type", default)]
289    r#type: Option<String>,
290    #[serde(default = "default_limit_10")]
291    limit: u32,
292}
293
294fn default_limit_10() -> u32 {
295    10
296}
297
298#[derive(Debug, Clone, Deserialize, JsonSchema)]
299struct MemGetParams {
300    id: String,
301}
302
303#[derive(Debug, Clone, Deserialize, JsonSchema)]
304struct MemListParams {
305    #[serde(default)]
306    project: Option<String>,
307    #[serde(rename = "type", default)]
308    r#type: Option<String>,
309    #[serde(default = "default_limit_20")]
310    limit: u32,
311}
312
313fn default_limit_20() -> u32 {
314    20
315}
316
317#[derive(Debug, Clone, Deserialize, JsonSchema)]
318struct MemContextParams {
319    #[serde(default)]
320    project: Option<String>,
321    #[serde(default = "default_limit_10")]
322    limit: u32,
323}
324
325#[derive(Debug, Clone, Deserialize, JsonSchema)]
326struct MemTimelineParams {
327    id: String,
328    #[serde(default = "default_limit_10")]
329    limit: u32,
330}
331
332#[derive(Debug, Clone, Deserialize, JsonSchema)]
333struct MemSessionStartParams {
334    #[serde(default)]
335    project: Option<String>,
336    #[serde(default)]
337    directory: Option<String>,
338}
339
340#[derive(Debug, Clone, Deserialize, JsonSchema)]
341struct MemSessionEndParams {
342    id: String,
343    #[serde(default)]
344    summary: Option<String>,
345}
346
347#[derive(Debug, Clone, Deserialize, JsonSchema)]
348struct MemSessionSummaryParams {
349    #[serde(default)]
350    project: Option<String>,
351}
352
353#[derive(Debug, Clone, Deserialize, JsonSchema)]
354struct MemStatsParams {
355    #[serde(default)]
356    project: Option<String>,
357}
358
359#[derive(Debug, Clone, Deserialize, JsonSchema)]
360struct MemProjectsParams {}
361
362#[derive(Debug, Clone, Deserialize, JsonSchema)]
363struct MemConflictsParams {
364    #[serde(default)]
365    project: Option<String>,
366    #[serde(default = "default_limit_10")]
367    limit: u32,
368}
369
370#[derive(Debug, Clone, Deserialize, JsonSchema)]
371struct MemSavePromptParams {
372    content: String,
373    #[serde(default)]
374    session_id: Option<String>,
375    #[serde(default)]
376    project: Option<String>,
377}
378
379#[derive(Debug, Clone, Deserialize, JsonSchema)]
380struct MemSuggestTopicKeyParams {
381    #[serde(rename = "type")]
382    r#type: String,
383    title: String,
384}
385
386#[derive(Debug, Clone, Deserialize, JsonSchema)]
387struct MemCurrentProjectParams {}
388
389#[derive(Debug, Clone, Deserialize, JsonSchema)]
390struct MemDoctorParams {
391    #[serde(default)]
392    project: Option<String>,
393}
394
395#[derive(Debug, Clone, Deserialize, JsonSchema)]
396struct MemSimilarParams {
397    /// Texto o UUID para buscar similitud semántica.
398    query: String,
399    #[serde(default)]
400    project: Option<String>,
401    #[serde(default = "default_limit_5")]
402    limit: u32,
403    #[serde(default = "default_threshold_75")]
404    threshold: f32,
405}
406
407fn default_limit_5() -> u32 {
408    5
409}
410fn default_threshold_75() -> f32 {
411    0.75
412}
413
414#[derive(Debug, Clone, Deserialize, JsonSchema)]
415struct BatchMemoryInput {
416    title: String,
417    content: String,
418    #[serde(rename = "type", default = "default_note")]
419    r#type: String,
420    #[serde(default = "default_medium")]
421    importance: String,
422    #[serde(default)]
423    tags: Vec<String>,
424    #[serde(default)]
425    what: Option<String>,
426    #[serde(default)]
427    why: Option<String>,
428    #[serde(default)]
429    context: Option<String>,
430    #[serde(default)]
431    learned: Option<String>,
432    #[serde(default)]
433    scope: Option<String>,
434    #[serde(default)]
435    topic_key: Option<String>,
436}
437
438#[derive(Debug, Clone, Deserialize, JsonSchema)]
439struct MemSaveBatchParams {
440    memories: Vec<BatchMemoryInput>,
441    #[serde(default)]
442    project: Option<String>,
443}
444
445#[derive(Debug, Clone, Deserialize, JsonSchema)]
446struct MemDeleteRelationParams {
447    relation_id: String,
448}
449
450#[derive(Debug, Clone, Deserialize, JsonSchema)]
451struct MemAuditParams {
452    #[serde(default)]
453    project: Option<String>,
454    #[serde(default = "default_days_30")]
455    days_threshold: u32,
456}
457
458fn default_days_30() -> u32 {
459    30
460}
461
462#[derive(Debug, Clone, Deserialize, JsonSchema)]
463struct MemDeduplicateParams {
464    #[serde(default)]
465    project: Option<String>,
466    #[serde(default = "default_threshold_85")]
467    threshold: f64,
468}
469
470fn default_threshold_85() -> f64 {
471    0.85
472}
473
474#[derive(Debug, Clone, Deserialize, JsonSchema)]
475struct MemFeedbackParams {
476    memory_id: String,
477    is_useful: bool,
478    #[serde(default)]
479    reason: Option<String>,
480}
481
482#[derive(Debug, Clone, Deserialize, JsonSchema)]
483struct MemDeprecateParams {
484    memory_id: String,
485    reason: String,
486    #[serde(default)]
487    supersedes_id: Option<String>,
488}
489
490#[derive(Debug, Clone, Deserialize, JsonSchema)]
491struct MemGraphParams {
492    #[serde(default)]
493    project: Option<String>,
494}
495
496#[derive(Debug, Clone, Deserialize, JsonSchema)]
497struct MemSummarizeParams {
498    #[serde(default)]
499    project: Option<String>,
500    #[serde(default)]
501    session_id: Option<String>,
502}
503
504#[derive(Debug, Clone, Deserialize, JsonSchema)]
505struct MemInjectContextParams {
506    #[serde(default)]
507    project: Option<String>,
508    #[serde(default)]
509    file: Option<String>,
510    #[serde(default = "default_limit_10")]
511    limit: u32,
512}
513
514#[derive(Debug, Clone, Deserialize, JsonSchema)]
515struct MemForgetProjectParams {
516    #[serde(default)]
517    project: Option<String>,
518    confirm: bool,
519}
520
521#[derive(Debug, Clone, Deserialize, JsonSchema)]
522struct MemHealthParams {
523    #[serde(default)]
524    project: Option<String>,
525}
526
527#[derive(Debug, Clone, Deserialize, JsonSchema)]
528struct MemRemindParams {
529    #[serde(default)]
530    project: Option<String>,
531    #[serde(default = "default_high")]
532    importance: String,
533}
534
535fn default_high() -> String {
536    "high".into()
537}
538
539#[derive(Debug, Clone, Deserialize, JsonSchema)]
540struct MemTagSuggestParams {
541    #[serde(default)]
542    project: Option<String>,
543    title: String,
544    #[serde(default)]
545    content: Option<String>,
546}
547
548#[derive(Debug, Clone, Deserialize, JsonSchema)]
549struct MemKnowledgeGapsParams {
550    #[serde(default)]
551    project: Option<String>,
552}
553
554#[derive(Debug, Clone, Deserialize, JsonSchema)]
555struct MemSyncStatusParams {
556    #[serde(default)]
557    project: Option<String>,
558}
559
560#[derive(Debug, Clone, Deserialize, JsonSchema)]
561struct MemSyncNowParams {
562    #[serde(default)]
563    project: Option<String>,
564}
565
566#[derive(Debug, Clone, Deserialize, JsonSchema)]
567struct MemSyncExportParams {
568    #[serde(default)]
569    project: Option<String>,
570    #[serde(default)]
571    output: Option<String>,
572}
573
574#[derive(Debug, Clone, Deserialize, JsonSchema)]
575struct MemEncryptParams {
576    memory_id: String,
577}
578
579#[derive(Debug, Clone, Deserialize, JsonSchema)]
580struct MemDecryptParams {
581    memory_id: String,
582}
583
584#[derive(Debug, Clone, Deserialize, JsonSchema)]
585struct KeysListParams {}
586
587#[derive(Debug, Clone, Deserialize, JsonSchema)]
588struct KeysStatusParams {
589    #[serde(default)]
590    project: Option<String>,
591}
592
593// --- Tool Implementations ---
594
595fn mem_save(
596    db: &Database,
597    args: JsonObject,
598    project: &str,
599    embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
600) -> crate::error::Result<serde_json::Value> {
601    let params: MemSaveParams = serde_json::from_value(serde_json::Value::Object(args))
602        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
603
604    let project = params.project.unwrap_or_else(|| project.to_string());
605    let memory_type = params.r#type.parse()?;
606    let importance = params.importance.parse()?;
607    let scope = params
608        .scope
609        .as_deref()
610        .map(Scope::from_str)
611        .transpose()?
612        .unwrap_or(Scope::Project);
613
614    let input = CreateMemoryInput {
615        project,
616        scope: Some(scope),
617        title: params.title,
618        content: params.content,
619        what: params.what,
620        why: params.why,
621        context: params.context,
622        learned: params.learned,
623        memory_type,
624        importance,
625        tags: params.tags,
626        topic_key: params.topic_key,
627        capture_prompt: None,
628        encrypt: false,
629        valid_from: params.valid_from.as_deref()
630            .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
631            .map(|d| d.with_timezone(&chrono::Utc)),
632        valid_until: params.valid_until.as_deref()
633            .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
634            .map(|d| d.with_timezone(&chrono::Utc)),
635        provenance: params.provenance,
636    };
637
638    let engine = embeddings.map(std::sync::Arc::clone);
639    let embedding_store = db.embeddings();
640    let memory = db.memories().save(input, engine, Some(embedding_store))?;
641    Ok(serde_json::to_value(memory)?)
642}
643
644fn mem_update(
645    db: &Database,
646    args: JsonObject,
647    _project: &str,
648) -> crate::error::Result<serde_json::Value> {
649    let params: MemUpdateParams = serde_json::from_value(serde_json::Value::Object(args))
650        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
651
652    let id =
653        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
654
655    let update = UpdateMemoryInput {
656        title: params.title,
657        content: params.content,
658        what: params.what,
659        why: params.why,
660        context: params.context,
661        learned: params.learned,
662        memory_type: params.r#type.as_deref().map(str::parse).transpose()?,
663        importance: params.importance.as_deref().map(str::parse).transpose()?,
664        tags: params.tags,
665        scope: params.scope.as_deref().map(Scope::from_str).transpose()?,
666        topic_key: params.topic_key,
667    };
668
669    let memory = db.memories().update(id, update)?;
670    Ok(serde_json::to_value(memory)?)
671}
672
673fn mem_delete(
674    db: &Database,
675    args: JsonObject,
676    _project: &str,
677) -> crate::error::Result<serde_json::Value> {
678    let params: MemDeleteParams = serde_json::from_value(serde_json::Value::Object(args))
679        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
680
681    let id =
682        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
683
684    db.memories().delete(id, params.hard)?;
685    Ok(json!({"deleted": true, "id": params.id}))
686}
687
688fn mem_restore(
689    db: &Database,
690    args: JsonObject,
691    _project: &str,
692) -> crate::error::Result<serde_json::Value> {
693    let params: MemRestoreParams = serde_json::from_value(serde_json::Value::Object(args))
694        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
695
696    let id =
697        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
698
699    let memory = db.memories().restore(id)?;
700    Ok(serde_json::to_value(memory)?)
701}
702
703fn mem_search(
704    db: &Database,
705    args: JsonObject,
706    project: &str,
707) -> crate::error::Result<serde_json::Value> {
708    let params: MemSearchParams = serde_json::from_value(serde_json::Value::Object(args))
709        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
710
711    let project = params.project.unwrap_or_else(|| project.to_string());
712    let memory_type = params.r#type.as_deref().map(str::parse).transpose()?;
713
714    let query = SearchQuery {
715        text: params.query,
716        project: Some(project),
717        scope: None,
718        memory_type,
719        importance: None,
720        tags: Vec::new(),
721        limit: params.limit,
722        include_snippet: true,
723        all_projects: false,
724    };
725
726    let weights = crate::store::search::SearchWeights::default();
727    let results = db.memories().search(&query, &weights, None)?;
728    Ok(serde_json::to_value(results)?)
729}
730
731async fn mem_similar(
732    db: &Database,
733    args: JsonObject,
734    project: &str,
735    embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
736) -> crate::error::Result<serde_json::Value> {
737    let params: MemSimilarParams = serde_json::from_value(serde_json::Value::Object(args))
738        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
739
740    let engine = match embeddings {
741        Some(e) => e,
742        None => return Err(crate::error::MnemeError::EmbeddingsDisabled),
743    };
744
745    let project = params.project.unwrap_or_else(|| project.to_string());
746    let embedding_store = db.embeddings();
747
748    // Try to parse query as UUID first
749    let query_embedding = if let Ok(id) = Uuid::parse_str(&params.query) {
750        match embedding_store.load(id)? {
751            Some(embedding) => embedding,
752            None => {
753                // If no stored embedding, load memory and embed it
754                let memory = db
755                    .memories()
756                    .get(id)?
757                    .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
758                let text = crate::embeddings::engine::EmbeddingEngine::memory_to_text(&memory);
759                engine.embed(&text).await?
760            }
761        }
762    } else {
763        engine.embed(&params.query).await?
764    };
765
766    let all_embeddings = embedding_store.load_all_for_project(&project)?;
767    let mut matches = Vec::new();
768    for (id, embedding) in all_embeddings {
769        let score = crate::embeddings::similarity::cosine_similarity(&query_embedding, &embedding);
770        if score >= params.threshold {
771            matches.push(crate::embeddings::similarity::SemanticMatch {
772                memory_id: id,
773                cosine_score: score,
774                combined_score: f64::from(score),
775            });
776        }
777    }
778    crate::embeddings::similarity::rank_by_combined_score(&mut matches);
779    matches.truncate(params.limit as usize);
780
781    let mut results = Vec::new();
782    for m in matches {
783        if let Some(memory) = db.memories().get(m.memory_id)? {
784            results.push(crate::store::memory::SearchResult {
785                memory,
786                score: m.combined_score,
787                snippet: None,
788                match_type: crate::store::memory::MatchType::Semantic,
789                cosine_score: Some(m.cosine_score),
790            });
791        }
792    }
793
794    Ok(serde_json::to_value(results)?)
795}
796
797fn mem_get(
798    db: &Database,
799    args: JsonObject,
800    _project: &str,
801) -> crate::error::Result<serde_json::Value> {
802    let params: MemGetParams = serde_json::from_value(serde_json::Value::Object(args))
803        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
804
805    let id =
806        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
807
808    match db.memories().get(id)? {
809        Some(memory) => Ok(serde_json::to_value(memory)?),
810        None => Err(crate::error::MnemeError::NotFound(id)),
811    }
812}
813
814fn mem_list(
815    db: &Database,
816    args: JsonObject,
817    project: &str,
818) -> crate::error::Result<serde_json::Value> {
819    let params: MemListParams = serde_json::from_value(serde_json::Value::Object(args))
820        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
821
822    let project = params.project.unwrap_or_else(|| project.to_string());
823    let memory_type = params.r#type.as_deref().map(str::parse).transpose()?;
824
825    let memories =
826        db.memories()
827            .list(&project, memory_type.as_ref(), None, None, params.limit, 0)?;
828    Ok(serde_json::to_value(memories)?)
829}
830
831fn mem_context(
832    db: &Database,
833    args: JsonObject,
834    _project: &str,
835) -> crate::error::Result<serde_json::Value> {
836    let params: MemContextParams = serde_json::from_value(serde_json::Value::Object(args))
837        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
838
839    let project = params.project.unwrap_or_else(|| _project.to_string());
840    let memories = db.memories().context(&project, None, params.limit)?;
841    Ok(serde_json::to_value(memories)?)
842}
843
844fn mem_timeline(
845    db: &Database,
846    args: JsonObject,
847    _project: &str,
848) -> crate::error::Result<serde_json::Value> {
849    let params: MemTimelineParams = serde_json::from_value(serde_json::Value::Object(args))
850        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
851
852    let id =
853        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
854
855    let memory = db
856        .memories()
857        .get(id)?
858        .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
859
860    let project = memory.project.clone();
861    let memories = db
862        .memories()
863        .list(&project, None, None, None, params.limit, 0)?;
864    Ok(serde_json::to_value(memories)?)
865}
866
867fn mem_session_start(
868    db: &Database,
869    args: JsonObject,
870    project: &str,
871) -> crate::error::Result<serde_json::Value> {
872    let params: MemSessionStartParams = serde_json::from_value(serde_json::Value::Object(args))
873        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
874
875    let project = params.project.unwrap_or_else(|| project.to_string());
876    let current_dir = std::env::current_dir().ok();
877    let dir = params
878        .directory
879        .as_deref()
880        .or_else(|| current_dir.as_ref().and_then(|p| p.to_str()));
881    let session = db.sessions().start(&project, dir)?;
882    Ok(serde_json::to_value(session)?)
883}
884
885fn mem_session_end(
886    db: &Database,
887    args: JsonObject,
888    _project: &str,
889) -> crate::error::Result<serde_json::Value> {
890    let params: MemSessionEndParams = serde_json::from_value(serde_json::Value::Object(args))
891        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
892
893    let id =
894        Uuid::parse_str(&params.id).map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
895
896    let session = db.sessions().end(id, params.summary.as_deref())?;
897    Ok(serde_json::to_value(session)?)
898}
899
900fn mem_session_summary(
901    db: &Database,
902    args: JsonObject,
903    project: &str,
904) -> crate::error::Result<serde_json::Value> {
905    let params: MemSessionSummaryParams =
906        serde_json::from_value(serde_json::Value::Object(args))
907            .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
908
909    let project = params.project.unwrap_or_else(|| project.to_string());
910    match db.sessions().get_active(&project)? {
911        Some(session) => Ok(serde_json::to_value(session)?),
912        None => Ok(json!(null)),
913    }
914}
915
916fn mem_stats(
917    db: &Database,
918    args: JsonObject,
919    project: &str,
920) -> crate::error::Result<serde_json::Value> {
921    let params: MemStatsParams = serde_json::from_value(serde_json::Value::Object(args))
922        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
923
924    let project = params.project.unwrap_or_else(|| project.to_string());
925    let stats = db.memories().stats(&project)?;
926    Ok(serde_json::to_value(stats)?)
927}
928
929fn mem_projects(
930    db: &Database,
931    _args: JsonObject,
932    _project: &str,
933) -> crate::error::Result<serde_json::Value> {
934    let projects = db.memories().list_projects()?;
935    Ok(serde_json::to_value(projects)?)
936}
937
938fn mem_conflicts(
939    db: &Database,
940    args: JsonObject,
941    project: &str,
942) -> crate::error::Result<serde_json::Value> {
943    let params: MemConflictsParams = serde_json::from_value(serde_json::Value::Object(args))
944        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
945
946    let project = params.project.unwrap_or_else(|| project.to_string());
947    let query = SearchQuery {
948        text: "conflict".to_string(),
949        project: Some(project),
950        scope: None,
951        memory_type: None,
952        importance: None,
953        tags: Vec::new(),
954        limit: params.limit,
955        include_snippet: false,
956        all_projects: false,
957    };
958
959    let weights = crate::store::search::SearchWeights::default();
960    let results = db.memories().search(&query, &weights, None)?;
961    Ok(serde_json::to_value(results)?)
962}
963
964fn mem_save_prompt(
965    db: &Database,
966    args: JsonObject,
967    project: &str,
968) -> crate::error::Result<serde_json::Value> {
969    let params: MemSavePromptParams = serde_json::from_value(serde_json::Value::Object(args))
970        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
971
972    let project = params.project.unwrap_or_else(|| project.to_string());
973    let session_id = params
974        .session_id
975        .as_deref()
976        .map(Uuid::parse_str)
977        .transpose()
978        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
979
980    let input = CreatePromptInput {
981        session_id,
982        content: params.content,
983        project,
984    };
985
986    let store = db.memories();
987    let prompt = store.save_prompt(input)?;
988    Ok(serde_json::to_value(prompt)?)
989}
990
991fn mem_suggest_topic_key(
992    _db: &Database,
993    args: JsonObject,
994    _project: &str,
995) -> crate::error::Result<serde_json::Value> {
996    let params: MemSuggestTopicKeyParams = serde_json::from_value(serde_json::Value::Object(args))
997        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
998
999    let family = match params.r#type.to_lowercase().as_str() {
1000        "architecture" => "architecture",
1001        "decision" => "decision",
1002        "bugfix" => "bug",
1003        "pattern" => "pattern",
1004        "convention" => "convention",
1005        "dependency" => "dependency",
1006        "workflow" => "workflow",
1007        "note" => "note",
1008        "config" => "config",
1009        "discovery" => "discovery",
1010        "learning" => "learning",
1011        _ => "general",
1012    };
1013
1014    let slug = params
1015        .title
1016        .to_lowercase()
1017        .replace(|c: char| !c.is_alphanumeric() && c != ' ', "")
1018        .split_whitespace()
1019        .take(4)
1020        .collect::<Vec<_>>()
1021        .join("-");
1022
1023    let suggested = format!("{}/{}", family, slug);
1024    Ok(json!({"topic_key": suggested}))
1025}
1026
1027fn mem_current_project(
1028    _db: &Database,
1029    _args: JsonObject,
1030    _project: &str,
1031) -> crate::error::Result<serde_json::Value> {
1032    let inferred = Settings::infer_project();
1033    let git_root = Settings::git_toplevel();
1034    let source = if git_root.is_some() {
1035        "git"
1036    } else {
1037        "directory"
1038    };
1039    let path = git_root
1040        .or_else(|| std::env::current_dir().ok())
1041        .map(|p| p.to_string_lossy().to_string())
1042        .unwrap_or_default();
1043
1044    Ok(json!({
1045        "project": inferred,
1046        "source": source,
1047        "path": path
1048    }))
1049}
1050
1051fn mem_doctor(
1052    db: &Database,
1053    args: JsonObject,
1054    project: &str,
1055) -> crate::error::Result<serde_json::Value> {
1056    let params: MemDoctorParams = serde_json::from_value(serde_json::Value::Object(args))
1057        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1058
1059    let project = params.project.unwrap_or_else(|| project.to_string());
1060    let issues: Vec<String> = Vec::new();
1061    let mut checks = Vec::new();
1062
1063    checks.push("Database connection: OK".to_string());
1064
1065    let memory_count = db
1066        .memories()
1067        .stats(&project)
1068        .map(|s| s.total_memories)
1069        .unwrap_or(0);
1070    let session_count = db
1071        .sessions()
1072        .list(&project, 1000)
1073        .map(|s| s.len() as u32)
1074        .unwrap_or(0);
1075
1076    checks.push(format!("Memories: {}", memory_count));
1077    checks.push(format!("Sessions: {}", session_count));
1078    checks.push("Orphaned relations: 0".to_string());
1079
1080    let healthy = issues.is_empty();
1081
1082    Ok(json!({
1083        "healthy": healthy,
1084        "project": project,
1085        "memory_count": memory_count,
1086        "session_count": session_count,
1087        "issues": issues,
1088        "checks": checks
1089    }))
1090}
1091
1092fn mem_save_batch(
1093    db: &Database,
1094    args: JsonObject,
1095    project: &str,
1096    embeddings: Option<&std::sync::Arc<crate::embeddings::engine::EmbeddingEngine>>,
1097) -> crate::error::Result<serde_json::Value> {
1098    let params: MemSaveBatchParams = serde_json::from_value(serde_json::Value::Object(args))
1099        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1100
1101    let project = params.project.unwrap_or_else(|| project.to_string());
1102    let mut inputs = Vec::new();
1103    for item in params.memories {
1104        let memory_type = item.r#type.parse()?;
1105        let importance = item.importance.parse()?;
1106        let scope = item
1107            .scope
1108            .as_deref()
1109            .map(Scope::from_str)
1110            .transpose()?
1111            .unwrap_or(Scope::Project);
1112        inputs.push(CreateMemoryInput {
1113            project: project.clone(),
1114            scope: Some(scope),
1115            title: item.title,
1116            content: item.content,
1117            what: item.what,
1118            why: item.why,
1119            context: item.context,
1120            learned: item.learned,
1121            memory_type,
1122            importance,
1123            tags: item.tags,
1124            topic_key: item.topic_key,
1125            capture_prompt: None,
1126            encrypt: false,
1127            valid_from: None,
1128            valid_until: None,
1129            provenance: None,
1130        });
1131    }
1132
1133    let engine = embeddings.map(std::sync::Arc::clone);
1134    let embedding_store = db.embeddings();
1135    let (saved, duplicates) = db
1136        .memories()
1137        .save_batch(inputs, engine, Some(embedding_store))?;
1138    Ok(json!({
1139        "saved": saved,
1140        "duplicates": duplicates,
1141        "saved_count": saved.len(),
1142        "duplicate_count": duplicates.len()
1143    }))
1144}
1145
1146fn mem_delete_relation(
1147    db: &Database,
1148    args: JsonObject,
1149    _project: &str,
1150) -> crate::error::Result<serde_json::Value> {
1151    let params: MemDeleteRelationParams =
1152        serde_json::from_value(serde_json::Value::Object(args))
1153            .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1154
1155    let id = Uuid::parse_str(&params.relation_id)
1156        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1157
1158    let deleted = db.memories().delete_relation(id)?;
1159    Ok(json!({"deleted": deleted, "id": params.relation_id}))
1160}
1161
1162fn mem_audit(
1163    db: &Database,
1164    args: JsonObject,
1165    project: &str,
1166) -> crate::error::Result<serde_json::Value> {
1167    let params: MemAuditParams = serde_json::from_value(serde_json::Value::Object(args))
1168        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1169
1170    let project = params.project.unwrap_or_else(|| project.to_string());
1171    let report = db.memories().audit(&project, params.days_threshold)?;
1172    Ok(serde_json::to_value(report)?)
1173}
1174
1175fn mem_deduplicate(
1176    db: &Database,
1177    args: JsonObject,
1178    project: &str,
1179) -> crate::error::Result<serde_json::Value> {
1180    let params: MemDeduplicateParams = serde_json::from_value(serde_json::Value::Object(args))
1181        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1182
1183    let project = params.project.unwrap_or_else(|| project.to_string());
1184    let embedding_store = db.embeddings();
1185    let groups =
1186        db.memories()
1187            .find_duplicates_semantic(&project, params.threshold, &embedding_store)?;
1188    Ok(serde_json::to_value(groups)?)
1189}
1190
1191fn mem_feedback(
1192    db: &Database,
1193    args: JsonObject,
1194    _project: &str,
1195) -> crate::error::Result<serde_json::Value> {
1196    let params: MemFeedbackParams = serde_json::from_value(serde_json::Value::Object(args))
1197        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1198
1199    let memory_id = Uuid::parse_str(&params.memory_id)
1200        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1201
1202    let feedback_id =
1203        db.memories()
1204            .add_feedback(memory_id, params.is_useful, params.reason.as_deref())?;
1205    Ok(json!({
1206        "memory_id": params.memory_id,
1207        "feedback_id": feedback_id
1208    }))
1209}
1210
1211fn mem_deprecate(
1212    db: &Database,
1213    args: JsonObject,
1214    _project: &str,
1215) -> crate::error::Result<serde_json::Value> {
1216    let params: MemDeprecateParams = serde_json::from_value(serde_json::Value::Object(args))
1217        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1218
1219    let memory_id = Uuid::parse_str(&params.memory_id)
1220        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1221    let supersedes_id = params
1222        .supersedes_id
1223        .as_deref()
1224        .map(Uuid::parse_str)
1225        .transpose()
1226        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1227
1228    let memory = db
1229        .memories()
1230        .deprecate(memory_id, &params.reason, supersedes_id)?;
1231    Ok(serde_json::to_value(memory)?)
1232}
1233
1234fn mem_graph(
1235    db: &Database,
1236    args: JsonObject,
1237    project: &str,
1238) -> crate::error::Result<serde_json::Value> {
1239    let params: MemGraphParams = serde_json::from_value(serde_json::Value::Object(args))
1240        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1241
1242    let project = params.project.unwrap_or_else(|| project.to_string());
1243    let graph = db.memories().get_graph(&project)?;
1244    Ok(serde_json::to_value(graph)?)
1245}
1246
1247fn mem_summarize(
1248    db: &Database,
1249    args: JsonObject,
1250    project: &str,
1251) -> crate::error::Result<serde_json::Value> {
1252    let params: MemSummarizeParams = serde_json::from_value(serde_json::Value::Object(args))
1253        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1254
1255    let project = params.project.unwrap_or_else(|| project.to_string());
1256    let session_id = params
1257        .session_id
1258        .as_deref()
1259        .map(Uuid::parse_str)
1260        .transpose()
1261        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1262
1263    let summary = db.memories().summarize(&project, session_id)?;
1264    Ok(serde_json::to_value(summary)?)
1265}
1266
1267fn mem_inject_context(
1268    db: &Database,
1269    args: JsonObject,
1270    project: &str,
1271) -> crate::error::Result<serde_json::Value> {
1272    let params: MemInjectContextParams = serde_json::from_value(serde_json::Value::Object(args))
1273        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1274
1275    let project = params.project.unwrap_or_else(|| project.to_string());
1276    let context = db
1277        .memories()
1278        .inject_context(&project, params.file.as_deref(), params.limit)?;
1279    Ok(json!({"context": context, "project": project}))
1280}
1281
1282fn mem_forget_project(
1283    db: &Database,
1284    args: JsonObject,
1285    project: &str,
1286) -> crate::error::Result<serde_json::Value> {
1287    let params: MemForgetProjectParams = serde_json::from_value(serde_json::Value::Object(args))
1288        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1289
1290    if !params.confirm {
1291        return Err(crate::error::MnemeError::Config(
1292            "confirm must be true to forget project".into(),
1293        ));
1294    }
1295
1296    let project = params.project.unwrap_or_else(|| project.to_string());
1297    let deleted = db.memories().forget_project(&project)?;
1298    Ok(json!({"deleted": deleted, "project": project}))
1299}
1300
1301fn mem_health(
1302    db: &Database,
1303    args: JsonObject,
1304    _project: &str,
1305) -> crate::error::Result<serde_json::Value> {
1306    let params: MemHealthParams = serde_json::from_value(serde_json::Value::Object(args))
1307        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1308
1309    let report = db.memories().health(params.project.as_deref())?;
1310    Ok(serde_json::to_value(report)?)
1311}
1312
1313fn mem_remind(
1314    db: &Database,
1315    args: JsonObject,
1316    project: &str,
1317) -> crate::error::Result<serde_json::Value> {
1318    let params: MemRemindParams = serde_json::from_value(serde_json::Value::Object(args))
1319        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1320
1321    let project = params.project.unwrap_or_else(|| project.to_string());
1322    let importance = params.importance.parse()?;
1323    let memories = db.memories().remind(&project, &importance)?;
1324    Ok(serde_json::to_value(memories)?)
1325}
1326
1327fn mem_tag_suggest(
1328    db: &Database,
1329    args: JsonObject,
1330    project: &str,
1331) -> crate::error::Result<serde_json::Value> {
1332    let params: MemTagSuggestParams = serde_json::from_value(serde_json::Value::Object(args))
1333        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1334
1335    let project = params.project.unwrap_or_else(|| project.to_string());
1336    let tags = db
1337        .memories()
1338        .suggest_tags(&project, &params.title, params.content.as_deref())?;
1339    Ok(serde_json::to_value(tags)?)
1340}
1341
1342fn mem_knowledge_gaps(
1343    db: &Database,
1344    args: JsonObject,
1345    project: &str,
1346) -> crate::error::Result<serde_json::Value> {
1347    let params: MemKnowledgeGapsParams = serde_json::from_value(serde_json::Value::Object(args))
1348        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1349
1350    let project = params.project.unwrap_or_else(|| project.to_string());
1351    let report = db.memories().knowledge_gaps(&project)?;
1352    Ok(serde_json::to_value(report)?)
1353}
1354
1355fn mem_sync_status(
1356    db: &Database,
1357    args: JsonObject,
1358    project: &str,
1359) -> crate::error::Result<serde_json::Value> {
1360    let params: MemSyncStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1361        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1362
1363    let project = params.project.unwrap_or_else(|| project.to_string());
1364    let peers = db.peers().list(&project)?;
1365    let peer_names: Vec<String> = peers.iter().map(|p| p.name.clone()).collect();
1366
1367    Ok(json!({
1368        "project": project,
1369        "peers": peer_names,
1370        "peer_count": peers.len()
1371    }))
1372}
1373
1374fn mem_sync_now(
1375    db: &Database,
1376    args: JsonObject,
1377    project: &str,
1378) -> crate::error::Result<serde_json::Value> {
1379    let params: MemSyncNowParams = serde_json::from_value(serde_json::Value::Object(args))
1380        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1381
1382    let project = params.project.unwrap_or_else(|| project.to_string());
1383    let settings = Settings::load()?;
1384    let engine = crate::sync::engine::SyncEngine::new(Arc::new(db.clone()), settings.sync)?;
1385
1386    let rt = tokio::runtime::Runtime::new()?;
1387    let results = rt.block_on(async { engine.sync_auto(&project).await })?;
1388
1389    Ok(json!({
1390        "project": project,
1391        "results": results
1392    }))
1393}
1394
1395fn mem_sync_export(
1396    db: &Database,
1397    args: JsonObject,
1398    project: &str,
1399) -> crate::error::Result<serde_json::Value> {
1400    let params: MemSyncExportParams = serde_json::from_value(serde_json::Value::Object(args))
1401        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1402
1403    let project = params.project.unwrap_or_else(|| project.to_string());
1404    let output = params.output.map(std::path::PathBuf::from);
1405    let settings = Settings::load()?;
1406    let engine = crate::sync::engine::SyncEngine::new(Arc::new(db.clone()), settings.sync)?;
1407    let stats = engine.export_project(&project, output)?;
1408
1409    Ok(json!({
1410        "project": project,
1411        "memories_exported": stats.memories_exported,
1412        "bytes_written": stats.bytes_written
1413    }))
1414}
1415
1416fn mem_encrypt(
1417    db: &Database,
1418    args: JsonObject,
1419    _project: &str,
1420) -> crate::error::Result<serde_json::Value> {
1421    let params: MemEncryptParams = serde_json::from_value(serde_json::Value::Object(args))
1422        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1423
1424    let memory_id = Uuid::parse_str(&params.memory_id)
1425        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1426
1427    let memory = db.memories().encrypt_existing(memory_id)?;
1428    Ok(json!({ "memory": memory, "encrypted": true }))
1429}
1430
1431fn mem_decrypt(
1432    db: &Database,
1433    args: JsonObject,
1434    _project: &str,
1435) -> crate::error::Result<serde_json::Value> {
1436    let params: MemDecryptParams = serde_json::from_value(serde_json::Value::Object(args))
1437        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1438
1439    let memory_id = Uuid::parse_str(&params.memory_id)
1440        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1441
1442    let memory = db.memories().decrypt_existing(memory_id)?;
1443    Ok(json!({ "memory": memory, "decrypted": true }))
1444}
1445
1446fn keys_list(
1447    db: &Database,
1448    _args: JsonObject,
1449    _project: &str,
1450) -> crate::error::Result<serde_json::Value> {
1451    let key_store = crate::crypto::KeyStore::new(db.get_conn());
1452    let keys = key_store.list()?;
1453    Ok(json!({ "keys": keys }))
1454}
1455
1456fn keys_status(
1457    db: &Database,
1458    args: JsonObject,
1459    _project: &str,
1460) -> crate::error::Result<serde_json::Value> {
1461    let params: KeysStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1462        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1463
1464    let key_store = crate::crypto::KeyStore::new(db.get_conn());
1465    let keys = key_store.list().unwrap_or_default();
1466    let keys_count = keys.len() as u32;
1467    let default_key = keys.iter().find(|k| k.is_default).map(|k| k.alias.clone());
1468
1469    let encrypted_memories = if let Some(ref proj) = params.project {
1470        db.memories()
1471            .list(proj, None, None, None, 10000, 0)
1472            .map(|mems| mems.iter().filter(|m| m.is_encrypted).count() as u32)
1473            .unwrap_or(0)
1474    } else {
1475        0u32
1476    };
1477
1478    Ok(json!({
1479        "keys_count": keys_count,
1480        "encrypted_memories": encrypted_memories,
1481        "identity_loaded": false,
1482        "default_key": default_key,
1483    }))
1484}
1485
1486// --- Entity Extraction & Linking Tools ---
1487
1488#[derive(Debug, Clone, Deserialize, JsonSchema)]
1489struct MemEntityExtractParams {
1490    /// Memory ID to extract entities from, or text content
1491    id: Option<String>,
1492    /// Text content to extract entities from (alternative to id)
1493    text: Option<String>,
1494    /// Optional project context for the extraction.
1495    #[serde(default)]
1496    #[allow(dead_code)]
1497    project: Option<String>,
1498}
1499
1500#[derive(Debug, Clone, Deserialize, JsonSchema)]
1501struct MemEntitySearchParams {
1502    query: String,
1503    #[serde(default)]
1504    entity_type: Option<String>,
1505    #[serde(default = "default_limit_10")]
1506    limit: u32,
1507}
1508
1509#[derive(Debug, Clone, Deserialize, JsonSchema)]
1510struct MemEntityLinksParams {
1511    /// Memory ID to get linked memories for
1512    id: String,
1513    #[serde(default = "default_limit_10")]
1514    limit: u32,
1515}
1516
1517#[derive(Debug, Clone, Deserialize, JsonSchema)]
1518struct MemEntityFrequentParams {
1519    #[serde(default)]
1520    project: Option<String>,
1521    #[serde(default = "default_limit_20")]
1522    limit: u32,
1523}
1524
1525fn mem_entity_extract(
1526    db: &Database,
1527    args: JsonObject,
1528    project: &str,
1529) -> crate::error::Result<serde_json::Value> {
1530    let params: MemEntityExtractParams = serde_json::from_value(serde_json::Value::Object(args))
1531        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1532
1533    // Use project param for scope context (even if only used for metadata)
1534    let _project = params.project.as_deref().unwrap_or(project);
1535
1536    let entity_store = db.entities();
1537
1538    // If memory ID is provided, extract entities from existing memory
1539    if let Some(id_str) = &params.id {
1540        let id = Uuid::parse_str(id_str)
1541            .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1542        let memory = db
1543            .memories()
1544            .get(id)?
1545            .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
1546        
1547        if memory.is_encrypted {
1548            return Err(crate::error::MnemeError::Config(
1549                "Cannot extract entities from encrypted memory".into(),
1550            ));
1551        }
1552
1553        let entities = entity_store.extract_and_save(&memory)?;
1554        return Ok(serde_json::to_value(entities)?);
1555    }
1556
1557    // If raw text is provided, just extract without saving
1558    if let Some(text) = &params.text {
1559        let raw = crate::store::entities::EntityStore::extract_entities_from_text(text);
1560        return Ok(json!(raw));
1561    }
1562
1563    Err(crate::error::MnemeError::Config(
1564        "Provide either 'id' (memory UUID) or 'text' (raw content)".into(),
1565    ))
1566}
1567
1568fn mem_entity_search(
1569    db: &Database,
1570    args: JsonObject,
1571    _project: &str,
1572) -> crate::error::Result<serde_json::Value> {
1573    let params: MemEntitySearchParams = serde_json::from_value(serde_json::Value::Object(args))
1574        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1575
1576    let entity_type = params
1577        .entity_type
1578        .as_deref()
1579        .map(std::str::FromStr::from_str)
1580        .transpose()?;
1581
1582    let entity_store = db.entities();
1583    let results = entity_store.search_entities(&params.query, entity_type.as_ref(), params.limit)?;
1584    Ok(serde_json::to_value(results)?)
1585}
1586
1587fn mem_entity_links(
1588    db: &Database,
1589    args: JsonObject,
1590    _project: &str,
1591) -> crate::error::Result<serde_json::Value> {
1592    let params: MemEntityLinksParams = serde_json::from_value(serde_json::Value::Object(args))
1593        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1594
1595    let id = Uuid::parse_str(&params.id)
1596        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1597
1598    let entity_store = db.entities();
1599    let entities = entity_store.get_memory_entities(id)?;
1600    let links = entity_store.get_memory_links(id, params.limit)?;
1601
1602    Ok(json!({
1603        "memory_id": params.id,
1604        "entities": entities,
1605        "links": links.iter().map(|(link, title)| {
1606            json!({
1607                "entity_name": link.entity_name,
1608                "entity_type": link.entity_type.to_string(),
1609                "target_memory_id": link.target_memory_id.to_string(),
1610                "target_title": title,
1611                "link_strength": link.link_strength,
1612            })
1613        }).collect::<Vec<_>>()
1614    }))
1615}
1616
1617// --- Cloud Sync Tools ---
1618
1619#[derive(Debug, Clone, Deserialize, JsonSchema)]
1620struct MemCloudEnrollParams {
1621    /// Cloud server URL (e.g., https://cloud.mneme.dev)
1622    server: String,
1623    /// Authentication token
1624    token: String,
1625    #[serde(default)]
1626    project: Option<String>,
1627}
1628
1629#[derive(Debug, Clone, Deserialize, JsonSchema)]
1630struct MemCloudSyncParams {
1631    #[serde(default)]
1632    project: Option<String>,
1633}
1634
1635#[derive(Debug, Clone, Deserialize, JsonSchema)]
1636struct MemCloudStatusParams {
1637    #[serde(default)]
1638    project: Option<String>,
1639}
1640
1641fn mem_cloud_enroll(
1642    db: &Database,
1643    args: JsonObject,
1644    project: &str,
1645) -> crate::error::Result<serde_json::Value> {
1646    let params: MemCloudEnrollParams = serde_json::from_value(serde_json::Value::Object(args))
1647        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1648    let project = params.project.unwrap_or_else(|| project.to_string());
1649    let settings = crate::config::settings::Settings::load()?;
1650
1651    let orch = crate::cloud::CloudOrchestrator::new(
1652        // We need an Arc<Database>, but we have &Database
1653        // Create an Arc clone
1654        std::sync::Arc::new(db.clone()),
1655        settings.sync,
1656    );
1657
1658    let rt = tokio::runtime::Runtime::new()?;
1659    let result = rt.block_on(async {
1660        orch.enroll(&params.server, &params.token, &project).await
1661    })?;
1662
1663    Ok(serde_json::to_value(result)?)
1664}
1665
1666fn mem_cloud_sync(
1667    db: &Database,
1668    args: JsonObject,
1669    project: &str,
1670) -> crate::error::Result<serde_json::Value> {
1671    let params: MemCloudSyncParams = serde_json::from_value(serde_json::Value::Object(args))
1672        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1673    let project = params.project.unwrap_or_else(|| project.to_string());
1674    let settings = crate::config::settings::Settings::load()?;
1675
1676    let orch = crate::cloud::CloudOrchestrator::new(
1677        std::sync::Arc::new(db.clone()),
1678        settings.sync,
1679    );
1680
1681    let rt = tokio::runtime::Runtime::new()?;
1682    let result = rt.block_on(async {
1683        orch.sync_cloud(&project).await
1684    })?;
1685
1686    Ok(serde_json::to_value(result)?)
1687}
1688
1689fn mem_cloud_status(
1690    db: &Database,
1691    args: JsonObject,
1692    project: &str,
1693) -> crate::error::Result<serde_json::Value> {
1694    let params: MemCloudStatusParams = serde_json::from_value(serde_json::Value::Object(args))
1695        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1696    let project = params.project.unwrap_or_else(|| project.to_string());
1697    let settings = crate::config::settings::Settings::load()?;
1698
1699    let orch = crate::cloud::CloudOrchestrator::new(
1700        std::sync::Arc::new(db.clone()),
1701        settings.sync,
1702    );
1703
1704    orch.cloud_status(&project)
1705}
1706
1707// --- Failure Mining Tools ---
1708
1709#[derive(Debug, Clone, Deserialize, JsonSchema)]
1710struct MemLearnParams {
1711    #[serde(default)]
1712    project: Option<String>,
1713}
1714
1715#[derive(Debug, Clone, Deserialize, JsonSchema)]
1716struct MemSessionOutcomeParams {
1717    /// ID of the session
1718    session_id: String,
1719    /// Outcome: "success", "partial", or "failure"
1720    outcome: String,
1721    /// Comma-separated failure reasons (only for failure outcome)
1722    #[serde(default)]
1723    failure_reasons: Option<String>,
1724    /// Number of files affected
1725    #[serde(default)]
1726    affected_files: u32,
1727    /// Number of bugs introduced (only for failure outcome)
1728    #[serde(default)]
1729    bugs_introduced: u32,
1730    /// Description of user corrections (if any)
1731    #[serde(default)]
1732    user_corrections: Option<String>,
1733}
1734
1735fn mem_learn_failures(
1736    db: &Database,
1737    args: JsonObject,
1738    project: &str,
1739) -> crate::error::Result<serde_json::Value> {
1740    let params: MemLearnParams = serde_json::from_value(serde_json::Value::Object(args))
1741        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1742    let learn_db = std::sync::Arc::new(db.clone());
1743    let miner = crate::learn::FailureMiner::new(learn_db);
1744    let project = params.project.unwrap_or_else(|| project.to_string());
1745    let report = miner.mine(&project)?;
1746    Ok(serde_json::to_value(report)?)
1747}
1748
1749fn mem_session_outcome(
1750    db: &Database,
1751    args: JsonObject,
1752    _project: &str,
1753) -> crate::error::Result<serde_json::Value> {
1754    let params: MemSessionOutcomeParams = serde_json::from_value(serde_json::Value::Object(args))
1755        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1756
1757    let session_id = uuid::Uuid::parse_str(&params.session_id)
1758        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
1759
1760    let outcome = match params.outcome.as_str() {
1761        "success" => crate::learn::SessionOutcome::Success,
1762        "partial" => crate::learn::SessionOutcome::PartialSuccess,
1763        "failure" => {
1764            let reasons: Vec<String> = params
1765                .failure_reasons
1766                .as_deref()
1767                .map(|s| s.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect())
1768                .unwrap_or_default();
1769            crate::learn::SessionOutcome::Failure { reasons }
1770        }
1771        _ => return Err(crate::error::MnemeError::Config(
1772            format!("Invalid outcome '{}'. Must be: success, partial, failure", params.outcome)
1773        )),
1774    };
1775
1776    let learn_db = std::sync::Arc::new(db.clone());
1777    let miner = crate::learn::FailureMiner::new(learn_db);
1778    miner.record_session_outcome(
1779        session_id,
1780        outcome,
1781        params.affected_files,
1782        params.bugs_introduced,
1783        params.user_corrections.as_deref(),
1784    )?;
1785
1786    Ok(json!({
1787        "session_id": params.session_id,
1788        "outcome": params.outcome,
1789        "recorded": true
1790    }))
1791}
1792
1793// --- Consolidation & Memory Blocks Tools ---
1794
1795#[derive(Debug, Clone, Deserialize, JsonSchema)]
1796struct MemConsolidateParams {
1797    #[serde(default)]
1798    project: Option<String>,
1799    /// auto, age, deprecated, unused
1800    #[serde(default = "default_auto")]
1801    strategy: String,
1802    /// Days threshold for stale memories
1803    #[serde(default = "default_30")]
1804    days: u64,
1805    #[serde(default)]
1806    dry_run: bool,
1807}
1808
1809fn default_auto() -> String { "auto".to_string() }
1810fn default_30() -> u64 { 30 }
1811
1812#[derive(Debug, Clone, Deserialize, JsonSchema)]
1813struct MemBlockSetParams {
1814    #[serde(default)]
1815    project: Option<String>,
1816    slot: String,
1817    title: String,
1818    content: String,
1819}
1820
1821#[derive(Debug, Clone, Deserialize, JsonSchema)]
1822struct MemBlockGetParams {
1823    #[serde(default)]
1824    project: Option<String>,
1825    slot: String,
1826}
1827
1828#[derive(Debug, Clone, Deserialize, JsonSchema)]
1829struct MemBlockListParams {
1830    #[serde(default)]
1831    project: Option<String>,
1832}
1833
1834fn mem_consolidate(
1835    db: &Database,
1836    args: JsonObject,
1837    project: &str,
1838) -> crate::error::Result<serde_json::Value> {
1839    let params: MemConsolidateParams = serde_json::from_value(serde_json::Value::Object(args))
1840        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1841    let project = params.project.unwrap_or_else(|| project.to_string());
1842    let strat = match params.strategy.as_str() {
1843        "age" => crate::consolidate::ConsolidationStrategy::Age,
1844        "deprecated" => crate::consolidate::ConsolidationStrategy::Deprecated,
1845        "unused" => crate::consolidate::ConsolidationStrategy::Unused,
1846        _ => crate::consolidate::ConsolidationStrategy::Auto,
1847    };
1848    let eng = crate::consolidate::ConsolidationEngine::new(
1849        std::sync::Arc::new(db.clone()),
1850    );
1851    let result = eng.consolidate(&project, strat, params.days, params.dry_run)?;
1852    Ok(serde_json::to_value(result)?)
1853}
1854
1855fn mem_block_set(
1856    db: &Database,
1857    args: JsonObject,
1858    project: &str,
1859) -> crate::error::Result<serde_json::Value> {
1860    let params: MemBlockSetParams = serde_json::from_value(serde_json::Value::Object(args))
1861        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1862    let project = params.project.unwrap_or_else(|| project.to_string());
1863    let eng = crate::consolidate::ConsolidationEngine::new(
1864        std::sync::Arc::new(db.clone()),
1865    );
1866    let block = eng.set_block(&project, &params.slot, &params.title, &params.content)?;
1867    Ok(serde_json::to_value(block)?)
1868}
1869
1870fn mem_block_get(
1871    db: &Database,
1872    args: JsonObject,
1873    project: &str,
1874) -> crate::error::Result<serde_json::Value> {
1875    let params: MemBlockGetParams = serde_json::from_value(serde_json::Value::Object(args))
1876        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1877    let project = params.project.unwrap_or_else(|| project.to_string());
1878    let eng = crate::consolidate::ConsolidationEngine::new(
1879        std::sync::Arc::new(db.clone()),
1880    );
1881    let block = eng.get_block(&project, &params.slot)?;
1882    Ok(serde_json::to_value(block)?)
1883}
1884
1885fn mem_block_list(
1886    db: &Database,
1887    args: JsonObject,
1888    project: &str,
1889) -> crate::error::Result<serde_json::Value> {
1890    let params: MemBlockListParams = serde_json::from_value(serde_json::Value::Object(args))
1891        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1892    let project = params.project.unwrap_or_else(|| project.to_string());
1893    let eng = crate::consolidate::ConsolidationEngine::new(
1894        std::sync::Arc::new(db.clone()),
1895    );
1896    let blocks = eng.list_blocks(&project)?;
1897    Ok(serde_json::to_value(blocks)?)
1898}
1899
1900// --- Obsidian Export Tools ---
1901
1902#[derive(Debug, Clone, Deserialize, JsonSchema)]
1903struct MemObsidianExportParams {
1904    #[serde(default)]
1905    project: Option<String>,
1906    /// Output directory for the Obsidian vault
1907    output: String,
1908}
1909
1910fn mem_obsidian_export(
1911    db: &Database,
1912    args: JsonObject,
1913    project: &str,
1914) -> crate::error::Result<serde_json::Value> {
1915    let params: MemObsidianExportParams = serde_json::from_value(serde_json::Value::Object(args))
1916        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1917
1918    let project = params.project.unwrap_or_else(|| project.to_string());
1919    let output_dir = std::path::PathBuf::from(&params.output);
1920
1921    let memories = db.memories().list(&project, None, None, None, 10000, 0)?;
1922    let graph = db.memories().get_graph(&project)?;
1923
1924    // Convert graph edges to MemoryRelations for the export
1925    let relations: Vec<crate::store::memory::MemoryRelation> = graph
1926        .edges
1927        .into_iter()
1928        .filter_map(|e| {
1929            let source_id = uuid::Uuid::parse_str(&e.source).ok()?;
1930            let target_id = uuid::Uuid::parse_str(&e.target).ok()?;
1931            Some(crate::store::memory::MemoryRelation {
1932                id: uuid::Uuid::nil(),
1933                sync_id: String::new(),
1934                source_id,
1935                target_id,
1936                relation_type: std::str::FromStr::from_str(&e.relation_type).ok()?,
1937                confidence: e.confidence,
1938                judgment_status: "active".to_string(),
1939                reason: None,
1940                evidence: None,
1941                marked_by_actor: "system".to_string(),
1942                created_at: chrono::Utc::now(),
1943                updated_at: chrono::Utc::now(),
1944            })
1945        })
1946        .collect();
1947
1948    let stats = crate::export::obsidian::export_to_obsidian(
1949        &memories,
1950        &relations,
1951        &project,
1952        &output_dir,
1953    )?;
1954
1955    Ok(json!({
1956        "project": project,
1957        "output": output_dir.join("mneme-export").to_string_lossy().to_string(),
1958        "files_written": stats.files_written,
1959        "bytes_written": stats.bytes_written,
1960        "memories_exported": memories.len()
1961    }))
1962}
1963
1964// --- File Watcher Tools ---
1965
1966#[derive(Debug, Clone, Deserialize, JsonSchema)]
1967struct MemWatchScanParams {
1968    /// Directory to scan for files
1969    directory: String,
1970    /// File extension to watch (default: .md)
1971    #[serde(default = "default_md_ext")]
1972    ext: String,
1973    #[serde(default)]
1974    project: Option<String>,
1975}
1976
1977fn default_md_ext() -> String {
1978    ".md".to_string()
1979}
1980
1981fn mem_watch_scan(
1982    db: &Database,
1983    args: JsonObject,
1984    project: &str,
1985) -> crate::error::Result<serde_json::Value> {
1986    let params: MemWatchScanParams = serde_json::from_value(serde_json::Value::Object(args))
1987        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
1988
1989    let project = params.project.unwrap_or_else(|| project.to_string());
1990    let dir = std::path::PathBuf::from(&params.directory);
1991    let store = db.memories();
1992
1993    let mut watcher = crate::watch::DirectoryWatcher::new(
1994        dir,
1995        params.ext,
1996        3600, // interval doesn't matter for one-shot
1997        store,
1998        project,
1999    );
2000
2001    let rt = tokio::runtime::Runtime::new()?;
2002    let result = rt.block_on(async { watcher.scan().await })?;
2003
2004    Ok(json!({
2005        "indexed": result.indexed,
2006        "skipped": result.skipped,
2007        "errors": result.errors,
2008        "removed": result.removed,
2009        "tracked_files": watcher.tracked_count()
2010    }))
2011}
2012
2013// --- Context Compression Tools ---
2014
2015#[derive(Debug, Clone, Deserialize, JsonSchema)]
2016struct MemCompressParams {
2017    /// Memory ID to compress
2018    id: String,
2019    /// Compression strategy: truncate, smart_summary, keywords_only, minimal
2020    #[serde(default = "default_smart_summary")]
2021    strategy: String,
2022}
2023
2024fn default_smart_summary() -> String {
2025    "smart_summary".to_string()
2026}
2027
2028#[derive(Debug, Clone, Deserialize, JsonSchema)]
2029struct MemCompressBatchParams {
2030    /// Project to compress memories for
2031    #[serde(default)]
2032    project: Option<String>,
2033    /// Compression strategy
2034    #[serde(default = "default_smart_summary")]
2035    strategy: String,
2036    #[serde(default = "default_limit_10")]
2037    limit: u32,
2038}
2039
2040#[derive(Debug, Clone, Deserialize, JsonSchema)]
2041struct MemCompressContextParams {
2042    #[serde(default)]
2043    project: Option<String>,
2044    /// Compression strategy
2045    #[serde(default = "default_smart_summary")]
2046    strategy: String,
2047    #[serde(default = "default_limit_10")]
2048    limit: u32,
2049}
2050
2051fn mem_compress(
2052    db: &Database,
2053    args: JsonObject,
2054    _project: &str,
2055) -> crate::error::Result<serde_json::Value> {
2056    let params: MemCompressParams = serde_json::from_value(serde_json::Value::Object(args))
2057        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2058
2059    let id = Uuid::parse_str(&params.id)
2060        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2061    let memory = db.memories().get(id)?
2062        .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
2063
2064    let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2065    let compressed = crate::compress::CompressionPipeline::compress(&memory, strategy);
2066
2067    Ok(serde_json::to_value(compressed)?)
2068}
2069
2070fn mem_compress_batch(
2071    db: &Database,
2072    args: JsonObject,
2073    project: &str,
2074) -> crate::error::Result<serde_json::Value> {
2075    let params: MemCompressBatchParams = serde_json::from_value(serde_json::Value::Object(args))
2076        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2077
2078    let project = params.project.unwrap_or_else(|| project.to_string());
2079    let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2080
2081    let memories = db.memories().list(&project, None, None, None, params.limit, 0)?;
2082    let mut compressed = Vec::new();
2083    let mut total_ratio = 0.0;
2084
2085    for memory in &memories {
2086        let result = crate::compress::CompressionPipeline::compress(memory, strategy);
2087        total_ratio += result.compression_ratio;
2088        compressed.push(result);
2089    }
2090
2091    let avg_ratio = if !compressed.is_empty() {
2092        total_ratio / compressed.len() as f64
2093    } else {
2094        0.0
2095    };
2096
2097    Ok(json!({
2098        "compressed": compressed,
2099        "count": compressed.len(),
2100        "average_compression_ratio": avg_ratio,
2101        "strategy": params.strategy,
2102        "project": project
2103    }))
2104}
2105
2106fn mem_compress_context(
2107    db: &Database,
2108    args: JsonObject,
2109    project: &str,
2110) -> crate::error::Result<serde_json::Value> {
2111    let params: MemCompressContextParams = serde_json::from_value(serde_json::Value::Object(args))
2112        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2113
2114    let project = params.project.unwrap_or_else(|| project.to_string());
2115    let strategy: crate::compress::CompressionStrategy = params.strategy.parse()?;
2116
2117    let memories = db.memories().context(&project, None, params.limit)?;
2118    let context_block = crate::compress::CompressionPipeline::compress_context_block(&memories, strategy, params.limit as usize);
2119
2120    Ok(json!({
2121        "compressed_context": context_block,
2122        "memory_count": memories.len(),
2123        "strategy": params.strategy,
2124        "project": project
2125    }))
2126}
2127
2128// --- Temporal Query Tools ---
2129
2130#[derive(Debug, Clone, Deserialize, JsonSchema)]
2131struct MemTemporalQueryParams {
2132    /// Query text to search
2133    query: String,
2134    /// RFC3339 timestamp: find facts that were valid at this time
2135    at_time: String,
2136    #[serde(default)]
2137    project: Option<String>,
2138    #[serde(default = "default_limit_10")]
2139    limit: u32,
2140}
2141
2142fn mem_temporal_query(
2143    db: &Database,
2144    args: JsonObject,
2145    project: &str,
2146) -> crate::error::Result<serde_json::Value> {
2147    let params: MemTemporalQueryParams = serde_json::from_value(serde_json::Value::Object(args))
2148        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2149
2150    let at_time = chrono::DateTime::parse_from_rfc3339(&params.at_time)
2151        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid at_time: {}", e)))?
2152        .with_timezone(&chrono::Utc);
2153    let at_time_str = at_time.to_rfc3339();
2154    let project = params.project.unwrap_or_else(|| project.to_string());
2155
2156    let conn = db.get_conn();
2157    let like_pattern = format!("%{}%", params.query);
2158    let limit_i64 = params.limit as i64;
2159
2160    let results: Vec<serde_json::Value>;
2161    {
2162        let conn_guard = conn.lock().map_err(|_| crate::error::MnemeError::Config("mutex poisoned".into()))?;
2163        let mut stmt = conn_guard.prepare(
2164            "SELECT id, project, scope, title, content, what, why, context, learned,
2165             memory_type, importance, tags, topic_key, access_count, revision_count,
2166             duplicate_count, normalized_hash, created_at, updated_at, last_accessed_at, last_seen_at, deleted_at,
2167             deprecated_at, deprecated_reason, supersedes_id, context_inject_count, origin_peer,
2168             is_encrypted, encrypted_for, valid_from, valid_until, provenance
2169             FROM memories
2170             WHERE project = ?1 AND deleted_at IS NULL
2171             AND (title LIKE ?2 OR content LIKE ?2)
2172             AND (valid_from IS NULL OR valid_from <= ?3)
2173             AND (valid_until IS NULL OR valid_until > ?3)
2174             ORDER BY updated_at DESC
2175             LIMIT ?4"
2176        )?;
2177        let rows = stmt.query_map(
2178            rusqlite::params![project, like_pattern, at_time_str, limit_i64],
2179            crate::store::memory::MemoryStore::row_to_memory,
2180        )?;
2181        results = rows
2182            .filter_map(|r| r.ok())
2183            .map(|m| serde_json::to_value(m).unwrap_or_default())
2184            .collect();
2185    }
2186
2187    Ok(json!({
2188        "results": results,
2189        "count": results.len(),
2190        "query": params.query,
2191        "at_time": params.at_time,
2192        "project": project,
2193        "description": "Facts that were valid at the specified time"
2194    }))
2195}
2196
2197// --- Progressive Retrieval Tools ---
2198
2199#[derive(Debug, Clone, Deserialize, JsonSchema)]
2200struct MemExpandParams {
2201    /// Memory ID to expand (returns full content)
2202    id: String,
2203}
2204
2205#[derive(Debug, Clone, Deserialize, JsonSchema)]
2206struct MemTranscriptParams {
2207    /// Session ID to retrieve raw transcript for
2208    session_id: String,
2209    #[serde(default = "default_limit_50")]
2210    limit: u32,
2211}
2212
2213fn default_limit_50() -> u32 { 50 }
2214
2215fn mem_expand(
2216    db: &Database,
2217    args: JsonObject,
2218    _project: &str,
2219) -> crate::error::Result<serde_json::Value> {
2220    let params: MemExpandParams = serde_json::from_value(serde_json::Value::Object(args))
2221        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2222
2223    let id = Uuid::parse_str(&params.id)
2224        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2225
2226    let memory = db.memories().get(id)?
2227        .ok_or_else(|| crate::error::MnemeError::NotFound(id))?;
2228
2229    // Return the full expanded view: all structured fields + entities + relations
2230    let entity_store = db.entities();
2231    let entities = entity_store.get_memory_entities(id).ok();
2232    let links = entity_store.get_memory_links(id, 10).ok();
2233
2234    Ok(json!({
2235        "memory": memory,
2236        "entities": entities,
2237        "entity_links": links.map(|l| l.iter().map(|(link, title)| {
2238            json!({
2239                "entity_name": link.entity_name,
2240                "target_title": title,
2241                "link_strength": link.link_strength
2242            })
2243        }).collect::<Vec<_>>()),
2244        "layer": 2,
2245        "instruction": "This is the expanded view (Layer 2). Use this content for detailed context."
2246    }))
2247}
2248
2249fn mem_transcript(
2250    db: &Database,
2251    args: JsonObject,
2252    _project: &str,
2253) -> crate::error::Result<serde_json::Value> {
2254    let params: MemTranscriptParams = serde_json::from_value(serde_json::Value::Object(args))
2255        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2256
2257    let session_id = Uuid::parse_str(&params.session_id)
2258        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2259
2260    let session = db.sessions().get(session_id)?
2261        .ok_or_else(|| crate::error::MnemeError::NotFound(session_id))?;
2262
2263    // Get prompts for this session
2264    let conn = db.get_conn();
2265    let prompts: Vec<crate::store::memory::UserPrompt>;
2266    {
2267        let conn_guard = conn.lock().map_err(|_| crate::error::MnemeError::Config("mutex poisoned".into()))?;
2268        let mut stmt = conn_guard.prepare(
2269            "SELECT id, session_id, content, project, created_at
2270             FROM user_prompts WHERE session_id = ?1
2271             ORDER BY created_at ASC
2272             LIMIT ?2"
2273        )?;
2274        let limit_i64 = params.limit as i64;
2275        let rows = stmt.query_map(rusqlite::params![params.session_id, limit_i64], |row| {
2276            Ok(crate::store::memory::UserPrompt {
2277                id: uuid::Uuid::parse_str(&row.get::<_, String>(0)?).map_err(|e| {
2278                    rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Text, Box::new(e))
2279                })?,
2280                session_id: row.get::<_, Option<String>>(1)?
2281                    .and_then(|s| uuid::Uuid::parse_str(&s).ok()),
2282                content: row.get(2)?,
2283                project: row.get(3)?,
2284                created_at: chrono::DateTime::parse_from_rfc3339(&row.get::<_, String>(4)?)
2285                    .map_err(|e| {
2286                        rusqlite::Error::FromSqlConversionFailure(4, rusqlite::types::Type::Text, Box::new(e))
2287                    })?.with_timezone(&chrono::Utc),
2288            })
2289        })?;
2290        prompts = rows.collect::<Result<Vec<_>, _>>()?;
2291    }
2292
2293    // Build a readable transcript
2294    let mut transcript_lines = vec![
2295        format!("## Session Transcript: {}", session.id),
2296        format!("Project: {} | Started: {} | Ended: {}",
2297            session.project,
2298            session.started_at.to_rfc3339(),
2299            session.ended_at.map(|d| d.to_rfc3339()).unwrap_or_else(|| "active".to_string())
2300        ),
2301        String::new(),
2302    ];
2303
2304    if let Some(ref summary) = session.summary {
2305        transcript_lines.push(format!("Summary: {}", summary));
2306        transcript_lines.push(String::new());
2307    }
2308
2309    for (i, prompt) in prompts.iter().enumerate() {
2310        transcript_lines.push(format!("### Prompt {} ({})", i + 1, prompt.created_at.to_rfc3339()));
2311        transcript_lines.push(prompt.content.clone());
2312        transcript_lines.push(String::new());
2313    }
2314
2315    let transcript = transcript_lines.join("\n");
2316
2317    Ok(json!({
2318        "session": session,
2319        "prompts": prompts,
2320        "transcript": transcript,
2321        "prompt_count": prompts.len(),
2322        "layer": 3,
2323        "instruction": "This is the raw transcript (Layer 3). Used for full dialogue reconstruction."
2324    }))
2325}
2326
2327// --- Passive Capture Tool ---
2328
2329#[derive(Debug, Clone, Deserialize, JsonSchema)]
2330struct MemCapturePassiveParams {
2331    /// Session output text to parse and extract memories from
2332    text: String,
2333    #[serde(default)]
2334    project: Option<String>,
2335    /// Optional session ID to associate captured memories with
2336    #[serde(default)]
2337    session_id: Option<String>,
2338}
2339
2340fn mem_capture_passive(
2341    db: &Database,
2342    args: JsonObject,
2343    project: &str,
2344) -> crate::error::Result<serde_json::Value> {
2345    let params: MemCapturePassiveParams = serde_json::from_value(serde_json::Value::Object(args))
2346        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2347
2348    let project = params.project.unwrap_or_else(|| project.to_string());
2349    let session_id = params
2350        .session_id
2351        .as_deref()
2352        .map(Uuid::parse_str)
2353        .transpose()
2354        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2355
2356    let memories = db.memories().capture_passive(
2357        &params.text,
2358        &project,
2359        session_id,
2360        None,
2361        Some(db.embeddings()),
2362    )?;
2363
2364    Ok(json!({
2365        "captured": memories,
2366        "count": memories.len(),
2367        "project": project
2368    }))
2369}
2370
2371// --- Conflict Detection & Judgment Tools ---
2372
2373#[derive(Debug, Clone, Deserialize, JsonSchema)]
2374struct MemJudgeParams {
2375    /// ID of the existing/candidate memory (memory A)
2376    memory_id_a: String,
2377    /// ID of the new/incoming memory (memory B)
2378    memory_id_b: String,
2379    /// Optional candidate_id to update after judgment
2380    candidate_id: Option<i64>,
2381    /// The judged relation: conflicts_with, supersedes, extends, compatible, depends_on
2382    judged_relation: String,
2383    /// LLM's reasoning for the judgment
2384    reasoning: String,
2385}
2386
2387#[derive(Debug, Clone, Deserialize, JsonSchema)]
2388struct MemCompareParams {
2389    /// ID of the first memory
2390    memory_id_a: String,
2391    /// ID of the second memory
2392    memory_id_b: String,
2393}
2394
2395#[derive(Debug, Clone, Deserialize, JsonSchema)]
2396struct MemConflictCandidatesParams {
2397    #[serde(default)]
2398    project: Option<String>,
2399    /// Filter by status: pending, judged, dismissed (default: pending)
2400    #[serde(default = "default_pending")]
2401    status: String,
2402    #[serde(default = "default_limit_20")]
2403    limit: u32,
2404}
2405
2406fn default_pending() -> String {
2407    "pending".to_string()
2408}
2409
2410fn mem_judge(
2411    db: &Database,
2412    args: JsonObject,
2413    _project: &str,
2414) -> crate::error::Result<serde_json::Value> {
2415    let params: MemJudgeParams = serde_json::from_value(serde_json::Value::Object(args))
2416        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2417
2418    let memory_id_a = Uuid::parse_str(&params.memory_id_a)
2419        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2420    let memory_id_b = Uuid::parse_str(&params.memory_id_b)
2421        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2422
2423    // If candidate_id is provided, record the judgment
2424    if let Some(candidate_id) = params.candidate_id {
2425        let judgment = db.memories().judge_conflict(
2426            candidate_id,
2427            &params.judged_relation,
2428            &params.reasoning,
2429            "agent",
2430        )?;
2431        return Ok(json!({
2432            "judgment": judgment,
2433            "memory_id_a": params.memory_id_a,
2434            "memory_id_b": params.memory_id_b,
2435            "relation": params.judged_relation
2436        }));
2437    }
2438
2439    // Otherwise, just provide formatted context for LLM to reason about
2440    let context = db.memories().get_conflict_context(memory_id_a, memory_id_b)?;
2441    let memory_a = db.memories().get(memory_id_a)?;
2442    let memory_b = db.memories().get(memory_id_b)?;
2443
2444    Ok(json!({
2445        "context": context,
2446        "memory_a": memory_a,
2447        "memory_b": memory_b,
2448        "suggested_relations": ["compatible", "conflicts_with", "supersedes", "extends", "depends_on"],
2449        "instruction": "Analyze whether memory B conflicts with, extends, supersedes, depends_on, or is compatible with memory A."
2450    }))
2451}
2452
2453fn mem_compare(
2454    db: &Database,
2455    args: JsonObject,
2456    _project: &str,
2457) -> crate::error::Result<serde_json::Value> {
2458    let params: MemCompareParams = serde_json::from_value(serde_json::Value::Object(args))
2459        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2460
2461    let memory_id_a = Uuid::parse_str(&params.memory_id_a)
2462        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2463    let memory_id_b = Uuid::parse_str(&params.memory_id_b)
2464        .map_err(|e| crate::error::MnemeError::Config(e.to_string()))?;
2465
2466    let memory_a = db.memories().get(memory_id_a)?
2467        .ok_or_else(|| crate::error::MnemeError::NotFound(memory_id_a))?;
2468    let memory_b = db.memories().get(memory_id_b)?
2469        .ok_or_else(|| crate::error::MnemeError::NotFound(memory_id_b))?;
2470
2471    // Find shared entities
2472    let entity_store = db.entities();
2473    let shared_entities = entity_store.search_entities(&memory_a.title, None, 5)?;
2474
2475    // Check existing relations
2476    let existing_relations = db.memories().get_existing_relations(memory_id_a, memory_id_b).unwrap_or_default();
2477
2478    // Compare structured fields
2479    let comparison = json!({
2480        "memory_a": {
2481            "title": memory_a.title,
2482            "type": memory_a.memory_type.to_string(),
2483            "importance": memory_a.importance.to_string(),
2484            "topic_key": memory_a.topic_key,
2485            "tags": memory_a.tags,
2486            "content_preview": memory_a.content[..memory_a.content.len().min(200)].to_string()
2487        },
2488        "memory_b": {
2489            "title": memory_b.title,
2490            "type": memory_b.memory_type.to_string(),
2491            "importance": memory_b.importance.to_string(),
2492            "topic_key": memory_b.topic_key,
2493            "tags": memory_b.tags,
2494            "content_preview": memory_b.content[..memory_b.content.len().min(200)].to_string()
2495        },
2496        "shared_entities": shared_entities.iter().map(|e| e.entity.entity_name.clone()).collect::<Vec<_>>(),
2497        "existing_relations": existing_relations,
2498        "same_type": memory_a.memory_type == memory_b.memory_type,
2499        "same_topic_key": memory_a.topic_key.is_some() && memory_a.topic_key == memory_b.topic_key,
2500        "same_project": memory_a.project == memory_b.project,
2501    });
2502
2503    Ok(comparison)
2504}
2505
2506fn mem_conflict_candidates(
2507    db: &Database,
2508    args: JsonObject,
2509    project: &str,
2510) -> crate::error::Result<serde_json::Value> {
2511    let params: MemConflictCandidatesParams = serde_json::from_value(serde_json::Value::Object(args))
2512        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2513
2514    let project = params.project.unwrap_or_else(|| project.to_string());
2515    let status = if params.status.is_empty() { "pending" } else { &params.status };
2516    let candidates = db.memories().list_conflict_candidates(&project, Some(status), params.limit)?;
2517    Ok(serde_json::to_value(candidates)?)
2518}
2519
2520fn mem_entity_frequent(
2521    db: &Database,
2522    args: JsonObject,
2523    project: &str,
2524) -> crate::error::Result<serde_json::Value> {
2525    let params: MemEntityFrequentParams = serde_json::from_value(serde_json::Value::Object(args))
2526        .map_err(|e| crate::error::MnemeError::Config(format!("Invalid params: {}", e)))?;
2527
2528    let project = params.project.unwrap_or_else(|| project.to_string());
2529    let entity_store = db.entities();
2530    let frequent = entity_store.frequent_entities(&project, params.limit)?;
2531    Ok(serde_json::to_value(frequent)?)
2532}
2533
2534/// Returns the list of all MCP tool definitions, including plugin-provided tools.
2535pub fn list_tools(plugins: Option<&crate::plugins::PluginManager>) -> Vec<Tool> {
2536    let mut tools = vec![
2537        Tool::new(
2538            "mem_save",
2539            "Save a memory with deduplication and topic_key support",
2540            Arc::new(schema_for_type::<MemSaveParams>()),
2541        ),
2542        Tool::new(
2543            "mem_update",
2544            "Update an existing memory by ID",
2545            Arc::new(schema_for_type::<MemUpdateParams>()),
2546        ),
2547        Tool::new(
2548            "mem_delete",
2549            "Soft-delete a memory (hard delete optional)",
2550            Arc::new(schema_for_type::<MemDeleteParams>()),
2551        ),
2552        Tool::new(
2553            "mem_restore",
2554            "Restore a soft-deleted memory",
2555            Arc::new(schema_for_type::<MemRestoreParams>()),
2556        ),
2557        Tool::new(
2558            "mem_search",
2559            "Hybrid search (FTS5 + fuzzy matching)",
2560            Arc::new(schema_for_type::<MemSearchParams>()),
2561        ),
2562        Tool::new(
2563            "mem_get",
2564            "Get a memory by ID",
2565            Arc::new(schema_for_type::<MemGetParams>()),
2566        ),
2567        Tool::new(
2568            "mem_list",
2569            "List memories with optional filters",
2570            Arc::new(schema_for_type::<MemListParams>()),
2571        ),
2572        Tool::new(
2573            "mem_context",
2574            "Get recent memories for session context",
2575            Arc::new(schema_for_type::<MemContextParams>()),
2576        ),
2577        Tool::new(
2578            "mem_timeline",
2579            "Get memories around a specific memory",
2580            Arc::new(schema_for_type::<MemTimelineParams>()),
2581        ),
2582        Tool::new(
2583            "mem_session_start",
2584            "Start a new work session",
2585            Arc::new(schema_for_type::<MemSessionStartParams>()),
2586        ),
2587        Tool::new(
2588            "mem_session_end",
2589            "End an active session",
2590            Arc::new(schema_for_type::<MemSessionEndParams>()),
2591        ),
2592        Tool::new(
2593            "mem_session_summary",
2594            "Get the active session summary",
2595            Arc::new(schema_for_type::<MemSessionSummaryParams>()),
2596        ),
2597        Tool::new(
2598            "mem_stats",
2599            "Get project statistics",
2600            Arc::new(schema_for_type::<MemStatsParams>()),
2601        ),
2602        Tool::new(
2603            "mem_projects",
2604            "List all projects",
2605            Arc::new(schema_for_type::<MemProjectsParams>()),
2606        ),
2607        Tool::new(
2608            "mem_conflicts",
2609            "Detect potential conflicts",
2610            Arc::new(schema_for_type::<MemConflictsParams>()),
2611        ),
2612        Tool::new(
2613            "mem_save_prompt",
2614            "Save a user prompt",
2615            Arc::new(schema_for_type::<MemSavePromptParams>()),
2616        ),
2617        Tool::new(
2618            "mem_suggest_topic_key",
2619            "Suggest a topic_key from type and title",
2620            Arc::new(schema_for_type::<MemSuggestTopicKeyParams>()),
2621        ),
2622        Tool::new(
2623            "mem_current_project",
2624            "Detect current project from cwd/git",
2625            Arc::new(schema_for_type::<MemCurrentProjectParams>()),
2626        ),
2627        Tool::new(
2628            "mem_doctor",
2629            "Run diagnostics on the database",
2630            Arc::new(schema_for_type::<MemDoctorParams>()),
2631        ),
2632        Tool::new(
2633            "mem_similar",
2634            "Find memories semantically similar to a given memory or text",
2635            Arc::new(schema_for_type::<MemSimilarParams>()),
2636        ),
2637        Tool::new(
2638            "mem_save_batch",
2639            "Save multiple memories in one call",
2640            Arc::new(schema_for_type::<MemSaveBatchParams>()),
2641        ),
2642        Tool::new(
2643            "mem_delete_relation",
2644            "Delete a relation by ID",
2645            Arc::new(schema_for_type::<MemDeleteRelationParams>()),
2646        ),
2647        Tool::new(
2648            "mem_audit",
2649            "Run quality audit on project memories",
2650            Arc::new(schema_for_type::<MemAuditParams>()),
2651        ),
2652        Tool::new(
2653            "mem_deduplicate",
2654            "Find semantically similar memories using embeddings",
2655            Arc::new(schema_for_type::<MemDeduplicateParams>()),
2656        ),
2657        Tool::new(
2658            "mem_feedback",
2659            "Record feedback on a memory",
2660            Arc::new(schema_for_type::<MemFeedbackParams>()),
2661        ),
2662        Tool::new(
2663            "mem_deprecate",
2664            "Mark a memory as deprecated",
2665            Arc::new(schema_for_type::<MemDeprecateParams>()),
2666        ),
2667        Tool::new(
2668            "mem_graph",
2669            "Get knowledge graph for a project",
2670            Arc::new(schema_for_type::<MemGraphParams>()),
2671        ),
2672        Tool::new(
2673            "mem_summarize",
2674            "Generate executive summary for a project or session",
2675            Arc::new(schema_for_type::<MemSummarizeParams>()),
2676        ),
2677        Tool::new(
2678            "mem_inject_context",
2679            "Generate formatted context block for prompt injection",
2680            Arc::new(schema_for_type::<MemInjectContextParams>()),
2681        ),
2682        Tool::new(
2683            "mem_forget_project",
2684            "Hard-delete all memories for a project",
2685            Arc::new(schema_for_type::<MemForgetProjectParams>()),
2686        ),
2687        Tool::new(
2688            "mem_health",
2689            "Get system health report",
2690            Arc::new(schema_for_type::<MemHealthParams>()),
2691        ),
2692        Tool::new(
2693            "mem_remind",
2694            "Get critical/high importance memories as reminders",
2695            Arc::new(schema_for_type::<MemRemindParams>()),
2696        ),
2697        Tool::new(
2698            "mem_tag_suggest",
2699            "Suggest tags based on project tags and content",
2700            Arc::new(schema_for_type::<MemTagSuggestParams>()),
2701        ),
2702        Tool::new(
2703            "mem_knowledge_gaps",
2704            "Analyze knowledge coverage gaps",
2705            Arc::new(schema_for_type::<MemKnowledgeGapsParams>()),
2706        ),
2707        Tool::new(
2708            "mem_sync_status",
2709            "Get sync status for a project",
2710            Arc::new(schema_for_type::<MemSyncStatusParams>()),
2711        ),
2712        Tool::new(
2713            "mem_sync_now",
2714            "Trigger sync with all auto-sync peers",
2715            Arc::new(schema_for_type::<MemSyncNowParams>()),
2716        ),
2717        Tool::new(
2718            "mem_sync_export",
2719            "Export project memories to sync file",
2720            Arc::new(schema_for_type::<MemSyncExportParams>()),
2721        ),
2722        Tool::new(
2723            "mem_encrypt",
2724            "Encrypt an existing memory",
2725            Arc::new(schema_for_type::<MemEncryptParams>()),
2726        ),
2727        Tool::new(
2728            "mem_decrypt",
2729            "Decrypt an existing memory",
2730            Arc::new(schema_for_type::<MemDecryptParams>()),
2731        ),
2732        Tool::new(
2733            "keys_list",
2734            "List registered encryption keys",
2735            Arc::new(schema_for_type::<KeysListParams>()),
2736        ),
2737        Tool::new(
2738            "keys_status",
2739            "Get encryption keys status",
2740            Arc::new(schema_for_type::<KeysStatusParams>()),
2741        ),
2742        Tool::new(
2743            "mem_entity_extract",
2744            "Extract entities from a memory or text",
2745            Arc::new(schema_for_type::<MemEntityExtractParams>()),
2746        ),
2747        Tool::new(
2748            "mem_entity_search",
2749            "Search memories by extracted entity",
2750            Arc::new(schema_for_type::<MemEntitySearchParams>()),
2751        ),
2752        Tool::new(
2753            "mem_entity_links",
2754            "Get entity-linked memories for a given memory",
2755            Arc::new(schema_for_type::<MemEntityLinksParams>()),
2756        ),
2757        Tool::new(
2758            "mem_entity_frequent",
2759            "Get most frequent entities in a project",
2760            Arc::new(schema_for_type::<MemEntityFrequentParams>()),
2761        ),
2762        Tool::new(
2763            "mem_cloud_enroll",
2764            "Enroll this project with a cloud sync server",
2765            Arc::new(schema_for_type::<MemCloudEnrollParams>()),
2766        ),
2767        Tool::new(
2768            "mem_cloud_sync",
2769            "Trigger a full cloud sync cycle",
2770            Arc::new(schema_for_type::<MemCloudSyncParams>()),
2771        ),
2772        Tool::new(
2773            "mem_cloud_status",
2774            "Check cloud sync status and recent sync history",
2775            Arc::new(schema_for_type::<MemCloudStatusParams>()),
2776        ),
2777        Tool::new(
2778            "mem_consolidate",
2779            "Consolidate stale/old/deprecated memories into auto-generated summary memories",
2780            Arc::new(schema_for_type::<MemConsolidateParams>()),
2781        ),
2782        Tool::new(
2783            "mem_block_set",
2784            "Set a memory block (slot: human/persona/workflow)",
2785            Arc::new(schema_for_type::<MemBlockSetParams>()),
2786        ),
2787        Tool::new(
2788            "mem_block_get",
2789            "Get a memory block by slot name",
2790            Arc::new(schema_for_type::<MemBlockGetParams>()),
2791        ),
2792        Tool::new(
2793            "mem_block_list",
2794            "List all memory blocks for a project",
2795            Arc::new(schema_for_type::<MemBlockListParams>()),
2796        ),
2797        Tool::new(
2798            "mem_learn_failures",
2799            "Analyze failed sessions and negative feedback to mine failure patterns and auto-generate corrective memories",
2800            Arc::new(schema_for_type::<MemLearnParams>()),
2801        ),
2802        Tool::new(
2803            "mem_session_outcome",
2804            "Record the outcome of a session (success/partial/failure with reasons)",
2805            Arc::new(schema_for_type::<MemSessionOutcomeParams>()),
2806        ),
2807        Tool::new(
2808            "mem_obsidian_export",
2809            "Export memories to an Obsidian vault (.md files with frontmatter + wikilinks)",
2810            Arc::new(schema_for_type::<MemObsidianExportParams>()),
2811        ),
2812        Tool::new(
2813            "mem_watch_scan",
2814            "Scan a directory and auto-index markdown files as memories",
2815            Arc::new(schema_for_type::<MemWatchScanParams>()),
2816        ),
2817        Tool::new(
2818            "mem_compress",
2819            "Compress a memory's content for token-efficient context injection",
2820            Arc::new(schema_for_type::<MemCompressParams>()),
2821        ),
2822        Tool::new(
2823            "mem_compress_batch",
2824            "Compress multiple memories in a project",
2825            Arc::new(schema_for_type::<MemCompressBatchParams>()),
2826        ),
2827        Tool::new(
2828            "mem_compress_context",
2829            "Generate a compressed context block for prompt injection",
2830            Arc::new(schema_for_type::<MemCompressContextParams>()),
2831        ),
2832        Tool::new(
2833            "mem_temporal_query",
2834            "Query facts that were valid at a specific point in time",
2835            Arc::new(schema_for_type::<MemTemporalQueryParams>()),
2836        ),
2837        Tool::new(
2838            "mem_expand",
2839            "[Layer 2] Get expanded view of a memory with full content, entities, and links",
2840            Arc::new(schema_for_type::<MemExpandParams>()),
2841        ),
2842        Tool::new(
2843            "mem_transcript",
2844            "[Layer 3] Get raw session transcript with all prompts",
2845            Arc::new(schema_for_type::<MemTranscriptParams>()),
2846        ),
2847        Tool::new(
2848            "mem_capture_passive",
2849            "Parse session output and auto-save memories (Key Learnings, Decisions, Architecture, etc.)",
2850            Arc::new(schema_for_type::<MemCapturePassiveParams>()),
2851        ),
2852        Tool::new(
2853            "mem_judge",
2854            "Judge the relationship between two memories (optionally recording the result)",
2855            Arc::new(schema_for_type::<MemJudgeParams>()),
2856        ),
2857        Tool::new(
2858            "mem_compare",
2859            "Compare two memories side-by-side with shared entities and existing relations",
2860            Arc::new(schema_for_type::<MemCompareParams>()),
2861        ),
2862        Tool::new(
2863            "mem_conflict_candidates",
2864            "List auto-detected conflict candidates for LLM judgment",
2865            Arc::new(schema_for_type::<MemConflictCandidatesParams>()),
2866        ),
2867    ];
2868
2869    // Append plugin-provided tools.
2870    if let Some(pm) = plugins {
2871        for pt in pm.plugin_tools() {
2872            tools.push(Tool::new(
2873                pt.name,
2874                pt.description,
2875                Arc::new(
2876                    pt.input_schema
2877                        .as_object()
2878                        .cloned()
2879                        .unwrap_or_default()
2880                        .into_iter()
2881                        .collect(),
2882                ),
2883            ));
2884        }
2885    }
2886
2887    tools
2888}