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