{"record":{"id":"5647a63e2e109b86","repo":"PrefectHQ/fastmcp","slug":"functions-with-kwargs-are-not-supported-as-tools","errorCode":null,"errorMessage":"Functions with **kwargs are not supported as tools","messagePattern":"Functions with \\*\\*kwargs are not supported as tools","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/tools/function_parsing.py","lineNumber":270,"sourceCode":"        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):\n            fn = fn.__call__\n        # if the fn is a staticmethod, we need to work with the underlying function\n        if isinstance(fn, staticmethod):\n            fn = fn.__func__\n\n        # For callable classes, parameter descriptions must come from\n        # __call__'s docstring — where the exposed parameters are actually\n        # declared. The class docstring's Args section, if any, typically\n        # describes __init__, so falling back to it would risk injecting","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/tools/function_parsing.py#L252-L288","documentation":"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.","triggerScenarios":"`FunctionTool.from_function(fn)` or `@mcp.tool` on functions like `def configure(host, **options)`; generic forwarding wrappers; decorator-generated functions that capture kwargs.","commonSituations":"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.","solutions":["Replace `**kwargs` with explicit named, typed parameters.","If arbitrary options are genuinely needed, accept a single `options: dict[str, Any]` parameter and validate its keys manually.","Split the generic function into concrete tool functions with fixed signatures."],"exampleFix":"# before\ndef configure(host, **options): ...\n# after\ndef configure(host, timeout: int = 30, retries: int = 3): ...","handlingStrategy":"validation","validationCode":"import inspect\ndef tool_ready(fn) -> bool:\n    return not any(p.kind == inspect.Parameter.VAR_KEYWORD\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 \"**kwargs\" in str(e):\n        raise TypeError(f\"{fn.__name__} must not use **kwargs; declare explicit options\")\n    raise","preventionTips":["Avoid **kwargs in tool-facing functions; use explicit options or an options dict param.","Inspect signatures before registering in dynamic tool-loading code.","Keep decorators from silently adding kwargs to tool functions."],"tags":["python","valueerror","var-kwargs","signature-validation","tool"],"backgroundTag":"unsupported-signature","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}