do-memory-mcp 0.1.46

Model Context Protocol (MCP) server for AI agents
//! Tool definitions for the lazy-loading registry
//!
//! This module defines the core and extended tools for the MCP server.

use crate::server::tool_definitions_extended;
use std::collections::HashMap;

#[path = "builder.rs"]
mod builder;
#[path = "builder_core.rs"]
mod builder_core;

use builder::create_additional_extended_tools;
use builder_core::create_core_tools;

/// Create the default tool registry with core and extended tools
pub fn create_default_registry() -> super::ToolRegistry {
    // Core tools: essential for basic operations
    let core_tools = create_core_tools();

    // All extended tools from existing tool definitions
    let extended_defs = tool_definitions_extended::create_extended_tools();
    let mut extended_tools = HashMap::new();

    // Convert Vec<Tool> to HashMap
    for tool in extended_defs {
        // Skip core tools that are already defined above
        if !core_tools.iter().any(|t| t.name == tool.name) {
            extended_tools.insert(tool.name.clone(), tool);
        }
    }

    // Add additional extended tools from tool_definitions.rs
    // These include advanced pattern analysis, embeddings, and pattern search
    let additional_tools = create_additional_extended_tools();
    for tool in additional_tools {
        // Skip if already in core tools
        if !core_tools.iter().any(|t| t.name == tool.name) &&
           // Skip if already in extended tools
           !extended_tools.contains_key(&tool.name)
        {
            extended_tools.insert(tool.name.clone(), tool);
        }
    }

    super::ToolRegistry::new(core_tools, extended_tools)
}

#[cfg(test)]
mod tests {
    use super::create_default_registry;

    #[test]
    fn default_registry_has_expected_core_tools() {
        let registry = create_default_registry();
        let core_names: Vec<String> = registry
            .get_core_tools()
            .into_iter()
            .map(|tool| tool.name)
            .collect();

        assert!(core_names.iter().any(|name| name == "query_memory"));
        assert!(core_names.iter().any(|name| name == "health_check"));
        assert!(core_names.iter().any(|name| name == "get_metrics"));
        assert!(core_names.iter().any(|name| name == "analyze_patterns"));
        assert!(core_names.iter().any(|name| name == "create_episode"));
    }

    #[test]
    fn default_registry_includes_extended_tools() {
        let registry = create_default_registry();

        assert!(registry.tool_exists("search_patterns"));
        assert!(registry.tool_exists("recommend_patterns"));
        assert!(registry.total_tool_count() > registry.get_core_tools().len());
    }

    /// Every tool advertised by both registries must be textually identical
    /// (name, description, and input_schema). ADR-080 adds `episode_id` to the
    /// recommendation tools, so they are built from shared constructors to
    /// guarantee the overlap cannot drift.
    #[test]
    fn overlapping_tools_are_identical_across_registries() {
        let default_tools = crate::server::tool_definitions::create_default_tools();
        let extended_tools = super::builder::create_additional_extended_tools();

        for default in &default_tools {
            if let Some(extended) = extended_tools.iter().find(|t| t.name == default.name) {
                assert_eq!(
                    extended.name, default.name,
                    "overlapping tool name mismatch"
                );
                assert_eq!(
                    extended.description, default.description,
                    "overlapping tool '{}' description diverged between registries",
                    default.name
                );
                assert_eq!(
                    extended.input_schema, default.input_schema,
                    "overlapping tool '{}' input_schema diverged between registries",
                    default.name
                );
            }
        }

        // The recommendation tools must be present in BOTH registries so the
        // attribution surface is exposed uniformly (ADR-080 ยง1, RAT-A5).
        for name in ["recommend_patterns", "recommend_playbook"] {
            assert!(
                default_tools.iter().any(|t| t.name == name),
                "'{name}' missing from create_default_tools()"
            );
            assert!(
                extended_tools.iter().any(|t| t.name == name),
                "'{name}' missing from create_additional_extended_tools()"
            );
        }
    }
}