PrefectHQ/fastmcp · error

Functions with positional-only parameters are not supported

Error message

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.

What it means

MCP tool arguments arrive as a JSON object keyed by parameter name, so every tool parameter must be callable by keyword. Parameters declared positional-only (`def f(x, /)`) can't be supplied that way, so `FunctionTool.from_function` rejects such signatures with ValueError before the tool is registered.

Source

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

    description: str | None
    input_schema: dict[str, Any]
    output_schema: dict[str, Any] | None
    return_type: Any = None

    @classmethod
    def from_function(
        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):

View on GitHub (pinned to 1f02114297)

Solutions

  1. Rewrite the function to drop the `/` marker so parameters accept keyword arguments.
  2. Wrap the function in a keyword-friendly adapter and register the adapter as the tool.
  3. Use functools.partial or a lambda with keyword params to re-expose the function if you can't modify it.
  4. If it's a third-party function, submit/patch upstream to accept keywords, or shim locally.

Example fix

# before
def query(sql, /, limit=10): ...
tool = FunctionTool.from_function(query)
# after
def query(sql, limit=10): ...
tool = FunctionTool.from_function(query)
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try:
    tool = FunctionTool.from_function(fn)
except ValueError as e:
    if "positional-only" in str(e):
        tool = FunctionTool.from_function(lambda *a, **kw: fn(*a), name=fn.__name__)
    else:
        raise

Prevention

When it happens

Trigger: `FunctionTool.from_function(fn)` (or decorating with `@mcp.tool`) on a function with `/`-delimited positional-only params, e.g. `def query(sql, /, limit=10)`; frequently seen on bound methods or wrappers from libraries using positional-only markers (common in stdlib-style code since Python 3.8+).

Common situations: Exposing a third-party function that uses positional-only syntax; copy-pasting C-extension-like signatures; writing `def f(a, /)` intentionally for perf/API stability in your own code and then registering it as a tool.

Related errors


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