diff --git a/fastmcp_slim/fastmcp/utilities/callable_utils.py b/fastmcp_slim/fastmcp/utilities/callable_utils.py index c572dc6ee..567434511 100644 --- a/fastmcp_slim/fastmcp/utilities/callable_utils.py +++ b/fastmcp_slim/fastmcp/utilities/callable_utils.py @@ -66,7 +66,21 @@ def prepare_callable(fn: Callable[..., Any]) -> Callable[..., Any]: # Strip __wrapped__ from partials so Pydantic sees the partial's own # signature with bound args removed, not the original function's signature. if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"): + old = fn fn = functools.partial(fn.func, *fn.args, **fn.keywords) + # Preserve annotation metadata copied by functools.update_wrapper + # (e.g. __module__, __qualname__) needed for deferred annotation + # resolution when `from __future__ import annotations` is active. + for attr in functools.WRAPPER_ASSIGNMENTS: + try: + val = getattr(old, attr) + except AttributeError: + pass + else: + try: + setattr(fn, attr, val) + except AttributeError: + pass # Callable classes (not routines, not partials) → unwrap to __call__ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):