{"record":{"id":"8bd01767faf3d83e","repo":"PrefectHQ/fastmcp","slug":"functions-with-positional-only-parameters-are-not","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/tools/function_parsing.py","lineNumber":261,"sourceCode":"    description: str | None\n    input_schema: dict[str, Any]\n    output_schema: dict[str, Any] | None\n    return_type: Any = None\n\n    @classmethod\n    def from_function(\n        cls,\n        fn: Callable[..., Any],\n        validate: bool = True,\n        wrap_non_object_output_schema: bool = True,\n    ) -> ParsedFunction:\n        if validate:\n            sig = inspect.signature(fn)\n            # Reject signatures that cannot be represented by MCP's\n            # object-shaped tool arguments.\n            for param in sig.parameters.values():\n                if param.kind == inspect.Parameter.POSITIONAL_ONLY:\n                    raise ValueError(\n                        \"Functions with positional-only parameters are not \"\n                        \"supported as tools because MCP passes tool arguments by \"\n                        \"name. Replace them with standard parameters that can be \"\n                        \"passed as keywords.\"\n                    )\n                if param.kind == inspect.Parameter.VAR_POSITIONAL:\n                    raise ValueError(\"Functions with *args are not supported as tools\")\n                if param.kind == inspect.Parameter.VAR_KEYWORD:\n                    raise ValueError(\n                        \"Functions with **kwargs are not supported as tools\"\n                    )\n\n        # collect name and description before we potentially modify the function\n        fn_name = getattr(fn, \"__name__\", None) or fn.__class__.__name__\n        outer_docstring = parse_docstring(fn)\n\n        # if the fn is a callable class, we need to get the __call__ method from here out\n        if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/tools/function_parsing.py#L243-L279","documentation":"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.","triggerScenarios":"`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+).","commonSituations":"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.","solutions":["Rewrite the function to drop the `/` marker so parameters accept keyword arguments.","Wrap the function in a keyword-friendly adapter and register the adapter as the tool.","Use functools.partial or a lambda with keyword params to re-expose the function if you can't modify it.","If it's a third-party function, submit/patch upstream to accept keywords, or shim locally."],"exampleFix":"# before\ndef query(sql, /, limit=10): ...\ntool = FunctionTool.from_function(query)\n# after\ndef query(sql, limit=10): ...\ntool = FunctionTool.from_function(query)","handlingStrategy":"validation","validationCode":"import inspect\ndef tool_ready(fn) -> bool:\n    return not any(p.kind == inspect.Parameter.POSITIONAL_ONLY\n                   for p in inspect.signature(fn).parameters.values())","typeGuard":null,"tryCatchPattern":"try:\n    tool = FunctionTool.from_function(fn)\nexcept ValueError as e:\n    if \"positional-only\" in str(e):\n        tool = FunctionTool.from_function(lambda *a, **kw: fn(*a), name=fn.__name__)\n    else:\n        raise","preventionTips":["Avoid `/` in functions intended as tools.","Validate signatures at import time with an inspection helper.","Write adapters for third-party functions with positional-only params."],"tags":["python","valueerror","positional-only","signature-validation","tool"],"backgroundTag":"unsupported-signature","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}