PrefectHQ/fastmcp · error · TypeError

First argument to @{decorator_name} must be a function, stri

Error message

First argument to @{decorator_name} must be a function, string, or None, got {type(name_or_fn)}

What it means

@app.tool() and @app.ui() use _dispatch_decorator to accept a function, a name string, or nothing. Any other first argument (e.g. a bool, dict, class, or an accidentally invoked decorator) cannot be interpreted and raises this TypeError.

Source

Thrown at fastmcp_slim/fastmcp/apps/app.py:133

    name_or_fn: str | AnyFunction | None,
    name: str | None,
    register: Callable[[Any, str | None], Any],
    decorator_name: str,
) -> Any:
    """Shared dispatch logic for @app.tool() and @app.ui() calling patterns."""
    if inspect.isroutine(name_or_fn):
        return register(name_or_fn, name)

    if isinstance(name_or_fn, str):
        if name is not None:
            raise TypeError(
                "Cannot specify both a name as first argument and as keyword argument."
            )
        tool_name: str | None = name_or_fn
    elif name_or_fn is None:
        tool_name = name
    else:
        raise TypeError(
            f"First argument to @{decorator_name} must be a function, string, or None, "
            f"got {type(name_or_fn)}"
        )

    def decorator(fn: F) -> F:
        return register(fn, tool_name)

    return decorator


# ---------------------------------------------------------------------------
# FastMCPApp
# ---------------------------------------------------------------------------


class FastMCPApp(Provider):
    """A Provider that represents an MCP application.

View on GitHub (pinned to 1f02114297)

Solutions

  1. Pass only a function, a string tool name, or nothing: `@app.tool`, `@app.tool("name")`, or `@app.tool()`.
  2. Ensure the decorated target is a plain function/routine (use staticmethod-free, plain defs).
  3. Check for stray positional arguments (e.g. moving kwargs like description into the correct supported signature).

Example fix

// before
@app.tool({"name": "upload"})
def upload(...): ...
// after
@app.tool("upload")
def upload(...): ...
Defensive patterns

Strategy: type-guard

Validate before calling

def is_valid_decorator_arg(x) -> bool:
    import inspect
    return x is None or isinstance(x, str) or inspect.isroutine(x)

Type guard

import inspect
def is_decorator_target(x) -> bool:
    return x is None or isinstance(x, str) or inspect.isroutine(x)

Try / catch

try:
    app.tool(target)
except TypeError as e:
    if "First argument to @" in str(e):
        raise ValueError(f"Unsupported decorator target: {target!r}") from e

Prevention

When it happens

Trigger: Calling `@app.tool(some_object)` where some_object is not a callable routine, string, or None — commonly `@app.tool` misuse like `@app.tool(description="...")`-style kwargs not supported, or `app.tool(True)`, or forgetting parentheses vs adding wrong ones (`@app.tool()` vs `@app.tool`).

Common situations: Passing configuration options positionally instead of by keyword; decorating a class or non-function object; double-application where the first application returned something other than a function.

Related errors


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