ricecoder_mcp/
metadata.rs

1//! Tool metadata management
2
3use serde_json::Value;
4
5/// Tool metadata
6#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
7pub struct ToolMetadata {
8    pub id: String,
9    pub name: String,
10    pub description: String,
11    pub category: String,
12    pub parameters: Vec<ParameterMetadata>,
13    pub return_type: String,
14    pub source: ToolSource,
15    pub server_id: Option<String>,
16}
17
18/// Parameter metadata
19#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20pub struct ParameterMetadata {
21    pub name: String,
22    pub type_: String,
23    pub description: String,
24    pub required: bool,
25    pub default: Option<Value>,
26}
27
28/// Tool source
29#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
30pub enum ToolSource {
31    BuiltIn,
32    Custom,
33    Mcp(String),
34}
35
36impl ToolMetadata {
37    /// Creates a new tool metadata
38    pub fn new(
39        id: String,
40        name: String,
41        description: String,
42        category: String,
43        return_type: String,
44        source: ToolSource,
45    ) -> Self {
46        Self {
47            id,
48            name,
49            description,
50            category,
51            parameters: Vec::new(),
52            return_type,
53            source,
54            server_id: None,
55        }
56    }
57
58    /// Adds a parameter to the tool
59    pub fn add_parameter(&mut self, parameter: ParameterMetadata) {
60        self.parameters.push(parameter);
61    }
62
63    /// Sets the server ID for MCP tools
64    pub fn set_server_id(&mut self, server_id: String) {
65        self.server_id = Some(server_id);
66    }
67
68    /// Gets the tool documentation
69    pub fn get_documentation(&self) -> String {
70        let mut doc = format!("# {}\n\n", self.name);
71        doc.push_str(&format!("**Description**: {}\n\n", self.description));
72        doc.push_str(&format!("**Category**: {}\n\n", self.category));
73
74        if !self.parameters.is_empty() {
75            doc.push_str("## Parameters\n\n");
76            for param in &self.parameters {
77                doc.push_str(&format!(
78                    "- **{}** ({}{}): {}\n",
79                    param.name,
80                    param.type_,
81                    if param.required { ", required" } else { "" },
82                    param.description
83                ));
84                if let Some(default) = &param.default {
85                    doc.push_str(&format!("  - Default: {}\n", default));
86                }
87            }
88            doc.push('\n');
89        }
90
91        doc.push_str(&format!("## Returns\n\n{}\n", self.return_type));
92
93        doc
94    }
95
96    /// Validates the tool metadata
97    pub fn validate(&self) -> Result<(), String> {
98        if self.id.is_empty() {
99            return Err("Tool ID cannot be empty".to_string());
100        }
101
102        if self.name.is_empty() {
103            return Err("Tool name cannot be empty".to_string());
104        }
105
106        if self.description.is_empty() {
107            return Err("Tool description cannot be empty".to_string());
108        }
109
110        if self.category.is_empty() {
111            return Err("Tool category cannot be empty".to_string());
112        }
113
114        if self.return_type.is_empty() {
115            return Err("Tool return type cannot be empty".to_string());
116        }
117
118        // Validate parameters
119        for param in &self.parameters {
120            if param.name.is_empty() {
121                return Err("Parameter name cannot be empty".to_string());
122            }
123
124            if param.type_.is_empty() {
125                return Err("Parameter type cannot be empty".to_string());
126            }
127
128            if param.description.is_empty() {
129                return Err("Parameter description cannot be empty".to_string());
130            }
131        }
132
133        Ok(())
134    }
135}
136
137impl ParameterMetadata {
138    /// Creates a new parameter metadata
139    pub fn new(
140        name: String,
141        type_: String,
142        description: String,
143        required: bool,
144    ) -> Self {
145        Self {
146            name,
147            type_,
148            description,
149            required,
150            default: None,
151        }
152    }
153
154    /// Sets the default value for the parameter
155    pub fn with_default(mut self, default: Value) -> Self {
156        self.default = Some(default);
157        self
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_create_tool_metadata() {
167        let tool = ToolMetadata::new(
168            "test-tool".to_string(),
169            "Test Tool".to_string(),
170            "A test tool".to_string(),
171            "test".to_string(),
172            "string".to_string(),
173            ToolSource::Custom,
174        );
175
176        assert_eq!(tool.id, "test-tool");
177        assert_eq!(tool.name, "Test Tool");
178        assert_eq!(tool.description, "A test tool");
179        assert_eq!(tool.category, "test");
180        assert_eq!(tool.return_type, "string");
181        assert!(tool.parameters.is_empty());
182        assert!(tool.server_id.is_none());
183    }
184
185    #[test]
186    fn test_add_parameter() {
187        let mut tool = ToolMetadata::new(
188            "test-tool".to_string(),
189            "Test Tool".to_string(),
190            "A test tool".to_string(),
191            "test".to_string(),
192            "string".to_string(),
193            ToolSource::Custom,
194        );
195
196        let param = ParameterMetadata::new(
197            "param1".to_string(),
198            "string".to_string(),
199            "First parameter".to_string(),
200            true,
201        );
202
203        tool.add_parameter(param);
204        assert_eq!(tool.parameters.len(), 1);
205        assert_eq!(tool.parameters[0].name, "param1");
206    }
207
208    #[test]
209    fn test_set_server_id() {
210        let mut tool = ToolMetadata::new(
211            "test-tool".to_string(),
212            "Test Tool".to_string(),
213            "A test tool".to_string(),
214            "test".to_string(),
215            "string".to_string(),
216            ToolSource::Mcp("server-1".to_string()),
217        );
218
219        tool.set_server_id("server-1".to_string());
220        assert_eq!(tool.server_id, Some("server-1".to_string()));
221    }
222
223    #[test]
224    fn test_get_documentation() {
225        let mut tool = ToolMetadata::new(
226            "test-tool".to_string(),
227            "Test Tool".to_string(),
228            "A test tool".to_string(),
229            "test".to_string(),
230            "string".to_string(),
231            ToolSource::Custom,
232        );
233
234        let param = ParameterMetadata::new(
235            "param1".to_string(),
236            "string".to_string(),
237            "First parameter".to_string(),
238            true,
239        );
240
241        tool.add_parameter(param);
242
243        let doc = tool.get_documentation();
244        assert!(doc.contains("Test Tool"));
245        assert!(doc.contains("A test tool"));
246        assert!(doc.contains("param1"));
247        assert!(doc.contains("First parameter"));
248    }
249
250    #[test]
251    fn test_validate_valid_tool() {
252        let tool = ToolMetadata::new(
253            "test-tool".to_string(),
254            "Test Tool".to_string(),
255            "A test tool".to_string(),
256            "test".to_string(),
257            "string".to_string(),
258            ToolSource::Custom,
259        );
260
261        assert!(tool.validate().is_ok());
262    }
263
264    #[test]
265    fn test_validate_empty_id() {
266        let tool = ToolMetadata::new(
267            "".to_string(),
268            "Test Tool".to_string(),
269            "A test tool".to_string(),
270            "test".to_string(),
271            "string".to_string(),
272            ToolSource::Custom,
273        );
274
275        assert!(tool.validate().is_err());
276    }
277
278    #[test]
279    fn test_parameter_with_default() {
280        let param = ParameterMetadata::new(
281            "param1".to_string(),
282            "string".to_string(),
283            "First parameter".to_string(),
284            false,
285        )
286        .with_default(Value::String("default_value".to_string()));
287
288        assert_eq!(param.default, Some(Value::String("default_value".to_string())));
289    }
290}