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

  1. Swap the decorator order: put @classmethod first, then @tool beneath it.
  2. Convert the classmethod to a staticmethod or module-level function if no cls access is needed.
  3. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/2d303b8676e2b776. Report an issue: GitHub.