PrefectHQ/fastmcp · error

Functions with **kwargs are not supported as tools

Error message

Functions with **kwargs are not supported as tools

What it means

Tools reject `**kwargs` because every tool argument must have a named schema entry in the input schema; VAR_KEYWORD parameters have no fixed names and can't be validated or documented, so `FunctionTool.from_function` raises ValueError when it encounters one.

Source

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

        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
        # declared. The class docstring's Args section, if any, typically
        # describes __init__, so falling back to it would risk injecting

View on GitHub (pinned to 1f02114297)

Solutions

  1. Replace `**kwargs` with explicit named, typed parameters.
  2. If arbitrary options are genuinely needed, accept a single `options: dict[str, Any]` parameter and validate its keys manually.
  3. Split the generic function into concrete tool functions with fixed signatures.

Example fix

# before
def configure(host, **options): ...
# after
def configure(host, timeout: int = 30, retries: int = 3): ...
Defensive patterns

Strategy: validation

Validate before calling

import inspect
def tool_ready(fn) -> bool:
    return not any(p.kind == 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 "**kwargs" in str(e):
        raise TypeError(f"{fn.__name__} must not use **kwargs; declare explicit options")
    raise

Prevention

When it happens

Trigger: `FunctionTool.from_function(fn)` or `@mcp.tool` on functions like `def configure(host, **options)`; generic forwarding wrappers; decorator-generated functions that capture kwargs.

Common situations: Exposing config-style or pass-through wrapper functions; maintaining one generic function for many tools instead of explicit per-tool signatures; migrating an old JSON-RPC handler that accepted arbitrary payloads.

Related errors


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