{"record":{"id":"7d68590c9f913f48","repo":"PrefectHQ/fastmcp","slug":"first-argument-to-tool-must-be-a-function-string","errorCode":null,"errorMessage":"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}","messagePattern":"First argument to @tool must be a function, string, or None, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/providers/local_provider/decorators/tools.py","lineNumber":338,"sourceCode":"            self.add_tool(fn)\n            return fn\n\n        if inspect.isroutine(name_or_fn):\n            return decorate_and_register(name_or_fn, name)\n\n        elif isinstance(name_or_fn, str):\n            # Case 3: @tool(\"custom_name\") - name passed as first argument\n            if name is not None:\n                raise TypeError(\n                    \"Cannot specify both a name as first argument and as keyword argument. \"\n                    f\"Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both.\"\n                )\n            tool_name = name_or_fn\n        elif name_or_fn is None:\n            # Case 4: @tool() or @tool(name=\"something\") - use keyword name\n            tool_name = name\n        else:\n            raise TypeError(\n                f\"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}\"\n            )\n\n        # Return partial for cases where we need to wait for the function\n        return partial(\n            self.tool,\n            name=tool_name,\n            version=version,\n            title=title,\n            description=description,\n            icons=icons,\n            tags=tags,\n            output_schema=output_schema,\n            annotations=annotations,\n            meta=meta,\n            enabled=enabled,\n            task=task,\n            timeout=timeout,","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/providers/local_provider/decorators/tools.py#L320-L356","documentation":"The first positional argument to @tool must be a function, a string (tool name), or None. Anything else — a class, dict, partial, bound method wrapper of unsupported kind, etc. — is rejected with a TypeError showing the received type.","triggerScenarios":"Calling @tool with an unsupported first positional argument, e.g. @tool(SomeClass), @tool(fn()) (passing a result instead of the function), @tool({'name': ...}), or a non-string non-callable object.","commonSituations":"Passing a called function's return value instead of the callable; attempting to pass a description or return type positionally; wrapping functions in functools.partial and expecting tool to accept it positionally.","solutions":["Pass the function itself, a string name, or nothing: @tool, @tool('name'), or @tool().","If you called the function by mistake, drop the parentheses so the callable itself is passed.","Use keyword arguments (name=, description=) for everything besides the function/name."],"exampleFix":"// before\n@tool(compute(x=1))\ndef handler(x: int) -> int:\n    ...\n\n// after\n@tool('compute')\ndef handler(x: int) -> int:\n    ...","handlingStrategy":"type-guard","validationCode":"def check_first_arg(name_or_fn):\n    if not (inspect.isroutine(name_or_fn) or isinstance(name_or_fn, str) or name_or_fn is None):\n        raise TypeError(f'@tool first arg must be function/str/None, got {type(name_or_fn)}')\n\ncheck_first_arg(compute)  # ok\ncheck_first_arg('name')   # ok","typeGuard":"def is_valid_tool_first_arg(x) -> bool:\n    import inspect\n    return inspect.isroutine(x) or isinstance(x, str) or x is None","tryCatchPattern":"try:\n    mcp.add_tool(make_tool(x))\nexcept TypeError as e:\n    if 'First argument to @tool' in str(e):\n        logging.error('pass the function itself, not its result')\n    raise","preventionTips":["Pass callables without parentheses — @tool(fn), never @tool(fn()).","Use keyword arguments for everything besides the function/name.","Only str, function, or None are valid first args; anything else goes through keywords."],"tags":["python","fastmcp","tools","type-error"],"backgroundTag":"invalid-decorator-argument","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}