PrefectHQ/fastmcp · error

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

The @tool decorator rejects classmethod objects directly. Applying @tool below @classmethod wraps the bound classmethod object instead of a function, so the decorator raises TypeError with a pointer to the documented stacking order.

Source

Thrown at fastmcp_slim/fastmcp/tools/function_tool.py:584

    Returns the original function with metadata attached. Register with a server
    using mcp.add_tool().

    Args:
        run_in_thread: Applies to sync tool functions only. When True (default),
            the sync function is dispatched to a worker thread so it does not
            block the event loop. Set to False to run the function inline on the
            event loop thread — useful for libraries with thread affinity
            (e.g. Windows COM via `uiautomation`/`comtypes`/`pywin32`, `tkinter`,
            some GPU/driver bindings). Ignored for async functions. Cannot be
            combined with `timeout` on a sync function: inline calls have no
            cancellation checkpoints, so the timeout would be a silent no-op.
    """
    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 attach_metadata(fn: F, tool_name: str | None) -> F:
        metadata = ToolMeta(
            name=tool_name,
            version=version,
            title=title,
            description=description,
            icons=icons,
            tags=tags,
            output_schema=output_schema,
            annotations=annotations,
            meta=meta,
            task=task,
            timeout=timeout,
            auth=auth,

View on GitHub (pinned to 1f02114297)

Solutions

  1. Reorder the decorators so @classmethod sits above @tool: @classmethod @tool def foo(cls): ...
  2. Convert the method to a regular method or staticmethod/plain function where appropriate
  3. Decorate inside the class body after instantiation-based registration is not needed — use the documented method pattern

Example fix

// before
@tool
@classmethod
def my_tool(cls, x: int) -> int: ...
// after
@classmethod
@tool
def my_tool(cls, x: int) -> int: ...
Defensive patterns

Strategy: type-guard

Validate before calling

def safe_tool(obj):
    import types
    if isinstance(obj, classmethod):
        raise TypeError('put @classmethod above @tool')
    return obj

Type guard

def is_classmethod_obj(obj) -> bool:
    return isinstance(obj, classmethod)

Try / catch

try:
    tool = tool_decorator(fn)
except TypeError as e:
    if 'classmethod' in str(e):
        tool = tool_decorator(classmethod(fn).__func__)  # or reorder decorators

Prevention

When it happens

Trigger: Writing: @tool @classmethod def foo(cls): ... — i.e. tool decorator applied before (above) classmethod, so name_or_fn is a classmethod instance.

Common situations: Defining tools as class methods on a server class and getting decorator order wrong; copying plain-function tool examples onto class-based code.

Related errors


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