{"record":{"id":"08f85a6e47ad0b94","repo":"PrefectHQ/fastmcp","slug":"cannot-resolve-tool-reference-fn-r","errorCode":null,"errorMessage":"Cannot resolve tool reference: {fn!r}","messagePattern":"Cannot resolve tool reference: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/apps/app.py","lineNumber":109,"sourceCode":"\n        fmeta: Any = None\n        try:\n            from fastmcp.decorators import get_fastmcp_meta\n\n            fmeta = get_fastmcp_meta(fn)\n        except Exception:\n            pass\n\n        if fmeta is not None:\n            name: str | None = getattr(fmeta, \"name\", None)\n            if name is not None:\n                return ResolvedTool(name=_prefix(name))\n\n        fn_name = getattr(fn, \"__name__\", None)\n        if fn_name is not None:\n            return ResolvedTool(name=_prefix(fn_name))\n\n        raise ValueError(f\"Cannot resolve tool reference: {fn!r}\")\n\n    return _resolve_tool_ref\n\n\ndef _dispatch_decorator(\n    name_or_fn: str | AnyFunction | None,\n    name: str | None,\n    register: Callable[[Any, str | None], Any],\n    decorator_name: str,\n) -> Any:\n    \"\"\"Shared dispatch logic for @app.tool() and @app.ui() calling patterns.\"\"\"\n    if inspect.isroutine(name_or_fn):\n        return register(name_or_fn, name)\n\n    if isinstance(name_or_fn, str):\n        if name is not None:\n            raise TypeError(\n                \"Cannot specify both a name as first argument and as keyword argument.\"","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/apps/app.py#L91-L127","documentation":"_resolve_tool_ref converts a tool reference (name string, callable, or Tool object) into a ResolvedTool. When the passed object is neither a string, a Tool, nor a callable carrying a __name__ attribute, resolution fails and this ValueError is raised. It protects the @app.tool registry from unresolvable references.","triggerScenarios":"Passing an arbitrary object (e.g. a dict, a class instance, a functools.partial-wrapped callable without __name__, or a lambda decorated to strip __name__) to a tool reference/lookup API that calls _resolve_tool_ref.","commonSituations":"Wrapping functions with decorators (e.g. functools.partial, custom decorators without functools.wraps) so __name__ disappears; passing a Mock or proxy object in tests; passing an already-serialized tool dict instead of its name.","solutions":["Pass the tool's name as a plain string instead of the object.","Ensure the callable has a __name__ attribute (apply functools.wraps in your decorator, or use the raw function).","Pass the actual Tool instance rather than a wrapper around it."],"exampleFix":"// before\nref = functools.partial(my_tool, flag=True)\napp.tool_ref(ref)\n// after\nref = functools.partial(my_tool, flag=True)\nref.__name__ = my_tool.__name__  # or pass \"my_tool\" as a string\napp.tool_ref(ref)","handlingStrategy":"type-guard","validationCode":"def is_resolvable_tool_ref(fn) -> bool:\n    if isinstance(fn, str):\n        return True\n    return callable(fn) and getattr(fn, \"__name__\", None) is not None","typeGuard":"def is_tool_ref(obj) -> bool:\n    return isinstance(obj, str) or (callable(obj) and hasattr(obj, \"__name__\"))","tryCatchPattern":"try:\n    resolved = register_tool_ref(fn)\nexcept ValueError as e:\n    if \"Cannot resolve tool reference\" in str(e):\n        resolved = register_tool_ref(getattr(fn, \"__name__\", str(fn)))","preventionTips":["Use functools.wraps in custom decorators so __name__ survives","Pass tool names as strings when you have a wrapper object","Avoid passing partials/proxies directly as tool references","Check hasattr(fn, '__name__') before registering"],"tags":["python","valueerror","tool-registration"],"backgroundTag":"tool-reference-unresolvable","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}