PrefectHQ/fastmcp · error · TypeError
To decorate a classmethod, use @classmethod above @tool. See
Error message
To decorate a classmethod, use @classmethod above @tool. See https://gofastmcp.com/servers/tools#using-with-methods
What it means
@tool was applied directly to a classmethod object (i.e., @tool below @classmethod). FastMCP cannot introspect a classmethod wrapper as the tool function, so it raises and instructs to place @classmethod above @tool.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/decorators/tools.py:272
Example:
```python
provider = LocalProvider()
@provider.tool
def greet(name: str) -> str:
return f"Hello, {name}!"
@provider.tool("custom_name")
def my_tool(x: int) -> str:
return str(x)
```
"""
if isinstance(annotations, dict):
annotations = ToolAnnotations(**annotations)
if isinstance(name_or_fn, classmethod):
raise TypeError(
"To decorate a classmethod, use @classmethod above @tool. "
"See https://gofastmcp.com/servers/tools#using-with-methods"
)
def decorate_and_register(
fn: AnyFunction, tool_name: str | None
) -> FunctionTool | AnyFunction:
# Check for unbound method
try:
params = list(inspect.signature(fn).parameters.keys())
except (ValueError, TypeError):
params = []
if params and params[0] in ("self", "cls"):
fn_name = getattr(fn, "__name__", "function")
raise TypeError(
f"The function '{fn_name}' has '{params[0]}' as its first parameter. "
f"Use the standalone @tool decorator and register the bound method:\n\n"
f" from fastmcp.tools import tool\n\n"View on GitHub (pinned to 1f02114297)
Solutions
- Swap the decorator order: put @classmethod first, then @tool beneath it.
- Convert the classmethod to a staticmethod or module-level function if no cls access is needed.
- Decorate with the standalone @tool and register the already-classmethod'd bound method via mcp.add_tool(MyClass.method).
Example fix
// before
class MyClass:
@tool
@classmethod
def create(cls, name: str) -> str:
...
// after
class MyClass:
@classmethod
@tool
def create(cls, name: str) -> str:
... Defensive patterns
Strategy: type-guard
Validate before calling
import inspect
def safe_tool(obj):
if isinstance(obj, classmethod):
raise TypeError('place @classmethod above @tool')
return tool(obj) Type guard
def is_plain_callable(obj) -> bool:
return inspect.isroutine(obj) and not isinstance(obj, classmethod) Try / catch
try:
tool_fn = tool(MyClass.create)
except TypeError as e:
if 'use @classmethod above @tool' in str(e):
raise SyntaxHintError('reorder: @classmethod on top, @tool below') from e
raise Prevention
- Always order decorators as @classmethod then @tool (top to bottom).
- Prefer staticmethod for tools that don't need cls.
- Add a unit test that imports the tools module so decorator-order errors surface at test time.
When it happens
Trigger: Writing `@tool` above `@classmethod` in a class body, so `tool` receives the classmethod object as name_or_fn.
Common situations: Exposing class-level factory methods as tools; copying instance-method tool patterns and adapting to classmethods with the decorators in the wrong order.
Related errors
- The function '{fn_name}' has '{params[0]}' as its first para
- Cannot specify both a name as first argument and as keyword
- First argument to @tool must be a function, string, or None,
- mcp_tool() got unexpected keyword argument(s): {sorted(unkno
- mcp_resource() got unexpected keyword argument(s): {sorted(u
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/2d303b8676e2b776.
Report an issue: GitHub.