rmcp_openapi/tool/
metadata.rs1use rmcp::model::{Tool, ToolAnnotations};
2use serde_json::Value;
3use std::sync::Arc;
4
5#[derive(Debug, Clone, serde::Serialize)]
14pub struct ToolMetadata {
15 pub name: String,
17 pub title: Option<String>,
19 pub description: String,
21 pub parameters: Value,
23 pub output_schema: Option<Value>,
25 pub method: String,
27 pub path: String,
29}
30
31impl ToolMetadata {
32 pub fn is_parameter_required(&self, param_name: &str) -> bool {
34 self.parameters
35 .get("required")
36 .and_then(|r| r.as_array())
37 .map(|arr| arr.iter().any(|v| v.as_str() == Some(param_name)))
38 .unwrap_or(false)
39 }
40
41 pub fn is_parameter_array_type(&self, param_name: &str) -> bool {
43 self.parameters
44 .get("properties")
45 .and_then(|props| props.get(param_name))
46 .and_then(|param| param.get("type"))
47 .and_then(|type_val| type_val.as_str())
48 .map(|type_str| type_str == "array")
49 .unwrap_or(false)
50 }
51
52 pub fn has_parameter_default(&self, param_name: &str) -> bool {
54 self.parameters
55 .get("properties")
56 .and_then(|props| props.get(param_name))
57 .map(|param| param.get("default").is_some())
58 .unwrap_or(false)
59 }
60
61 pub fn is_empty_array(value: &Value) -> bool {
63 matches!(value, Value::Array(arr) if arr.is_empty())
64 }
65
66 pub fn should_omit_empty_array_parameter(&self, param_name: &str, value: &Value) -> bool {
68 !self.is_parameter_required(param_name)
74 && self.is_parameter_array_type(param_name)
75 && Self::is_empty_array(value)
76 && !self.has_parameter_default(param_name)
77 }
78}
79
80impl From<&ToolMetadata> for Tool {
85 fn from(metadata: &ToolMetadata) -> Self {
86 let input_schema = if let Value::Object(obj) = &metadata.parameters {
88 Arc::new(obj.clone())
89 } else {
90 Arc::new(serde_json::Map::new())
91 };
92
93 let output_schema = metadata.output_schema.as_ref().and_then(|schema| {
95 if let Value::Object(obj) = schema {
96 Some(Arc::new(obj.clone()))
97 } else {
98 None
99 }
100 });
101
102 let annotations = metadata.title.as_ref().map(|title| ToolAnnotations {
104 title: Some(title.clone()),
105 ..Default::default()
106 });
107
108 Tool {
109 name: metadata.name.clone().into(),
110 description: Some(metadata.description.clone().into()),
111 input_schema,
112 output_schema,
113 annotations,
114 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use serde_json::json;
123
124 fn create_test_metadata_with_params(parameters: Value) -> ToolMetadata {
125 ToolMetadata {
126 name: "test_tool".to_string(),
127 title: Some("Test Tool".to_string()),
128 description: "A test tool".to_string(),
129 parameters,
130 output_schema: None,
131 method: "GET".to_string(),
132 path: "/test".to_string(),
133 }
134 }
135
136 #[test]
137 fn test_is_parameter_required() {
138 let metadata = create_test_metadata_with_params(json!({
139 "type": "object",
140 "properties": {
141 "required_param": {"type": "string"},
142 "optional_param": {"type": "string"}
143 },
144 "required": ["required_param"]
145 }));
146
147 assert!(metadata.is_parameter_required("required_param"));
148 assert!(!metadata.is_parameter_required("optional_param"));
149 assert!(!metadata.is_parameter_required("nonexistent_param"));
150 }
151
152 #[test]
153 fn test_is_parameter_required_no_required_array() {
154 let metadata = create_test_metadata_with_params(json!({
155 "type": "object",
156 "properties": {
157 "param": {"type": "string"}
158 }
159 }));
160
161 assert!(!metadata.is_parameter_required("param"));
162 }
163
164 #[test]
165 fn test_is_parameter_array_type() {
166 let metadata = create_test_metadata_with_params(json!({
167 "type": "object",
168 "properties": {
169 "array_param": {"type": "array", "items": {"type": "string"}},
170 "string_param": {"type": "string"},
171 "number_param": {"type": "number"}
172 }
173 }));
174
175 assert!(metadata.is_parameter_array_type("array_param"));
176 assert!(!metadata.is_parameter_array_type("string_param"));
177 assert!(!metadata.is_parameter_array_type("number_param"));
178 assert!(!metadata.is_parameter_array_type("nonexistent_param"));
179 }
180
181 #[test]
182 fn test_has_parameter_default() {
183 let metadata = create_test_metadata_with_params(json!({
184 "type": "object",
185 "properties": {
186 "with_default": {"type": "string", "default": "test"},
187 "with_array_default": {"type": "array", "items": {"type": "string"}, "default": ["item1"]},
188 "without_default": {"type": "string"},
189 "with_null_default": {"type": "string", "default": null}
190 }
191 }));
192
193 assert!(metadata.has_parameter_default("with_default"));
194 assert!(metadata.has_parameter_default("with_array_default"));
195 assert!(!metadata.has_parameter_default("without_default"));
196 assert!(metadata.has_parameter_default("with_null_default")); assert!(!metadata.has_parameter_default("nonexistent_param"));
198 }
199
200 #[test]
201 fn test_is_empty_array() {
202 assert!(ToolMetadata::is_empty_array(&json!([])));
203 assert!(!ToolMetadata::is_empty_array(&json!(["item"])));
204 assert!(!ToolMetadata::is_empty_array(&json!("string")));
205 assert!(!ToolMetadata::is_empty_array(&json!(42)));
206 assert!(!ToolMetadata::is_empty_array(&json!(null)));
207 }
208
209 #[test]
210 fn test_should_omit_empty_array_parameter() {
211 let metadata1 = create_test_metadata_with_params(json!({
213 "type": "object",
214 "properties": {
215 "optional_array": {"type": "array", "items": {"type": "string"}}
216 },
217 "required": []
218 }));
219 assert!(metadata1.should_omit_empty_array_parameter("optional_array", &json!([])));
220 assert!(!metadata1.should_omit_empty_array_parameter("optional_array", &json!(["item"])));
221
222 let metadata2 = create_test_metadata_with_params(json!({
224 "type": "object",
225 "properties": {
226 "required_array": {"type": "array", "items": {"type": "string"}}
227 },
228 "required": ["required_array"]
229 }));
230 assert!(!metadata2.should_omit_empty_array_parameter("required_array", &json!([])));
231
232 let metadata3 = create_test_metadata_with_params(json!({
234 "type": "object",
235 "properties": {
236 "optional_array_with_default": {
237 "type": "array",
238 "items": {"type": "string"},
239 "default": ["default_item"]
240 }
241 },
242 "required": []
243 }));
244 assert!(
245 !metadata3.should_omit_empty_array_parameter("optional_array_with_default", &json!([]))
246 );
247
248 let metadata4 = create_test_metadata_with_params(json!({
250 "type": "object",
251 "properties": {
252 "optional_string": {"type": "string"}
253 },
254 "required": []
255 }));
256 assert!(!metadata4.should_omit_empty_array_parameter("optional_string", &json!([])));
257 }
258}