1515logger = logging .getLogger (__name__ )
1616
1717
18- def _auto_convert_json_args (func : Callable ) -> Callable :
18+ def _is_dict_compatible_annotation (annotation ) -> bool :
19+ """Check if an annotation expects dict values that can be JSON-converted."""
20+ # Direct dict annotation
21+ if annotation is dict :
22+ return True
23+
24+ # Union types: Optional[dict], Union[dict, None], dict | None
25+ origin = get_origin (annotation )
26+ if origin is Union or (hasattr (annotation , '__class__' ) and annotation .__class__ .__name__ == 'UnionType' ):
27+ args = get_args (annotation )
28+ return dict in args
29+
30+ # Typed dict annotations: Dict[K, V], dict[str, Any]
31+ return bool (hasattr (annotation , '__origin__' ) and annotation .__origin__ is dict )
32+
33+
34+ def _wrap_with_json_conversion (func : Callable ) -> Callable :
1935 """
2036 Wrapper that automatically converts JSON string arguments to dictionaries.
2137
@@ -40,60 +56,36 @@ def _auto_convert_json_args(func: Callable) -> Callable:
4056
4157 def _should_convert_to_dict (annotation , value ):
4258 """Check if a parameter should be converted from JSON string to dict."""
43- if not isinstance (value , str ):
44- return False
45-
46- # Direct dict annotation
47- if annotation is dict :
48- return True
49-
50- # Optional[dict] or Union[dict, None] etc. (old typing.Union)
51- origin = get_origin (annotation )
52- if origin is Union :
53- args = get_args (annotation )
54- return dict in args
55-
56- # New Python 3.10+ union syntax: dict | None
57- if hasattr (annotation , '__class__' ) and annotation .__class__ .__name__ == 'UnionType' :
58- # For dict | None style unions
59- args = get_args (annotation )
60- return dict in args
61-
62- # Dict[K, V] style annotations
63- return bool (hasattr (annotation , '__origin__' ) and annotation .__origin__ is dict )
59+ return isinstance (value , str ) and _is_dict_compatible_annotation (annotation )
6460
65- def _modify_annotation_for_string_support (annotation ):
61+ def _add_string_to_annotation (annotation ):
6662 """Modify annotation to also accept strings for dict types."""
6763 # Direct dict annotation -> dict | str
6864 if annotation is dict :
6965 return dict | str
7066
71- # Optional[dict] or Union[dict, None] etc. (old typing.Union)
67+ # Union types: add str to existing union
7268 origin = get_origin (annotation )
7369 if origin is Union :
7470 args = get_args (annotation )
75- if dict in args :
76- # Add str to the union if it's not already there
77- if str not in args :
78- return Union [(* tuple (args ), str )]
79- return annotation
71+ if dict in args and str not in args :
72+ return Union [(* tuple (args ), str )]
73+ return annotation
8074
8175 # New Python 3.10+ union syntax: dict | None
8276 if hasattr (annotation , '__class__' ) and annotation .__class__ .__name__ == 'UnionType' :
8377 args = get_args (annotation )
84- if dict in args :
85- # Add str to the union if it's not already there
86- if str not in args :
87- # Reconstruct the union with str added
88- new_args = (* tuple (args ), str )
89- # Create new union type
90- result = new_args [0 ]
91- for arg in new_args [1 :]:
92- result = result | arg
93- return result
94- return annotation
78+ if dict in args and str not in args :
79+ # Reconstruct the union with str added
80+ new_args = (* tuple (args ), str )
81+ # Create new union type
82+ result = new_args [0 ]
83+ for arg in new_args [1 :]:
84+ result = result | arg
85+ return result
86+ return annotation
9587
96- # Dict[K, V] style annotations -> annotation | str
88+ # Typed dict annotations -> annotation | str
9789 if hasattr (annotation , '__origin__' ) and annotation .__origin__ is dict :
9890 return annotation | str
9991
@@ -103,7 +95,7 @@ def _modify_annotation_for_string_support(annotation):
10395 new_annotations = {}
10496 for param_name , param in sig .parameters .items ():
10597 if param .annotation != inspect .Parameter .empty :
106- new_annotations [param_name ] = _modify_annotation_for_string_support (param .annotation )
98+ new_annotations [param_name ] = _add_string_to_annotation (param .annotation )
10799 else :
108100 new_annotations [param_name ] = param .annotation
109101
@@ -136,6 +128,7 @@ async def async_wrapper(*args, **kwargs):
136128 # Set the modified annotations on the wrapper
137129 async_wrapper .__annotations__ = new_annotations
138130 return async_wrapper
131+
139132 @wraps (func )
140133 def sync_wrapper (* args , ** kwargs ):
141134 # Convert keyword arguments that should be dicts but are strings
@@ -162,7 +155,7 @@ def sync_wrapper(*args, **kwargs):
162155 return sync_wrapper
163156
164157
165- def _modify_schema_for_json_string_support (func : Callable , tool ) -> None :
158+ def _update_schema_for_json_args (func : Callable , tool ) -> None :
166159 """
167160 Modify the tool's JSON schema to accept strings for dict parameters.
168161
@@ -191,19 +184,7 @@ def _modify_schema_for_json_string_support(func: Callable, tool) -> None:
191184
192185 # Check if this parameter should support JSON string conversion
193186 annotation = param .annotation
194- should_support_string = False
195-
196- # Direct dict annotation
197- if annotation is dict :
198- should_support_string = True
199- # Optional[dict] or Union[dict, None] etc. (old typing.Union)
200- elif get_origin (annotation ) is Union or (hasattr (annotation , '__class__' ) and annotation .__class__ .__name__ == 'UnionType' ):
201- args = get_args (annotation )
202- if dict in args :
203- should_support_string = True
204- # Dict[K, V] style annotations
205- elif hasattr (annotation , '__origin__' ) and annotation .__origin__ is dict :
206- should_support_string = True
187+ should_support_string = _is_dict_compatible_annotation (annotation )
207188
208189 if should_support_string :
209190 # Modify the schema to also accept strings
@@ -292,15 +273,15 @@ def register_tool(
292273 )
293274
294275 # Apply auto-conversion wrapper (always enabled)
295- registered_func = _auto_convert_json_args (func )
276+ registered_func = _wrap_with_json_conversion (func )
296277 self .log .debug (f"Applied JSON argument auto-conversion wrapper to { tool_name } " )
297278
298279 # Register with FastMCP
299280 tool = self .mcp .tool (registered_func )
300281
301282 # Modify schema to support JSON strings for dict parameters
302283 if tool :
303- _modify_schema_for_json_string_support (func , tool )
284+ _update_schema_for_json_args (func , tool )
304285 self .log .debug (f"Modified schema for tool '{ tool_name } ' to accept JSON strings for dict parameters" )
305286
306287 # Keep track for listing
0 commit comments