PrefectHQ/fastmcp · error · TypeError
First argument to @tool must be a function, string, or None,
Error message
First argument to @tool must be a function, string, or None, got {type(name_or_fn)} What it means
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.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/decorators/tools.py:338
self.add_tool(fn)
return fn
if inspect.isroutine(name_or_fn):
return decorate_and_register(name_or_fn, name)
elif isinstance(name_or_fn, str):
# Case 3: @tool("custom_name") - name passed as first argument
if name is not None:
raise TypeError(
"Cannot specify both a name as first argument and as keyword argument. "
f"Use either @tool('{name_or_fn}') or @tool(name='{name}'), not both."
)
tool_name = name_or_fn
elif name_or_fn is None:
# Case 4: @tool() or @tool(name="something") - use keyword name
tool_name = name
else:
raise TypeError(
f"First argument to @tool must be a function, string, or None, got {type(name_or_fn)}"
)
# Return partial for cases where we need to wait for the function
return partial(
self.tool,
name=tool_name,
version=version,
title=title,
description=description,
icons=icons,
tags=tags,
output_schema=output_schema,
annotations=annotations,
meta=meta,
enabled=enabled,
task=task,
timeout=timeout,View on GitHub (pinned to 1f02114297)
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.
Example fix
// before
@tool(compute(x=1))
def handler(x: int) -> int:
...
// after
@tool('compute')
def handler(x: int) -> int:
... Defensive patterns
Strategy: type-guard
Validate before calling
def check_first_arg(name_or_fn):
if not (inspect.isroutine(name_or_fn) or isinstance(name_or_fn, str) or name_or_fn is None):
raise TypeError(f'@tool first arg must be function/str/None, got {type(name_or_fn)}')
check_first_arg(compute) # ok
check_first_arg('name') # ok Type guard
def is_valid_tool_first_arg(x) -> bool:
import inspect
return inspect.isroutine(x) or isinstance(x, str) or x is None Try / catch
try:
mcp.add_tool(make_tool(x))
except TypeError as e:
if 'First argument to @tool' in str(e):
logging.error('pass the function itself, not its result')
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- mcp_tool() got unexpected keyword argument(s): {sorted(unkno
- mcp_resource() got unexpected keyword argument(s): {sorted(u
- mcp_prompt() got unexpected keyword argument(s): {sorted(unk
- Expected Resource, ResourceTemplate, or @resource-decorated
- To decorate a classmethod, use @classmethod above @tool. See
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/7d68590c9f913f48.
Report an issue: GitHub.