33from  collections .abc  import  Awaitable , Callable , Sequence 
44from  itertools  import  chain 
55from  types  import  GenericAlias 
6- from  typing  import  Annotated , Any , ForwardRef ,  cast , get_args , get_origin , get_type_hints 
6+ from  typing  import  Annotated , Any , cast , get_args , get_origin , get_type_hints 
77
88import  pydantic_core 
99from  pydantic  import  (
1414    WithJsonSchema ,
1515    create_model ,
1616)
17- from  pydantic ._internal ._typing_extra  import  eval_type_backport 
1817from  pydantic .fields  import  FieldInfo 
1918from  pydantic .json_schema  import  GenerateJsonSchema , JsonSchemaWarningKind 
20- from  pydantic_core  import  PydanticUndefined 
19+ from  typing_extensions  import  is_typeddict 
20+ from  typing_inspection .introspection  import  UNKNOWN , AnnotationSource , inspect_annotation 
2121
2222from  mcp .server .fastmcp .exceptions  import  InvalidSignature 
2323from  mcp .server .fastmcp .utilities .logging  import  get_logger 
@@ -205,56 +205,47 @@ def func_metadata(
205205        - output_model: A pydantic model for the return type if output is structured 
206206        - output_conversion: Records how function output should be converted before returning. 
207207    """ 
208-     sig  =  _get_typed_signature (func )
208+     try :
209+         sig  =  inspect .signature (func , eval_str = True )
210+     except  NameError  as  e :
211+         # This raise could perhaps be skipped, and we (FastMCP) just call 
212+         # model_rebuild right before using it 🤷 
213+         raise  InvalidSignature (f"Unable to evaluate type annotations for callable { func .__name__ !r}  " ) from  e 
209214    params  =  sig .parameters 
210215    dynamic_pydantic_model_params : dict [str , Any ] =  {}
211-     globalns  =  getattr (func , "__globals__" , {})
212216    for  param  in  params .values ():
213217        if  param .name .startswith ("_" ):
214218            raise  InvalidSignature (f"Parameter { param .name }   of { func .__name__ }   cannot start with '_'" )
215219        if  param .name  in  skip_names :
216220            continue 
217-         annotation  =  param .annotation 
218- 
219-         # `x: None` / `x: None = None` 
220-         if  annotation  is  None :
221-             annotation  =  Annotated [
222-                 None ,
223-                 Field (default = param .default  if  param .default  is  not   inspect .Parameter .empty  else  PydanticUndefined ),
224-             ]
225- 
226-         # Untyped field 
227-         if  annotation  is  inspect .Parameter .empty :
228-             annotation  =  Annotated [
229-                 Any ,
230-                 Field (),
231-                 # 🤷 
232-                 WithJsonSchema ({"title" : param .name , "type" : "string" }),
233-             ]
234- 
235-         field_info  =  FieldInfo .from_annotated_attribute (
236-             _get_typed_annotation (annotation , globalns ),
237-             param .default  if  param .default  is  not   inspect .Parameter .empty  else  PydanticUndefined ,
238-         )
239221
222+         annotation  =  param .annotation  if  param .annotation  is  not   inspect .Parameter .empty  else  Any 
223+         field_name  =  param .name 
224+         field_kwargs : dict [str , Any ] =  {}
225+         field_metadata : list [Any ] =  []
226+ 
227+         if  param .annotation  is  inspect .Parameter .empty :
228+             field_metadata .append (WithJsonSchema ({"title" : param .name , "type" : "string" }))
240229        # Check if the parameter name conflicts with BaseModel attributes 
241230        # This is necessary because Pydantic warns about shadowing parent attributes 
242-         if  hasattr (BaseModel , param . name ) and  callable (getattr (BaseModel , param . name )):
231+         if  hasattr (BaseModel , field_name ) and  callable (getattr (BaseModel , field_name )):
243232            # Use an alias to avoid the shadowing warning 
244-             field_info .alias  =  param .name 
245-             field_info .validation_alias  =  param .name 
246-             field_info .serialization_alias  =  param .name 
247-             # Use a prefixed internal name 
248-             internal_name  =  f"field_{ param .name }  " 
249-             dynamic_pydantic_model_params [internal_name ] =  (field_info .annotation , field_info )
233+             field_kwargs ["alias" ] =  field_name 
234+             # Use a prefixed field name 
235+             field_name  =  f"field_{ field_name }  " 
236+ 
237+         if  param .default  is  not   inspect .Parameter .empty :
238+             dynamic_pydantic_model_params [field_name ] =  (
239+                 Annotated [(annotation , * field_metadata , Field (** field_kwargs ))],
240+                 param .default ,
241+             )
250242        else :
251-             dynamic_pydantic_model_params [param .name ] =  (field_info .annotation , field_info )
252-         continue 
243+             dynamic_pydantic_model_params [field_name ] =  Annotated [(annotation , * field_metadata , Field (** field_kwargs ))]
253244
254245    arguments_model  =  create_model (
255246        f"{ func .__name__ }  Arguments" ,
256-         ** dynamic_pydantic_model_params ,
257247        __base__ = ArgModelBase ,
248+         ** dynamic_pydantic_model_params ,
258249    )
259250
260251    if  structured_output  is  False :
@@ -265,15 +256,21 @@ def func_metadata(
265256    if  sig .return_annotation  is  inspect .Parameter .empty  and  structured_output  is  True :
266257        raise  InvalidSignature (f"Function { func .__name__ }  : return annotation required for structured output" )
267258
268-     output_info  =  FieldInfo .from_annotation (_get_typed_annotation (sig .return_annotation , globalns ))
269-     annotation  =  output_info .annotation 
259+     inspected_return_ann  =  inspect_annotation (sig .return_annotation , annotation_source = AnnotationSource .FUNCTION )
260+     return_type_expr  =  inspected_return_ann .type 
261+     if  return_type_expr  is  UNKNOWN  and  structured_output  is  True :
262+         # `return_type_expr` is `UNKNOWN` when a bare type qualifier is used (unlikely to happen as a return annotation 
263+         # because it doesn't make any sense, but technically possible). 
264+         raise  InvalidSignature (f"Function { func .__name__ }  : return annotation required for structured output" )
270265
271-     output_model , output_schema , wrap_output  =  _try_create_model_and_schema (annotation , func .__name__ , output_info )
266+     output_model , output_schema , wrap_output  =  _try_create_model_and_schema (
267+         sig .return_annotation , return_type_expr , func .__name__ 
268+     )
272269
273270    if  output_model  is  None  and  structured_output  is  True :
274271        # Model creation failed or produced warnings - no structured output 
275272        raise  InvalidSignature (
276-             f"Function { func .__name__ }  : return type { annotation }   is not serializable for structured output" 
273+             f"Function { func .__name__ }  : return type { return_type_expr }   is not serializable for structured output" 
277274        )
278275
279276    return  FuncMetadata (
@@ -285,10 +282,18 @@ def func_metadata(
285282
286283
287284def  _try_create_model_and_schema (
288-     annotation : Any , func_name : str , field_info : FieldInfo 
285+     annotation : Any ,
286+     type_expr : Any ,
287+     func_name : str ,
289288) ->  tuple [type [BaseModel ] |  None , dict [str , Any ] |  None , bool ]:
290289    """Try to create a model and schema for the given annotation without warnings. 
291290
291+     Args: 
292+         annotation: The original return annotation (may be wrapped in `Annotated` or type qualifiers). 
293+         type_expr: The underlying type expression derived from the return annotation 
294+             (`Annotated` and type qualifiers were stripped). 
295+         func_name: The name of the function. 
296+ 
292297    Returns: 
293298        tuple of (model or None, schema or None, wrap_output) 
294299        Model and schema are None if warnings occur or creation fails. 
@@ -298,58 +303,58 @@ def _try_create_model_and_schema(
298303    wrap_output  =  False 
299304
300305    # First handle special case: None 
301-     if  annotation  is  None :
302-         model  =  _create_wrapped_model (func_name , annotation ,  field_info )
306+     if  type_expr  is  None :
307+         model  =  _create_wrapped_model (func_name , type_expr )
303308        wrap_output  =  True 
304309
305310    # Handle GenericAlias types (list[str], dict[str, int], Union[str, int], etc.) 
306-     elif  isinstance (annotation , GenericAlias ):
307-         origin  =  get_origin (annotation )
311+     elif  isinstance (type_expr , GenericAlias ):
312+         origin  =  get_origin (type_expr )
308313
309314        # Special case: dict with string keys can use RootModel 
310315        if  origin  is  dict :
311-             args  =  get_args (annotation )
316+             args  =  get_args (type_expr )
312317            if  len (args ) ==  2  and  args [0 ] is  str :
313-                 model  =  _create_dict_model (func_name , annotation )
318+                 model  =  _create_dict_model (func_name , type_expr )
314319            else :
315320                # dict with non-str keys needs wrapping 
316-                 model  =  _create_wrapped_model (func_name , annotation ,  field_info )
321+                 model  =  _create_wrapped_model (func_name , type_expr )
317322                wrap_output  =  True 
318323        else :
319324            # All other generic types need wrapping (list, tuple, Union, Optional, etc.) 
320-             model  =  _create_wrapped_model (func_name , annotation ,  field_info )
325+             model  =  _create_wrapped_model (func_name , type_expr )
321326            wrap_output  =  True 
322327
323328    # Handle regular type objects 
324-     elif  isinstance (annotation , type ):
325-         type_annotation :  type [ Any ]  =  cast (type [Any ], annotation )
329+     elif  isinstance (type_expr , type ):
330+         type_annotation   =  cast (type [Any ], type_expr )
326331
327332        # Case 1: BaseModel subclasses (can be used directly) 
328-         if  issubclass (annotation , BaseModel ):
329-             model  =  annotation 
333+         if  issubclass (type_annotation , BaseModel ):
334+             model  =  type_annotation 
330335
331-         # Case 2: TypedDict (special dict subclass with __annotations__)  
332-         elif  hasattr (type_annotation ,  "__annotations__" )  and   issubclass ( annotation ,  dict ):
336+         # Case 2: TypedDicts:  
337+         elif  is_typeddict (type_annotation ):
333338            model  =  _create_model_from_typeddict (type_annotation )
334339
335340        # Case 3: Primitive types that need wrapping 
336-         elif  annotation  in  (str , int , float , bool , bytes , type (None )):
337-             model  =  _create_wrapped_model (func_name , annotation ,  field_info )
341+         elif  type_annotation  in  (str , int , float , bool , bytes , type (None )):
342+             model  =  _create_wrapped_model (func_name , type_annotation )
338343            wrap_output  =  True 
339344
340345        # Case 4: Other class types (dataclasses, regular classes with annotations) 
341346        else :
342347            type_hints  =  get_type_hints (type_annotation )
343348            if  type_hints :
344349                # Classes with type hints can be converted to Pydantic models 
345-                 model  =  _create_model_from_class (type_annotation )
350+                 model  =  _create_model_from_class (type_annotation ,  type_hints )
346351            # Classes without type hints are not serializable - model remains None 
347352
348353    # Handle any other types not covered above 
349354    else :
350355        # This includes typing constructs that aren't GenericAlias in Python 3.10 
351356        # (e.g., Union, Optional in some Python versions) 
352-         model  =  _create_wrapped_model (func_name , annotation ,  field_info )
357+         model  =  _create_wrapped_model (func_name , type_expr )
353358        wrap_output  =  True 
354359
355360    if  model :
@@ -363,40 +368,39 @@ def _try_create_model_and_schema(
363368            # ValueError: When there are issues with the type definition (including our custom warnings) 
364369            # SchemaError: When Pydantic can't build a schema 
365370            # ValidationError: When validation fails 
366-             logger .info (f"Cannot create schema for type { annotation }   in { func_name }  : { type (e ).__name__ }  : { e }  " )
371+             logger .info (f"Cannot create schema for type { type_expr }   in { func_name }  : { type (e ).__name__ }  : { e }  " )
367372            return  None , None , False 
368373
369374        return  model , schema , wrap_output 
370375
371376    return  None , None , False 
372377
373378
374- def  _create_model_from_class (cls : type [Any ]) ->  type [BaseModel ]:
379+ _no_default  =  object ()
380+ 
381+ 
382+ def  _create_model_from_class (cls : type [Any ], type_hints : dict [str , Any ]) ->  type [BaseModel ]:
375383    """Create a Pydantic model from an ordinary class. 
376384
377385    The created model will: 
378386    - Have the same name as the class 
379387    - Have fields with the same names and types as the class's fields 
380388    - Include all fields whose type does not include None in the set of required fields 
381389
382-     Precondition: cls must have type hints (i.e., get_type_hints(cls)  is non-empty) 
390+     Precondition: cls must have type hints (i.e., `type_hints`  is non-empty) 
383391    """ 
384-     type_hints  =  get_type_hints (cls )
385- 
386392    model_fields : dict [str , Any ] =  {}
387393    for  field_name , field_type  in  type_hints .items ():
388394        if  field_name .startswith ("_" ):
389395            continue 
390396
391-         default  =  getattr (cls , field_name , PydanticUndefined )
392-         field_info  =  FieldInfo .from_annotated_attribute (field_type , default )
393-         model_fields [field_name ] =  (field_info .annotation , field_info )
394- 
395-     # Create a base class with the config 
396-     class  BaseWithConfig (BaseModel ):
397-         model_config  =  ConfigDict (from_attributes = True )
397+         default  =  getattr (cls , field_name , _no_default )
398+         if  default  is  _no_default :
399+             model_fields [field_name ] =  field_type 
400+         else :
401+             model_fields [field_name ] =  (field_type , default )
398402
399-     return  create_model (cls .__name__ , ** model_fields ,  __base__ = BaseWithConfig )
403+     return  create_model (cls .__name__ , __config__ = ConfigDict ( from_attributes = True ),  ** model_fields )
400404
401405
402406def  _create_model_from_typeddict (td_type : type [Any ]) ->  type [BaseModel ]:
@@ -409,20 +413,18 @@ def _create_model_from_typeddict(td_type: type[Any]) -> type[BaseModel]:
409413
410414    model_fields : dict [str , Any ] =  {}
411415    for  field_name , field_type  in  type_hints .items ():
412-         field_info  =  FieldInfo .from_annotation (field_type )
413- 
414416        if  field_name  not  in   required_keys :
415417            # For optional TypedDict fields, set default=None 
416418            # This makes them not required in the Pydantic model 
417419            # The model should use exclude_unset=True when dumping to get TypedDict semantics 
418-             field_info . default  =  None 
419- 
420-         model_fields [field_name ] =  ( field_info . annotation ,  field_info ) 
420+             model_fields [ field_name ]  =  ( field_type ,  None ) 
421+          else : 
422+              model_fields [field_name ] =  field_type 
421423
422-     return  create_model (td_type .__name__ , ** model_fields ,  __base__ = BaseModel )
424+     return  create_model (td_type .__name__ , ** model_fields )
423425
424426
425- def  _create_wrapped_model (func_name : str , annotation : Any ,  field_info :  FieldInfo ) ->  type [BaseModel ]:
427+ def  _create_wrapped_model (func_name : str , annotation : Any ) ->  type [BaseModel ]:
426428    """Create a model that wraps a type in a 'result' field. 
427429
428430    This is used for primitive types, generic types like list/dict, etc. 
@@ -433,7 +435,7 @@ def _create_wrapped_model(func_name: str, annotation: Any, field_info: FieldInfo
433435    if  annotation  is  None :
434436        annotation  =  type (None )
435437
436-     return  create_model (model_name , result = ( annotation ,  field_info ),  __base__ = BaseModel )
438+     return  create_model (model_name , result = annotation )
437439
438440
439441def  _create_dict_model (func_name : str , dict_annotation : Any ) ->  type [BaseModel ]:
@@ -449,43 +451,6 @@ class DictModel(RootModel[dict_annotation]):
449451    return  DictModel 
450452
451453
452- def  _get_typed_annotation (annotation : Any , globalns : dict [str , Any ]) ->  Any :
453-     def  try_eval_type (value : Any , globalns : dict [str , Any ], localns : dict [str , Any ]) ->  tuple [Any , bool ]:
454-         try :
455-             return  eval_type_backport (value , globalns , localns ), True 
456-         except  NameError :
457-             return  value , False 
458- 
459-     if  isinstance (annotation , str ):
460-         annotation  =  ForwardRef (annotation )
461-         annotation , status  =  try_eval_type (annotation , globalns , globalns )
462- 
463-         # This check and raise could perhaps be skipped, and we (FastMCP) just call 
464-         # model_rebuild right before using it 🤷 
465-         if  status  is  False :
466-             raise  InvalidSignature (f"Unable to evaluate type annotation { annotation }  " )
467- 
468-     return  annotation 
469- 
470- 
471- def  _get_typed_signature (call : Callable [..., Any ]) ->  inspect .Signature :
472-     """Get function signature while evaluating forward references""" 
473-     signature  =  inspect .signature (call )
474-     globalns  =  getattr (call , "__globals__" , {})
475-     typed_params  =  [
476-         inspect .Parameter (
477-             name = param .name ,
478-             kind = param .kind ,
479-             default = param .default ,
480-             annotation = _get_typed_annotation (param .annotation , globalns ),
481-         )
482-         for  param  in  signature .parameters .values ()
483-     ]
484-     typed_return  =  _get_typed_annotation (signature .return_annotation , globalns )
485-     typed_signature  =  inspect .Signature (typed_params , return_annotation = typed_return )
486-     return  typed_signature 
487- 
488- 
489454def  _convert_to_content (
490455    result : Any ,
491456) ->  Sequence [ContentBlock ]:
0 commit comments