1use crate::{Error, Parameter, ParameterType, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct ToolDefinition {
10 pub name: String,
12
13 #[serde(default)]
15 pub description: String,
16
17 #[serde(default)]
19 pub parameters: Vec<Parameter>,
20}
21
22impl ToolDefinition {
23 pub fn new(name: impl Into<String>) -> Self {
25 Self {
26 name: name.into(),
27 description: String::new(),
28 parameters: Vec::new(),
29 }
30 }
31
32 pub fn builder(name: impl Into<String>) -> ToolDefinitionBuilder {
34 ToolDefinitionBuilder::new(name)
35 }
36
37 pub fn get_parameter(&self, name: &str) -> Option<&Parameter> {
39 self.parameters.iter().find(|p| p.name == name)
40 }
41
42 pub fn required_parameters(&self) -> impl Iterator<Item = &Parameter> {
44 self.parameters.iter().filter(|p| p.required)
45 }
46
47 pub fn validate_args(&self, args: &Value) -> Result<()> {
49 let empty_map = serde_json::Map::new();
50 let args_obj = args.as_object().unwrap_or(&empty_map);
51
52 for param in self.required_parameters() {
54 if !args_obj.contains_key(¶m.name) {
55 if param.default.is_none() {
57 return Err(Error::MissingParameter(param.name.clone()));
58 }
59 }
60 }
61
62 for (key, value) in args_obj {
64 if let Some(param) = self.get_parameter(key) {
65 if !param.param_type.matches(value) {
66 return Err(Error::InvalidParameterType {
67 name: key.clone(),
68 expected: param.param_type.as_str().to_string(),
69 actual: json_type_name(value).to_string(),
70 });
71 }
72
73 if !param.enum_values.is_empty() && !param.enum_values.contains(value) {
75 return Err(Error::InvalidConfig(format!(
76 "parameter '{}' must be one of: {:?}",
77 key, param.enum_values
78 )));
79 }
80 }
81 }
82
83 Ok(())
84 }
85
86 pub fn to_mcp_input_schema(&self) -> serde_json::Value {
95 let mut properties = serde_json::Map::new();
96 let mut required: Vec<serde_json::Value> = Vec::new();
97
98 for param in &self.parameters {
99 let mut prop = serde_json::Map::new();
100 prop.insert(
101 "type".to_string(),
102 serde_json::Value::String(param.param_type.as_str().to_string()),
103 );
104 if !param.description.is_empty() {
105 prop.insert(
106 "description".to_string(),
107 serde_json::Value::String(param.description.clone()),
108 );
109 }
110 if !param.enum_values.is_empty() {
111 prop.insert(
112 "enum".to_string(),
113 serde_json::Value::Array(param.enum_values.clone()),
114 );
115 }
116 if let Some(default) = ¶m.default {
117 prop.insert("default".to_string(), default.clone());
118 }
119 properties.insert(param.name.clone(), serde_json::Value::Object(prop));
120
121 if param.required {
122 required.push(serde_json::Value::String(param.name.clone()));
123 }
124 }
125
126 serde_json::json!({
127 "type": "object",
128 "properties": properties,
129 "required": required,
130 })
131 }
132
133 pub fn parse_mcp_input_schema(schema: &serde_json::Value) -> Result<Vec<Parameter>> {
135 let mut params = Vec::new();
136
137 if let Some(properties) = schema.get("properties") {
138 if let Some(props_obj) = properties.as_object() {
139 for (name, prop) in props_obj {
140 let param_type = if let Some(type_val) = prop.get("type") {
141 match type_val.as_str() {
142 Some("string") => ParameterType::String,
143 Some("integer") => ParameterType::Integer,
144 Some("number") => ParameterType::Number,
145 Some("boolean") => ParameterType::Boolean,
146 Some("array") => ParameterType::Array,
147 Some("object") => ParameterType::Object,
148 _ => ParameterType::String,
149 }
150 } else {
151 ParameterType::String
152 };
153
154 let description = prop
155 .get("description")
156 .and_then(|v| v.as_str())
157 .unwrap_or("")
158 .to_string();
159
160 let required = if let Some(req_array) = schema.get("required") {
161 if let Some(arr) = req_array.as_array() {
162 arr.iter().any(|v| v.as_str() == Some(name))
163 } else {
164 false
165 }
166 } else {
167 false
168 };
169
170 params.push(Parameter {
171 name: name.to_string(),
172 param_type,
173 description,
174 required,
175 default: None,
176 enum_values: vec![],
177 });
178 }
179 }
180 }
181
182 Ok(params)
183 }
184}
185
186fn json_type_name(value: &Value) -> &'static str {
188 match value {
189 Value::Null => "null",
190 Value::Bool(_) => "boolean",
191 Value::Number(_) => "number",
192 Value::String(_) => "string",
193 Value::Array(_) => "array",
194 Value::Object(_) => "object",
195 }
196}
197
198#[derive(Debug, Default)]
200pub struct ToolDefinitionBuilder {
201 name: String,
202 description: String,
203 parameters: Vec<Parameter>,
204}
205
206impl ToolDefinitionBuilder {
207 pub fn new(name: impl Into<String>) -> Self {
209 Self {
210 name: name.into(),
211 ..Default::default()
212 }
213 }
214
215 pub fn description(mut self, description: impl Into<String>) -> Self {
217 self.description = description.into();
218 self
219 }
220
221 pub fn parameter(mut self, parameter: Parameter) -> Self {
223 self.parameters.push(parameter);
224 self
225 }
226
227 pub fn parameters(mut self, parameters: impl IntoIterator<Item = Parameter>) -> Self {
229 self.parameters.extend(parameters);
230 self
231 }
232
233 pub fn build(self) -> ToolDefinition {
235 ToolDefinition {
236 name: self.name,
237 description: self.description,
238 parameters: self.parameters,
239 }
240 }
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
245pub struct ToolCall {
246 pub tool: String,
248
249 #[serde(default)]
251 pub arguments: Value,
252}
253
254impl ToolCall {
255 pub fn new(tool: impl Into<String>) -> Self {
257 Self {
258 tool: tool.into(),
259 arguments: Value::Object(serde_json::Map::new()),
260 }
261 }
262
263 pub fn with_args(tool: impl Into<String>, arguments: Value) -> Self {
265 Self {
266 tool: tool.into(),
267 arguments,
268 }
269 }
270
271 pub fn builder(tool: impl Into<String>) -> ToolCallBuilder {
273 ToolCallBuilder::new(tool)
274 }
275}
276
277#[derive(Debug, Default)]
279pub struct ToolCallBuilder {
280 tool: String,
281 arguments: serde_json::Map<String, Value>,
282}
283
284impl ToolCallBuilder {
285 pub fn new(tool: impl Into<String>) -> Self {
287 Self {
288 tool: tool.into(),
289 arguments: serde_json::Map::new(),
290 }
291 }
292
293 pub fn arg(mut self, name: impl Into<String>, value: Value) -> Self {
295 self.arguments.insert(name.into(), value);
296 self
297 }
298
299 pub fn arg_str(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
301 self.arguments
302 .insert(name.into(), Value::String(value.into()));
303 self
304 }
305
306 pub fn arg_int(mut self, name: impl Into<String>, value: i64) -> Self {
308 self.arguments
309 .insert(name.into(), Value::Number(value.into()));
310 self
311 }
312
313 pub fn arg_bool(mut self, name: impl Into<String>, value: bool) -> Self {
315 self.arguments.insert(name.into(), Value::Bool(value));
316 self
317 }
318
319 pub fn build(self) -> ToolCall {
321 ToolCall {
322 tool: self.tool,
323 arguments: Value::Object(self.arguments),
324 }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct ToolResult {
331 pub success: bool,
333
334 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub data: Option<Value>,
337
338 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub error: Option<String>,
341
342 #[serde(default, skip_serializing_if = "Option::is_none")]
344 pub duration_ms: Option<u64>,
345}
346
347impl ToolResult {
348 pub fn success(data: Value) -> Self {
350 Self {
351 success: true,
352 data: Some(data),
353 error: None,
354 duration_ms: None,
355 }
356 }
357
358 pub fn failure(error: impl Into<String>) -> Self {
360 Self {
361 success: false,
362 data: None,
363 error: Some(error.into()),
364 duration_ms: None,
365 }
366 }
367
368 pub fn with_duration(mut self, duration_ms: u64) -> Self {
370 self.duration_ms = Some(duration_ms);
371 self
372 }
373
374 pub fn is_success(&self) -> bool {
376 self.success
377 }
378
379 pub fn into_data(self) -> Result<Value> {
381 if self.success {
382 self.data
383 .ok_or_else(|| Error::ExecutionFailed("no data returned".to_string()))
384 } else {
385 Err(Error::ExecutionFailed(
386 self.error.unwrap_or_else(|| "unknown error".to_string()),
387 ))
388 }
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use crate::ParameterType;
396 use serde_json::json;
397
398 #[test]
399 fn tool_definition_new() {
400 let tool = ToolDefinition::new("test_tool");
401 assert_eq!(tool.name, "test_tool");
402 assert!(tool.description.is_empty());
403 assert!(tool.parameters.is_empty());
404 }
405
406 #[test]
407 fn tool_definition_builder() {
408 let tool = ToolDefinition::builder("read_file")
409 .description("Read a file")
410 .parameter(Parameter::required_string("path"))
411 .build();
412
413 assert_eq!(tool.name, "read_file");
414 assert_eq!(tool.description, "Read a file");
415 assert_eq!(tool.parameters.len(), 1);
416 }
417
418 #[test]
419 fn tool_definition_get_parameter() {
420 let tool = ToolDefinition::builder("test")
421 .parameter(Parameter::required_string("path"))
422 .parameter(Parameter::optional_string("encoding"))
423 .build();
424
425 assert!(tool.get_parameter("path").is_some());
426 assert!(tool.get_parameter("encoding").is_some());
427 assert!(tool.get_parameter("nonexistent").is_none());
428 }
429
430 #[test]
431 fn tool_definition_required_parameters() {
432 let tool = ToolDefinition::builder("test")
433 .parameter(Parameter::required_string("required1"))
434 .parameter(Parameter::optional_string("optional1"))
435 .parameter(Parameter::required_string("required2"))
436 .build();
437
438 let required: Vec<_> = tool.required_parameters().collect();
439 assert_eq!(required.len(), 2);
440 assert!(required.iter().any(|p| p.name == "required1"));
441 assert!(required.iter().any(|p| p.name == "required2"));
442 }
443
444 #[test]
445 fn tool_definition_validate_args_success() {
446 let tool = ToolDefinition::builder("test")
447 .parameter(Parameter::required_string("name"))
448 .parameter(
449 Parameter::builder("count")
450 .param_type(ParameterType::Integer)
451 .build(),
452 )
453 .build();
454
455 let args = json!({"name": "test", "count": 5});
456 assert!(tool.validate_args(&args).is_ok());
457 }
458
459 #[test]
460 fn tool_definition_validate_args_missing_required() {
461 let tool = ToolDefinition::builder("test")
462 .parameter(Parameter::required_string("name"))
463 .build();
464
465 let args = json!({});
466 let result = tool.validate_args(&args);
467 assert!(matches!(result, Err(Error::MissingParameter(_))));
468 }
469
470 #[test]
471 fn tool_definition_validate_args_with_default() {
472 let tool = ToolDefinition::builder("test")
473 .parameter(
474 Parameter::builder("count")
475 .required(true)
476 .default(json!(10))
477 .build(),
478 )
479 .build();
480
481 let args = json!({});
482 assert!(tool.validate_args(&args).is_ok());
483 }
484
485 #[test]
486 fn tool_definition_validate_args_wrong_type() {
487 let tool = ToolDefinition::builder("test")
488 .parameter(
489 Parameter::builder("count")
490 .param_type(ParameterType::Integer)
491 .build(),
492 )
493 .build();
494
495 let args = json!({"count": "not a number"});
496 let result = tool.validate_args(&args);
497 assert!(matches!(result, Err(Error::InvalidParameterType { .. })));
498 }
499
500 #[test]
501 fn tool_definition_validate_args_enum() {
502 let tool = ToolDefinition::builder("test")
503 .parameter(
504 Parameter::builder("format")
505 .enum_value(json!("json"))
506 .enum_value(json!("yaml"))
507 .build(),
508 )
509 .build();
510
511 assert!(tool.validate_args(&json!({"format": "json"})).is_ok());
512 assert!(tool.validate_args(&json!({"format": "yaml"})).is_ok());
513 assert!(tool.validate_args(&json!({"format": "toml"})).is_err());
514 }
515
516 #[test]
517 fn tool_call_new() {
518 let call = ToolCall::new("test_tool");
519 assert_eq!(call.tool, "test_tool");
520 assert!(call.arguments.is_object());
521 }
522
523 #[test]
524 fn tool_call_with_args() {
525 let args = json!({"path": "/tmp/test.txt"});
526 let call = ToolCall::with_args("read_file", args.clone());
527 assert_eq!(call.tool, "read_file");
528 assert_eq!(call.arguments, args);
529 }
530
531 #[test]
532 fn tool_call_builder() {
533 let call = ToolCall::builder("github.list_repos")
534 .arg_str("owner", "octocat")
535 .arg_int("per_page", 10)
536 .arg_bool("include_forks", false)
537 .build();
538
539 assert_eq!(call.tool, "github.list_repos");
540 assert_eq!(call.arguments["owner"], "octocat");
541 assert_eq!(call.arguments["per_page"], 10);
542 assert_eq!(call.arguments["include_forks"], false);
543 }
544
545 #[test]
546 fn tool_result_success() {
547 let result = ToolResult::success(json!({"status": "ok"}));
548 assert!(result.is_success());
549 assert!(result.data.is_some());
550 assert!(result.error.is_none());
551 }
552
553 #[test]
554 fn tool_result_failure() {
555 let result = ToolResult::failure("Something went wrong");
556 assert!(!result.is_success());
557 assert!(result.data.is_none());
558 assert_eq!(result.error, Some("Something went wrong".to_string()));
559 }
560
561 #[test]
562 fn tool_result_with_duration() {
563 let result = ToolResult::success(json!(null)).with_duration(250);
564 assert_eq!(result.duration_ms, Some(250));
565 }
566
567 #[test]
568 fn tool_result_into_data_success() {
569 let result = ToolResult::success(json!({"value": 42}));
570 let data = result.into_data().unwrap();
571 assert_eq!(data["value"], 42);
572 }
573
574 #[test]
575 fn tool_result_into_data_failure() {
576 let result = ToolResult::failure("error");
577 let err = result.into_data().unwrap_err();
578 assert!(matches!(err, Error::ExecutionFailed(_)));
579 }
580
581 #[test]
582 fn tool_definition_serialization() {
583 let tool = ToolDefinition::builder("test")
584 .description("A test tool")
585 .parameter(Parameter::required_string("name"))
586 .build();
587
588 let json = serde_json::to_string(&tool).unwrap();
589 let parsed: ToolDefinition = serde_json::from_str(&json).unwrap();
590
591 assert_eq!(tool, parsed);
592 }
593
594 #[test]
595 fn tool_definition_validate_args_edge_cases() {
596 let tool = ToolDefinition::builder("test")
597 .parameter(
598 Parameter::builder("array_param")
599 .param_type(ParameterType::Array)
600 .required(true)
601 .build(),
602 )
603 .parameter(
604 Parameter::builder("object_param")
605 .param_type(ParameterType::Object)
606 .required(false)
607 .default(json!({}))
608 .build(),
609 )
610 .build();
611
612 assert!(tool
614 .validate_args(&json!({"array_param": [1, 2, 3], "object_param": {"key": "value"}}))
615 .is_ok());
616
617 assert!(tool.validate_args(&json!({"array_param": []})).is_ok());
619
620 assert!(tool.validate_args(&json!({"array_param": {}})).is_err());
622
623 assert!(tool
625 .validate_args(&json!({"array_param": [], "object_param": "not an object"}))
626 .is_err());
627 }
628
629 #[test]
630 fn tool_definition_validate_args_with_all_types() {
631 let tool = ToolDefinition::builder("test")
632 .parameter(Parameter::required_string("str_param"))
633 .parameter(
634 Parameter::builder("int_param")
635 .param_type(ParameterType::Integer)
636 .required(true)
637 .build(),
638 )
639 .parameter(
640 Parameter::builder("num_param")
641 .param_type(ParameterType::Number)
642 .required(true)
643 .build(),
644 )
645 .parameter(
646 Parameter::builder("bool_param")
647 .param_type(ParameterType::Boolean)
648 .required(true)
649 .build(),
650 )
651 .parameter(
652 Parameter::builder("arr_param")
653 .param_type(ParameterType::Array)
654 .required(true)
655 .build(),
656 )
657 .parameter(
658 Parameter::builder("obj_param")
659 .param_type(ParameterType::Object)
660 .required(true)
661 .build(),
662 )
663 .build();
664
665 let args = json!({
666 "str_param": "test",
667 "int_param": 42,
668 "num_param": 3.14,
669 "bool_param": true,
670 "arr_param": [1, 2, 3],
671 "obj_param": {"key": "value"}
672 });
673
674 assert!(tool.validate_args(&args).is_ok());
675
676 assert!(tool.validate_args(&json!({"str_param": 42, "int_param": 42, "num_param": 3.14, "bool_param": true, "arr_param": [], "obj_param": {}})).is_err());
678 assert!(tool.validate_args(&json!({"str_param": "test", "int_param": "not int", "num_param": 3.14, "bool_param": true, "arr_param": [], "obj_param": {}})).is_err());
679 }
680
681 #[test]
682 fn tool_definition_validate_args_empty_required() {
683 let tool = ToolDefinition::builder("test")
684 .parameter(Parameter::required_string("param1"))
685 .parameter(Parameter::required_string("param2"))
686 .parameter(Parameter::required_string("param3"))
687 .build();
688
689 assert!(tool.validate_args(&json!({})).is_err());
691
692 assert!(tool.validate_args(&json!({"param1": "value"})).is_err());
694
695 assert!(tool
697 .validate_args(&json!({"param1": "v1", "param2": "v2", "param3": "v3"}))
698 .is_ok());
699 }
700
701 #[test]
702 fn tool_call_serialization() {
703 let call = ToolCall::builder("test").arg_str("name", "value").build();
704
705 let json = serde_json::to_string(&call).unwrap();
706 let parsed: ToolCall = serde_json::from_str(&json).unwrap();
707
708 assert_eq!(call, parsed);
709 }
710
711 #[test]
712 fn tool_result_serialization() {
713 let result = ToolResult::success(json!({"data": [1, 2, 3]})).with_duration(100);
714
715 let json = serde_json::to_string(&result).unwrap();
716 let parsed: ToolResult = serde_json::from_str(&json).unwrap();
717
718 assert_eq!(result, parsed);
719 }
720
721 #[test]
722 fn parse_mcp_input_schema_basic() {
723 let schema = json!({
724 "type": "object",
725 "properties": {
726 "name": {
727 "type": "string",
728 "description": "The name"
729 },
730 "age": {
731 "type": "integer",
732 "description": "The age"
733 }
734 },
735 "required": ["name"]
736 });
737
738 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
739 assert_eq!(params.len(), 2);
740
741 let name_param = params.iter().find(|p| p.name == "name").unwrap();
742 assert_eq!(name_param.param_type, ParameterType::String);
743 assert_eq!(name_param.description, "The name");
744 assert!(name_param.required);
745
746 let age_param = params.iter().find(|p| p.name == "age").unwrap();
747 assert_eq!(age_param.param_type, ParameterType::Integer);
748 assert_eq!(age_param.description, "The age");
749 assert!(!age_param.required);
750 }
751
752 #[test]
753 fn parse_mcp_input_schema_all_types() {
754 let schema = json!({
755 "type": "object",
756 "properties": {
757 "str": {"type": "string"},
758 "num": {"type": "number"},
759 "int": {"type": "integer"},
760 "bool": {"type": "boolean"},
761 "arr": {"type": "array"},
762 "obj": {"type": "object"}
763 }
764 });
765
766 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
767 assert_eq!(params.len(), 6);
768
769 assert_eq!(
770 params.iter().find(|p| p.name == "str").unwrap().param_type,
771 ParameterType::String
772 );
773 assert_eq!(
774 params.iter().find(|p| p.name == "num").unwrap().param_type,
775 ParameterType::Number
776 );
777 assert_eq!(
778 params.iter().find(|p| p.name == "int").unwrap().param_type,
779 ParameterType::Integer
780 );
781 assert_eq!(
782 params.iter().find(|p| p.name == "bool").unwrap().param_type,
783 ParameterType::Boolean
784 );
785 assert_eq!(
786 params.iter().find(|p| p.name == "arr").unwrap().param_type,
787 ParameterType::Array
788 );
789 assert_eq!(
790 params.iter().find(|p| p.name == "obj").unwrap().param_type,
791 ParameterType::Object
792 );
793 }
794
795 #[test]
796 fn parse_mcp_input_schema_empty() {
797 let schema = json!({});
798 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
799 assert_eq!(params.len(), 0);
800 }
801
802 #[test]
803 fn parse_mcp_input_schema_no_properties() {
804 let schema = json!({"type": "object"});
805 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
806 assert_eq!(params.len(), 0);
807 }
808
809 #[test]
810 fn parse_mcp_input_schema_unknown_type() {
811 let schema = json!({
812 "type": "object",
813 "properties": {
814 "unknown": {"type": "unknown_type"}
815 }
816 });
817
818 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
819 assert_eq!(params.len(), 1);
820 assert_eq!(params[0].param_type, ParameterType::String);
822 }
823
824 #[test]
825 fn parse_mcp_input_schema_missing_type() {
826 let schema = json!({
827 "type": "object",
828 "properties": {
829 "field": {"description": "A field without type"}
830 }
831 });
832
833 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
834 assert_eq!(params.len(), 1);
835 assert_eq!(params[0].param_type, ParameterType::String);
837 }
838
839 #[test]
840 fn parse_mcp_input_schema_all_required() {
841 let schema = json!({
842 "type": "object",
843 "properties": {
844 "field1": {"type": "string"},
845 "field2": {"type": "string"},
846 "field3": {"type": "string"}
847 },
848 "required": ["field1", "field2", "field3"]
849 });
850
851 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
852 assert_eq!(params.len(), 3);
853 assert!(params.iter().all(|p| p.required));
854 }
855
856 #[test]
857 fn parse_mcp_input_schema_no_required() {
858 let schema = json!({
859 "type": "object",
860 "properties": {
861 "field1": {"type": "string"},
862 "field2": {"type": "string"}
863 }
864 });
865
866 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
867 assert_eq!(params.len(), 2);
868 assert!(params.iter().all(|p| !p.required));
869 }
870
871 #[test]
872 fn parse_mcp_input_schema_no_description() {
873 let schema = json!({
874 "type": "object",
875 "properties": {
876 "field": {"type": "string"}
877 }
878 });
879
880 let params = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
881 assert_eq!(params.len(), 1);
882 assert_eq!(params[0].description, "");
883 }
884
885 #[test]
886 fn to_mcp_input_schema_basic() {
887 let def = ToolDefinition::builder("test_tool")
888 .description("A test tool")
889 .parameter(Parameter::required_string("path"))
890 .parameter(Parameter::optional_string("encoding"))
891 .build();
892 let schema = def.to_mcp_input_schema();
893 assert_eq!(schema["type"], "object");
894 assert_eq!(schema["properties"]["path"]["type"], "string");
895 assert_eq!(schema["properties"]["encoding"]["type"], "string");
896 let required = schema["required"].as_array().expect("required is array");
897 assert_eq!(required.len(), 1);
898 assert_eq!(required[0], "path");
899 }
900
901 #[test]
902 fn to_mcp_input_schema_round_trip() {
903 let original = ToolDefinition::builder("rt")
904 .description("round trip")
905 .parameter(Parameter::required_string("name"))
906 .parameter(Parameter::optional_string("note"))
907 .build();
908 let schema = original.to_mcp_input_schema();
909 let parsed = ToolDefinition::parse_mcp_input_schema(&schema).unwrap();
910 assert_eq!(parsed.len(), original.parameters.len());
911 for orig in &original.parameters {
912 let p = parsed
913 .iter()
914 .find(|p| p.name == orig.name)
915 .unwrap_or_else(|| panic!("missing param {}", orig.name));
916 assert_eq!(p.param_type, orig.param_type);
917 assert_eq!(p.required, orig.required);
918 }
919 }
920
921 #[test]
922 fn to_mcp_input_schema_carries_enum_and_default() {
923 let mut param = Parameter::new("level");
924 param.param_type = ParameterType::String;
925 param.required = false;
926 param.enum_values = vec![json!("low"), json!("med"), json!("high")];
927 param.default = Some(json!("med"));
928 let def = ToolDefinition::builder("with_enum").parameter(param).build();
929
930 let schema = def.to_mcp_input_schema();
931 let level = &schema["properties"]["level"];
932 assert_eq!(level["type"], "string");
933 assert_eq!(level["enum"][0], "low");
934 assert_eq!(level["default"], "med");
935 }
936
937 #[test]
938 fn to_mcp_input_schema_empty_definition_yields_empty_properties() {
939 let def = ToolDefinition::new("noargs");
940 let schema = def.to_mcp_input_schema();
941 assert_eq!(schema["type"], "object");
942 assert!(schema["properties"].as_object().unwrap().is_empty());
943 assert!(schema["required"].as_array().unwrap().is_empty());
944 }
945}