1pub 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#[async_trait]
15pub trait Tool: Send + Sync {
16 fn definition(&self) -> McpTool;
18
19 async fn execute(
21 &self,
22 arguments: serde_json::Value,
23 ) -> std::result::Result<CallToolResult, CallToolError>;
24}
25
26pub struct ToolRegistry {
28 tools: HashMap<String, Box<dyn Tool>>,
29}
30
31impl ToolRegistry {
32 #[must_use]
34 pub fn new() -> Self {
35 Self {
36 tools: HashMap::new(),
37 }
38 }
39
40 #[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 #[must_use]
51 pub fn get_tools(&self) -> Vec<McpTool> {
52 self.tools.values().map(|t| t.definition()).collect()
53 }
54
55 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 #[must_use]
69 pub fn has_tool(&self, name: &str) -> bool {
70 self.tools.contains_key(name)
71 }
72
73 #[must_use]
75 pub fn len(&self) -> usize {
76 self.tools.len()
77 }
78
79 #[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#[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}