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}