rmcp_openapi/tool/
tool_collection.rs1use super::Tool;
2use crate::error::{ToolCallError, ToolCallValidationError};
3use rmcp::model::{CallToolResult, Tool as McpTool};
4use rmcp_actix_web::transport::AuthorizationHeader;
5use serde_json::Value;
6use tracing::debug_span;
7
8#[derive(Clone, Default)]
13pub struct ToolCollection {
14 tools: Vec<Tool>,
15}
16
17impl ToolCollection {
18 pub fn new() -> Self {
20 Self { tools: Vec::new() }
21 }
22
23 pub fn from_tools(tools: Vec<Tool>) -> Self {
25 Self { tools }
26 }
27
28 pub fn add_tool(&mut self, tool: Tool) {
30 self.tools.push(tool);
31 }
32
33 pub fn len(&self) -> usize {
35 self.tools.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
40 self.tools.is_empty()
41 }
42
43 pub fn get_tool_names(&self) -> Vec<String> {
45 self.tools
46 .iter()
47 .map(|tool| tool.metadata.name.clone())
48 .collect()
49 }
50
51 pub fn has_tool(&self, name: &str) -> bool {
53 self.tools.iter().any(|tool| tool.metadata.name == name)
54 }
55
56 pub fn get_tool(&self, name: &str) -> Option<&Tool> {
58 self.tools.iter().find(|tool| tool.metadata.name == name)
59 }
60
61 pub fn to_mcp_tools(&self) -> Vec<McpTool> {
63 self.tools.iter().map(McpTool::from).collect()
64 }
65
66 pub async fn call_tool(
73 &self,
74 tool_name: &str,
75 arguments: &Value,
76 auth_header: Option<AuthorizationHeader>,
77 ) -> Result<CallToolResult, ToolCallError> {
78 let span = debug_span!(
79 "tool_execution",
80 tool_name = %tool_name,
81 total_tools = self.tools.len()
82 );
83 let _enter = span.enter();
84
85 if let Some(tool) = self.get_tool(tool_name) {
87 tool.call(arguments, auth_header).await
89 } else {
90 let tool_names: Vec<&str> = self
92 .tools
93 .iter()
94 .map(|tool| tool.metadata.name.as_str())
95 .collect();
96
97 Err(ToolCallError::Validation(
98 ToolCallValidationError::tool_not_found(tool_name.to_string(), &tool_names),
99 ))
100 }
101 }
102
103 pub fn get_stats(&self) -> String {
105 format!("Total tools: {}", self.tools.len())
106 }
107
108 pub fn iter(&self) -> impl Iterator<Item = &Tool> {
110 self.tools.iter()
111 }
112}
113
114impl From<Vec<Tool>> for ToolCollection {
115 fn from(tools: Vec<Tool>) -> Self {
116 Self::from_tools(tools)
117 }
118}
119
120impl IntoIterator for ToolCollection {
121 type Item = Tool;
122 type IntoIter = std::vec::IntoIter<Tool>;
123
124 fn into_iter(self) -> Self::IntoIter {
125 self.tools.into_iter()
126 }
127}
128
129impl<'a> IntoIterator for &'a ToolCollection {
130 type Item = &'a Tool;
131 type IntoIter = std::slice::Iter<'a, Tool>;
132
133 fn into_iter(self) -> Self::IntoIter {
134 self.tools.iter()
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::tool::ToolMetadata;
142 use serde_json::json;
143
144 fn create_test_tool(name: &str, description: &str) -> Tool {
145 let metadata = ToolMetadata {
146 name: name.to_string(),
147 title: Some(name.to_string()),
148 description: description.to_string(),
149 parameters: json!({
150 "type": "object",
151 "properties": {
152 "id": {"type": "integer"}
153 },
154 "required": ["id"]
155 }),
156 output_schema: None,
157 method: "GET".to_string(),
158 path: format!("/{}", name),
159 };
160 Tool::new(metadata, None, None).unwrap()
161 }
162
163 #[test]
164 fn test_tool_collection_creation() {
165 let collection = ToolCollection::new();
166 assert_eq!(collection.len(), 0);
167 assert!(collection.is_empty());
168 }
169
170 #[test]
171 fn test_tool_collection_from_tools() {
172 let tool1 = create_test_tool("test1", "Test tool 1");
173 let tool2 = create_test_tool("test2", "Test tool 2");
174 let tools = vec![tool1, tool2];
175
176 let collection = ToolCollection::from_tools(tools);
177 assert_eq!(collection.len(), 2);
178 assert!(!collection.is_empty());
179 assert!(collection.has_tool("test1"));
180 assert!(collection.has_tool("test2"));
181 assert!(!collection.has_tool("test3"));
182 }
183
184 #[test]
185 fn test_add_tool() {
186 let mut collection = ToolCollection::new();
187 let tool = create_test_tool("test", "Test tool");
188
189 collection.add_tool(tool);
190 assert_eq!(collection.len(), 1);
191 assert!(collection.has_tool("test"));
192 }
193
194 #[test]
195 fn test_get_tool_names() {
196 let tool1 = create_test_tool("getPetById", "Get pet by ID");
197 let tool2 = create_test_tool("getPetsByStatus", "Get pets by status");
198 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
199
200 let names = collection.get_tool_names();
201 assert_eq!(names, vec!["getPetById", "getPetsByStatus"]);
202 }
203
204 #[test]
205 fn test_get_tool() {
206 let tool = create_test_tool("test", "Test tool");
207 let collection = ToolCollection::from_tools(vec![tool]);
208
209 assert!(collection.get_tool("test").is_some());
210 assert!(collection.get_tool("nonexistent").is_none());
211 }
212
213 #[test]
214 fn test_to_mcp_tools() {
215 let tool1 = create_test_tool("test1", "Test tool 1");
216 let tool2 = create_test_tool("test2", "Test tool 2");
217 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
218
219 let mcp_tools = collection.to_mcp_tools();
220 assert_eq!(mcp_tools.len(), 2);
221 assert_eq!(mcp_tools[0].name, "test1");
222 assert_eq!(mcp_tools[1].name, "test2");
223 }
224
225 #[actix_web::test]
226 async fn test_call_tool_not_found_with_suggestions() {
227 let tool1 = create_test_tool("getPetById", "Get pet by ID");
228 let tool2 = create_test_tool("getPetsByStatus", "Get pets by status");
229 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
230
231 let result = collection.call_tool("getPetByID", &json!({}), None).await;
232 assert!(result.is_err());
233
234 if let Err(ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
235 tool_name,
236 suggestions,
237 })) = result
238 {
239 assert_eq!(tool_name, "getPetByID");
240 assert!(suggestions.contains(&"getPetById".to_string()));
242 assert!(!suggestions.is_empty());
243 } else {
244 panic!("Expected ToolNotFound error with suggestions");
245 }
246 }
247
248 #[actix_web::test]
249 async fn test_call_tool_not_found_no_suggestions() {
250 let tool = create_test_tool("getPetById", "Get pet by ID");
251 let collection = ToolCollection::from_tools(vec![tool]);
252
253 let result = collection
254 .call_tool("completelyDifferentName", &json!({}), None)
255 .await;
256 assert!(result.is_err());
257
258 if let Err(ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
259 tool_name,
260 suggestions,
261 })) = result
262 {
263 assert_eq!(tool_name, "completelyDifferentName");
264 assert!(suggestions.is_empty());
265 } else {
266 panic!("Expected ToolNotFound error with no suggestions");
267 }
268 }
269
270 #[test]
271 fn test_iterators() {
272 let tool1 = create_test_tool("test1", "Test tool 1");
273 let tool2 = create_test_tool("test2", "Test tool 2");
274 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
275
276 let names: Vec<String> = collection
278 .iter()
279 .map(|tool| tool.metadata.name.clone())
280 .collect();
281 assert_eq!(names, vec!["test1", "test2"]);
282
283 let names: Vec<String> = (&collection)
285 .into_iter()
286 .map(|tool| tool.metadata.name.clone())
287 .collect();
288 assert_eq!(names, vec!["test1", "test2"]);
289
290 let names: Vec<String> = collection
292 .into_iter()
293 .map(|tool| tool.metadata.name.clone())
294 .collect();
295 assert_eq!(names, vec!["test1", "test2"]);
296 }
297
298 #[test]
299 fn test_from_vec() {
300 let tool1 = create_test_tool("test1", "Test tool 1");
301 let tool2 = create_test_tool("test2", "Test tool 2");
302 let tools = vec![tool1, tool2];
303
304 let collection: ToolCollection = tools.into();
305 assert_eq!(collection.len(), 2);
306 }
307}