Skip to main content

crates_docs/tools/
mod.rs

1//! MCP tool module
2//!
3//! Provides MCP tools for Rust crate documentation lookup.
4//!
5//! # Tool List
6//!
7//! - `docs::lookup_crate::LookupCrateToolImpl`: Lookup crate documentation
8//! - `docs::search::SearchCratesToolImpl`: Search crates
9//! - `docs::lookup_item::LookupItemToolImpl`: Lookup specific items
10//! - `health::HealthCheckToolImpl`: Health check
11//!
12//! # Examples
13//!
14//! ```rust,no_run
15//! use std::sync::Arc;
16//! use crates_docs::tools::{ToolRegistry, create_default_registry};
17//! use crates_docs::tools::docs::DocService;
18//! use crates_docs::cache::memory::MemoryCache;
19//!
20//! let cache = Arc::new(MemoryCache::new(1000));
21//! let doc_service = Arc::new(DocService::new(cache).unwrap());
22//! let registry = create_default_registry(&doc_service);
23//! ```
24
25pub mod docs;
26pub mod health;
27
28use async_trait::async_trait;
29use rust_mcp_sdk::schema::{CallToolError, CallToolResult, Tool as McpTool};
30use std::collections::HashMap;
31use std::sync::Arc;
32
33/// Tool trait
34///
35/// Defines the basic interface for MCP tools, including getting tool definition and executing the tool.
36///
37/// # Implementations
38///
39/// All tools need to implement this trait to be registered with [`ToolRegistry`].
40#[async_trait]
41pub trait Tool: Send + Sync {
42    /// Get tool definition
43    ///
44    /// Returns the tool's metadata, including name, description, parameters, etc.
45    fn definition(&self) -> McpTool;
46
47    /// Execute tool
48    ///
49    /// # Arguments
50    ///
51    /// * `arguments` - Tool arguments (JSON format)
52    ///
53    /// # Returns
54    ///
55    /// Returns tool execution result or error
56    async fn execute(
57        &self,
58        arguments: serde_json::Value,
59    ) -> std::result::Result<CallToolResult, CallToolError>;
60}
61
62/// Tool registry
63///
64/// A tool registry using `HashMap` for O(1) lookup.
65///
66/// # Fields
67///
68/// - `tools`: Dictionary storing tools, keyed by tool name
69pub struct ToolRegistry {
70    tools: HashMap<String, Box<dyn Tool>>,
71}
72
73impl ToolRegistry {
74    /// Create a new tool registry
75    ///
76    /// # Examples
77    ///
78    /// ```rust
79    /// use crates_docs::tools::ToolRegistry;
80    ///
81    /// let registry = ToolRegistry::new();
82    /// assert!(registry.is_empty());
83    /// ```
84    #[must_use]
85    pub fn new() -> Self {
86        Self {
87            tools: HashMap::new(),
88        }
89    }
90
91    /// Register a tool
92    ///
93    /// # Arguments
94    ///
95    /// * `tool` - Tool instance implementing [`Tool`] trait
96    ///
97    /// # Returns
98    ///
99    /// Returns self for chaining
100    ///
101    /// # Examples
102    ///
103    /// ```rust,no_run
104    /// use crates_docs::tools::ToolRegistry;
105    /// use crates_docs::tools::health::HealthCheckToolImpl;
106    ///
107    /// let registry = ToolRegistry::new()
108    ///     .register(HealthCheckToolImpl::new());
109    /// ```
110    #[must_use]
111    pub fn register<T: Tool + 'static>(mut self, tool: T) -> Self {
112        let boxed_tool: Box<dyn Tool> = Box::new(tool);
113        let name = boxed_tool.definition().name.clone();
114        self.tools.insert(name, boxed_tool);
115        self
116    }
117
118    /// Get all tool definitions
119    ///
120    /// # Returns
121    ///
122    /// Returns a list of metadata for all registered tools
123    #[must_use]
124    pub fn get_tools(&self) -> Vec<McpTool> {
125        self.tools.values().map(|t| t.definition()).collect()
126    }
127
128    /// Execute tool by name
129    ///
130    /// # Arguments
131    ///
132    /// * `name` - Tool name
133    /// * `arguments` - Tool arguments (JSON format)
134    ///
135    /// # Returns
136    ///
137    /// Returns tool execution result, or error if tool not found
138    pub async fn execute_tool(
139        &self,
140        name: &str,
141        arguments: serde_json::Value,
142    ) -> std::result::Result<CallToolResult, CallToolError> {
143        match self.tools.get(name) {
144            Some(tool) => tool.execute(arguments).await,
145            None => Err(CallToolError::unknown_tool(name.to_string())),
146        }
147    }
148
149    /// Check if tool exists
150    ///
151    /// # Arguments
152    ///
153    /// * `name` - Tool name
154    ///
155    /// # Returns
156    ///
157    /// Returns `true` if tool exists, `false` otherwise
158    #[must_use]
159    pub fn has_tool(&self, name: &str) -> bool {
160        self.tools.contains_key(name)
161    }
162
163    /// Get number of registered tools
164    #[must_use]
165    pub fn len(&self) -> usize {
166        self.tools.len()
167    }
168
169    /// Check if registry is empty
170    #[must_use]
171    pub fn is_empty(&self) -> bool {
172        self.tools.is_empty()
173    }
174}
175
176impl Default for ToolRegistry {
177    fn default() -> Self {
178        Self::new()
179    }
180}
181
182/// Create default tool registry
183///
184/// Registers all built-in tools:
185/// - `lookup_crate`: Lookup crate documentation
186/// - `search_crates`: Search crates
187/// - `lookup_item`: Lookup specific items
188/// - `health_check`: Health check
189///
190/// # Arguments
191///
192/// * `service` - Document service instance
193///
194/// # Examples
195///
196/// ```rust,no_run
197/// use std::sync::Arc;
198/// use crates_docs::tools::create_default_registry;
199/// use crates_docs::tools::docs::DocService;
200/// use crates_docs::cache::memory::MemoryCache;
201///
202/// let cache = Arc::new(MemoryCache::new(1000));
203/// let doc_service = Arc::new(DocService::new(cache).unwrap());
204/// let registry = create_default_registry(&doc_service);
205/// ```
206#[must_use]
207pub fn create_default_registry(service: &Arc<docs::DocService>) -> ToolRegistry {
208    ToolRegistry::new()
209        .register(docs::lookup_crate::LookupCrateToolImpl::new(
210            service.clone(),
211        ))
212        .register(docs::search::SearchCratesToolImpl::new(service.clone()))
213        .register(docs::lookup_item::LookupItemToolImpl::new(service.clone()))
214        .register(health::HealthCheckToolImpl::new())
215}