PrefectHQ/fastmcp · error

Functions with *args are not supported as tools

Error message

Functions with *args are not supported as tools

What it means

Tools reject `*args` because MCP arguments are a named JSON object — there is no way for a client to supply extra positional arguments, and `*args` can't be represented in the tool's input schema. `FunctionTool.from_function` raises ValueError when it sees a VAR_POSITIONAL parameter.

Source

Thrown at fastmcp_slim/fastmcp/tools/function_parsing.py:268

        cls,
        fn: Callable[..., Any],
        validate: bool = True,
        wrap_non_object_output_schema: bool = True,
    ) -> ParsedFunction:
        if validate:
            sig = inspect.signature(fn)
            # Reject signatures that cannot be represented by MCP's
            # object-shaped tool arguments.
            for param in sig.parameters.values():
                if param.kind == inspect.Parameter.POSITIONAL_ONLY:
                    raise ValueError(
                        "Functions with positional-only parameters are not "
                        "supported as tools because MCP passes tool arguments by "
                        "name. Replace them with standard parameters that can be "
                        "passed as keywords."
                    )
                if param.kind == inspect.Parameter.VAR_POSITIONAL:
                    raise ValueError("Functions with *args are not supported as tools")
                if param.kind == inspect.Parameter.VAR_KEYWORD:
                    raise ValueError(
                        "Functions with **kwargs are not supported as tools"
                    )

        # collect name and description before we potentially modify the function
        fn_name = getattr(fn, "__name__", None) or fn.__class__.__name__
        outer_docstring = parse_docstring(fn)

        # if the fn is a callable class, we need to get the __call__ method from here out
        if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):
            fn = fn.__call__
        # if the fn is a staticmethod, we need to work with the underlying function
        if isinstance(fn, staticmethod):
            fn = fn.__func__

        # For callable classes, parameter descriptions must come from
        # __call__'s docstring — where the exposed parameters are actually

View on GitHub (pinned to 1f02114297)

Solutions

  1. Replace `*args` with explicit named parameters (e.g. `filters: list[str] | None = None`).
  2. Accept a list/dict parameter and iterate inside the function.
  3. Write a keyword-only wrapper function with explicit params and register that.

Example fix

# before
def search(term, *filters): ...
# after
def search(term, filters: list[str] | None = None): ...
Defensive patterns

Strategy: validation

Validate before calling

import inspect
def tool_ready(fn) -> bool:
    return not any(p.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
                   for p in inspect.signature(fn).parameters.values())

Try / catch

try:
    tool = FunctionTool.from_function(fn)
except ValueError as e:
    if "*args" in str(e):
        raise TypeError(f"{fn.__name__} must declare explicit parameters to be a tool")
    raise

Prevention

When it happens

Trigger: `FunctionTool.from_function(fn)` or `@mcp.tool` on functions like `def search(term, *filters)`; wrappers that forward arbitrary args; decorating a generic dispatcher function as a tool.

Common situations: Exposing variadic helper/utility functions; thin wrappers around logging or CLI-style APIs; code written before joining the project that assumed variadics would work over MCP.

Related errors


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