1- use std:: collections:: HashMap ;
1+ use std:: collections:: { HashMap , HashSet } ;
22
33use serde_json:: { Map , Value } ;
44
@@ -7,8 +7,71 @@ use crate::{
77 types:: { McpToolSParams , ParamTypes } ,
88} ;
99
10+ /// Resolves a $ref path to its target value in the schema.
11+ fn resolve_ref < ' a > (
12+ ref_path : & str ,
13+ root_schema : & ' a Value ,
14+ visited : & mut HashSet < String > ,
15+ ) -> DiscoveryResult < & ' a Value > {
16+ if !ref_path. starts_with ( "#/" ) {
17+ return Err ( DiscoveryError :: InvalidSchema ( format ! (
18+ "$ref '{}' must start with '#/'" ,
19+ ref_path
20+ ) ) ) ;
21+ }
22+
23+ if !visited. insert ( ref_path. to_string ( ) ) {
24+ return Err ( DiscoveryError :: InvalidSchema ( format ! (
25+ "Cycle detected in $ref path '{}'" ,
26+ ref_path
27+ ) ) ) ;
28+ }
29+
30+ let path = ref_path. trim_start_matches ( "#/" ) . split ( '/' ) ;
31+ let mut current = root_schema;
32+
33+ for segment in path {
34+ if segment. is_empty ( ) {
35+ return Err ( DiscoveryError :: InvalidSchema ( format ! (
36+ "Invalid $ref path '{}': empty segment" ,
37+ ref_path
38+ ) ) ) ;
39+ }
40+ current = match current {
41+ Value :: Object ( obj) => obj. get ( segment) . ok_or_else ( || {
42+ DiscoveryError :: InvalidSchema ( format ! (
43+ "Invalid $ref path '{}': segment '{}' not found" ,
44+ ref_path, segment
45+ ) )
46+ } ) ?,
47+ Value :: Array ( arr) => segment
48+ . parse :: < usize > ( )
49+ . ok ( )
50+ . and_then ( |i| arr. get ( i) )
51+ . ok_or_else ( || {
52+ DiscoveryError :: InvalidSchema ( format ! (
53+ "Invalid $ref path '{}': segment '{}' not found in array" ,
54+ ref_path, segment
55+ ) )
56+ } ) ?,
57+ _ => {
58+ return Err ( DiscoveryError :: InvalidSchema ( format ! (
59+ "Invalid $ref path '{}': cannot traverse into non-object/array" ,
60+ ref_path
61+ ) ) )
62+ }
63+ } ;
64+ }
65+
66+ Ok ( current)
67+ }
68+
1069/// Parses an object schema into a vector of `McpToolSParams`.
11- pub fn param_object ( object_map : & Map < String , Value > ) -> DiscoveryResult < Vec < McpToolSParams > > {
70+ pub fn param_object (
71+ object_map : & Map < String , Value > ,
72+ root_schema : & Value ,
73+ visited : & mut HashSet < String > ,
74+ ) -> DiscoveryResult < Vec < McpToolSParams > > {
1275 let properties = object_map
1376 . get ( "properties" )
1477 . and_then ( |v| v. as_object ( ) )
@@ -31,7 +94,7 @@ pub fn param_object(object_map: &Map<String, Value>) -> DiscoveryResult<Vec<McpT
3194 "Property '{}' is not an object" ,
3295 param_name
3396 ) ) ) ?;
34- let param_type = param_type ( param_value) ?;
97+ let param_type = param_type ( param_value, root_schema , visited ) ?;
3598 let param_description = object_map
3699 . get ( "description" )
37100 . and_then ( |v| v. as_str ( ) )
@@ -50,7 +113,26 @@ pub fn param_object(object_map: &Map<String, Value>) -> DiscoveryResult<Vec<McpT
50113}
51114
52115/// Determines the parameter type from a schema definition.
53- pub fn param_type ( type_info : & Map < String , Value > ) -> DiscoveryResult < ParamTypes > {
116+ pub fn param_type (
117+ type_info : & Map < String , Value > ,
118+ root_schema : & Value ,
119+ visited : & mut HashSet < String > ,
120+ ) -> DiscoveryResult < ParamTypes > {
121+ // Handle $ref
122+ if let Some ( ref_path) = type_info. get ( "$ref" ) {
123+ let ref_path_str = ref_path. as_str ( ) . ok_or ( DiscoveryError :: InvalidSchema (
124+ "$ref must be a string" . to_string ( ) ,
125+ ) ) ?;
126+ let ref_value = resolve_ref ( ref_path_str, root_schema, visited) ?;
127+ let ref_map = ref_value
128+ . as_object ( )
129+ . ok_or ( DiscoveryError :: InvalidSchema ( format ! (
130+ "$ref '{}' does not point to an object" ,
131+ ref_path_str
132+ ) ) ) ?;
133+ return param_type ( ref_map, root_schema, visited) ;
134+ }
135+
54136 // Check for 'enum' keyword
55137 if let Some ( enum_values) = type_info. get ( "enum" ) {
56138 let values = enum_values. as_array ( ) . ok_or ( DiscoveryError :: InvalidSchema (
@@ -90,6 +172,7 @@ pub fn param_type(type_info: &Map<String, Value>) -> DiscoveryResult<ParamTypes>
90172 ) ) ;
91173 }
92174
175+ // Check for 'anyOf'
93176 if let Some ( any_of) = type_info. get ( "anyOf" ) {
94177 let any_of_array = any_of. as_array ( ) . ok_or ( DiscoveryError :: InvalidSchema (
95178 "'anyOf' field must be an array" . to_string ( ) ,
@@ -104,7 +187,7 @@ pub fn param_type(type_info: &Map<String, Value>) -> DiscoveryResult<ParamTypes>
104187 let item_map = item. as_object ( ) . ok_or ( DiscoveryError :: InvalidSchema (
105188 "Items in 'anyOf' must be objects" . to_string ( ) ,
106189 ) ) ?;
107- enum_types. push ( param_type ( item_map) ?) ;
190+ enum_types. push ( param_type ( item_map, root_schema , visited ) ?) ;
108191 }
109192 return Ok ( ParamTypes :: Anyof ( enum_types) ) ;
110193 }
@@ -124,7 +207,7 @@ pub fn param_type(type_info: &Map<String, Value>) -> DiscoveryResult<ParamTypes>
124207 let item_map = item. as_object ( ) . ok_or ( DiscoveryError :: InvalidSchema (
125208 "Items in 'oneOf' must be objects" . to_string ( ) ,
126209 ) ) ?;
127- one_of_types. push ( param_type ( item_map) ?) ;
210+ one_of_types. push ( param_type ( item_map, root_schema , visited ) ?) ;
128211 }
129212 return Ok ( ParamTypes :: OneOf ( one_of_types) ) ;
130213 }
@@ -144,12 +227,12 @@ pub fn param_type(type_info: &Map<String, Value>) -> DiscoveryResult<ParamTypes>
144227 let item_map = item. as_object ( ) . ok_or ( DiscoveryError :: InvalidSchema (
145228 "Items in 'allOf' must be objects" . to_string ( ) ,
146229 ) ) ?;
147- all_of_types. push ( param_type ( item_map) ?) ;
230+ all_of_types. push ( param_type ( item_map, root_schema , visited ) ?) ;
148231 }
149232 return Ok ( ParamTypes :: AllOf ( all_of_types) ) ;
150233 }
151234
152- // other types
235+ // Other types
153236 let type_name =
154237 type_info
155238 . get ( "type" )
@@ -166,22 +249,34 @@ pub fn param_type(type_info: &Map<String, Value>) -> DiscoveryResult<ParamTypes>
166249 "Missing or invalid 'items' field in array type" . to_string ( ) ,
167250 ) ,
168251 ) ?;
169- Ok ( ParamTypes :: Array ( vec ! [ param_type( items_map) ?] ) )
252+ Ok ( ParamTypes :: Array ( vec ! [ param_type(
253+ items_map,
254+ root_schema,
255+ visited,
256+ ) ?] ) )
170257 }
171- "object" => Ok ( ParamTypes :: Object ( param_object ( type_info) ?) ) ,
258+ "object" => Ok ( ParamTypes :: Object ( param_object (
259+ type_info,
260+ root_schema,
261+ visited,
262+ ) ?) ) ,
172263 _ => Ok ( ParamTypes :: Primitive ( type_name. to_string ( ) ) ) ,
173264 }
174265}
175266
267+ /// Processes tool parameters with a given properties map and root schema.
176268pub fn tool_params (
177269 properties : & Option < HashMap < String , Map < String , Value > > > ,
270+ root_schema : & Value ,
178271) -> Vec < McpToolSParams > {
272+ let mut visited = HashSet :: new ( ) ;
179273 let result = properties. clone ( ) . map ( |props| {
180274 let mut params: Vec < _ > = props
181275 . iter ( )
182276 . map ( |( prop_name, prop_map) | {
183277 let param_name = prop_name. to_owned ( ) ;
184- let prop_type = param_type ( prop_map) . unwrap ( ) ;
278+ let prop_type = param_type ( prop_map, root_schema, & mut visited)
279+ . unwrap_or_else ( |_| ParamTypes :: Primitive ( "unknown" . to_string ( ) ) ) ;
185280 let prop_description = prop_map
186281 . get ( "description" )
187282 . and_then ( |v| v. as_str ( ) )
0 commit comments