Skip to main content

crates_docs/tools/
mod.rs

1//! MCP tools module
2//!
3//! Provides MCP tools for Rust crate documentation queries.
4
5pub mod docs;
6pub mod health;
7
8use async_trait::async_trait;
9use rust_mcp_sdk::schema::{CallToolError, CallToolResult, Tool as McpTool};
10use std::collections::HashMap;
11use std::sync::Arc;
12
13/// Tool trait
14#[async_trait]
15pub trait Tool: Send + Sync {
16    /// Get tool definition
17    fn definition(&self) -> McpTool;
18
19    /// Execute tool
20    async fn execute(
21        &self,
22        arguments: serde_json::Value,
23    ) -> std::result::Result<CallToolResult, CallToolError>;
24}
25
26/// Tool registry using `HashMap` for O(1) lookup
27pub struct ToolRegistry {
28    tools: HashMap<String, Box<dyn Tool>>,
29}
30
31impl ToolRegistry {
32    /// Create a new tool registry
33    #[must_use]
34    pub fn new() -> Self {
35        Self {
36            tools: HashMap::new(),
37        }
38    }
39
40    /// Register tool
41    #[must_use]
42    pub fn register<T: Tool + 'static>(mut self, tool: T) -> Self {
43        let boxed_tool: Box<dyn Tool> = Box::new(tool);
44        let name = boxed_tool.definition().name.clone();
45        self.tools.insert(name, boxed_tool);
46        self
47    }
48
49    /// Get all tool definitions
50    #[must_use]
51    pub fn get_tools(&self) -> Vec<McpTool> {
52        self.tools.values().map(|t| t.definition()).collect()
53    }
54
55    /// Execute tool by name
56    pub async fn execute_tool(
57        &self,
58        name: &str,
59        arguments: serde_json::Value,
60    ) -> std::result::Result<CallToolResult, CallToolError> {
61        match self.tools.get(name) {
62            Some(tool) => tool.execute(arguments).await,
63            None => Err(CallToolError::unknown_tool(name.to_string())),
64        }
65    }
66
67    /// Check if a tool exists
68    #[must_use]
69    pub fn has_tool(&self, name: &str) -> bool {
70        self.tools.contains_key(name)
71    }
72
73    /// Get the number of registered tools
74    #[must_use]
75    pub fn len(&self) -> usize {
76        self.tools.len()
77    }
78
79    /// Check if the registry is empty
80    #[must_use]
81    pub fn is_empty(&self) -> bool {
82        self.tools.is_empty()
83    }
84}
85
86impl Default for ToolRegistry {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92/// Create default tool registry
93#[must_use]
94pub fn create_default_registry(service: &Arc<docs::DocService>) -> ToolRegistry {
95    ToolRegistry::new()
96        .register(docs::lookup_crate::LookupCrateToolImpl::new(
97            service.clone(),
98        ))
99        .register(docs::search::SearchCratesToolImpl::new(service.clone()))
100        .register(docs::lookup_item::LookupItemToolImpl::new(service.clone()))
101        .register(health::HealthCheckToolImpl::new())
102}