rmcp_openapi/tool/
tool_collection.rs1use super::Tool;
2use crate::config::Authorization;
3use crate::error::{ToolCallError, ToolCallValidationError};
4use rmcp::model::{CallToolResult, Tool as McpTool};
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 authorization: Authorization,
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, authorization).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: Some(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 security: None,
160 parameter_mappings: std::collections::HashMap::new(),
161 };
162 Tool::new(metadata, None, None).unwrap()
163 }
164
165 #[test]
166 fn test_tool_collection_creation() {
167 let collection = ToolCollection::new();
168 assert_eq!(collection.len(), 0);
169 assert!(collection.is_empty());
170 }
171
172 #[test]
173 fn test_tool_collection_from_tools() {
174 let tool1 = create_test_tool("test1", "Test tool 1");
175 let tool2 = create_test_tool("test2", "Test tool 2");
176 let tools = vec![tool1, tool2];
177
178 let collection = ToolCollection::from_tools(tools);
179 assert_eq!(collection.len(), 2);
180 assert!(!collection.is_empty());
181 assert!(collection.has_tool("test1"));
182 assert!(collection.has_tool("test2"));
183 assert!(!collection.has_tool("test3"));
184 }
185
186 #[test]
187 fn test_add_tool() {
188 let mut collection = ToolCollection::new();
189 let tool = create_test_tool("test", "Test tool");
190
191 collection.add_tool(tool);
192 assert_eq!(collection.len(), 1);
193 assert!(collection.has_tool("test"));
194 }
195
196 #[test]
197 fn test_get_tool_names() {
198 let tool1 = create_test_tool("getPetById", "Get pet by ID");
199 let tool2 = create_test_tool("getPetsByStatus", "Get pets by status");
200 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
201
202 let names = collection.get_tool_names();
203 assert_eq!(names, vec!["getPetById", "getPetsByStatus"]);
204 }
205
206 #[test]
207 fn test_get_tool() {
208 let tool = create_test_tool("test", "Test tool");
209 let collection = ToolCollection::from_tools(vec![tool]);
210
211 assert!(collection.get_tool("test").is_some());
212 assert!(collection.get_tool("nonexistent").is_none());
213 }
214
215 #[test]
216 fn test_to_mcp_tools() {
217 let tool1 = create_test_tool("test1", "Test tool 1");
218 let tool2 = create_test_tool("test2", "Test tool 2");
219 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
220
221 let mcp_tools = collection.to_mcp_tools();
222 assert_eq!(mcp_tools.len(), 2);
223 assert_eq!(mcp_tools[0].name, "test1");
224 assert_eq!(mcp_tools[1].name, "test2");
225 }
226
227 #[actix_web::test]
228 async fn test_call_tool_not_found_with_suggestions() {
229 let tool1 = create_test_tool("getPetById", "Get pet by ID");
230 let tool2 = create_test_tool("getPetsByStatus", "Get pets by status");
231 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
232
233 let result = collection
234 .call_tool("getPetByID", &json!({}), Authorization::default())
235 .await;
236 assert!(result.is_err());
237
238 if let Err(ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
239 tool_name,
240 suggestions,
241 })) = result
242 {
243 assert_eq!(tool_name, "getPetByID");
244 assert!(suggestions.contains(&"getPetById".to_string()));
246 assert!(!suggestions.is_empty());
247 } else {
248 panic!("Expected ToolNotFound error with suggestions");
249 }
250 }
251
252 #[actix_web::test]
253 async fn test_call_tool_not_found_no_suggestions() {
254 let tool = create_test_tool("getPetById", "Get pet by ID");
255 let collection = ToolCollection::from_tools(vec![tool]);
256
257 let result = collection
258 .call_tool(
259 "completelyDifferentName",
260 &json!({}),
261 Authorization::default(),
262 )
263 .await;
264 assert!(result.is_err());
265
266 if let Err(ToolCallError::Validation(ToolCallValidationError::ToolNotFound {
267 tool_name,
268 suggestions,
269 })) = result
270 {
271 assert_eq!(tool_name, "completelyDifferentName");
272 assert!(suggestions.is_empty());
273 } else {
274 panic!("Expected ToolNotFound error with no suggestions");
275 }
276 }
277
278 #[test]
279 fn test_iterators() {
280 let tool1 = create_test_tool("test1", "Test tool 1");
281 let tool2 = create_test_tool("test2", "Test tool 2");
282 let collection = ToolCollection::from_tools(vec![tool1, tool2]);
283
284 let names: Vec<String> = collection
286 .iter()
287 .map(|tool| tool.metadata.name.clone())
288 .collect();
289 assert_eq!(names, vec!["test1", "test2"]);
290
291 let names: Vec<String> = (&collection)
293 .into_iter()
294 .map(|tool| tool.metadata.name.clone())
295 .collect();
296 assert_eq!(names, vec!["test1", "test2"]);
297
298 let names: Vec<String> = collection
300 .into_iter()
301 .map(|tool| tool.metadata.name.clone())
302 .collect();
303 assert_eq!(names, vec!["test1", "test2"]);
304 }
305
306 #[test]
307 fn test_from_vec() {
308 let tool1 = create_test_tool("test1", "Test tool 1");
309 let tool2 = create_test_tool("test2", "Test tool 2");
310 let tools = vec![tool1, tool2];
311
312 let collection: ToolCollection = tools.into();
313 assert_eq!(collection.len(), 2);
314 }
315}