PrefectHQ/fastmcp · warning · ValidationError

{str(e)}

Error message

{str(e)}

What it means

Argument-validation failures (pydantic ValidationErrors raised while validating call arguments against the tool's parameter schema) are re-raised as fastmcp ValidationError with log_level WARNING so middleware and error taxonomy treat them as client errors. The raised message is the stringified pydantic error listing which arguments failed validation.

Solutions

  1. Fix the caller's arguments to match the tool's parameter schema (types, required fields)
  2. Re-fetch the tool schema (list_tools) after any signature change and regenerate the call
  3. Validate arguments client-side with the tool's input schema before invoking

Example fix

// before
await client.call_tool("add", {"a": "1", "b": 2})   # a: str not int
// after
await client.call_tool("add", {"a": 1, "b": 2})
Defensive patterns

Strategy: validation

Validate before calling

# validate args against the tool's input schema before calling
def args_match_schema(args: dict, input_schema: dict) -> bool:
    import jsonschema
    try:
        jsonschema.validate(args, input_schema)
        return True
    except jsonschema.ValidationError:
        return False

Try / catch

from fastmcp.exceptions import ValidationError
try:
    result = await client.call_tool('my_tool', args)
except ValidationError as e:
    logger.warning('bad tool arguments: %s', e)
    args = fix_args_from_message(str(e), args)
    result = await client.call_tool('my_tool', args)

Prevention

When it happens

Trigger: Calling a tool with wrong argument types (e.g. string where int expected), missing required parameters, or extra unknown parameters — anything failing the tool's input schema validation in _run_body.

Common situations: LLM clients generating malformed arguments; API changes to a tool's signature while callers cache old schemas; passing JSON numbers as strings; forgetting to coerce nested models.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at fastmcp_slim/fastmcp/tools/function_tool.py:439

                        f"Tool '{self.name}' timed out after {self.timeout}s. "
                        f"Consider using task=True for long-running operations. "
                        f"See https://gofastmcp.com/servers/tasks"
                    )
                    raise MCPError(
                        code=-32000,
                        message=f"Tool '{self.name}' execution timed out after {self.timeout}s",
                    ) from None
            else:
                result = await self._execute(
                    type_adapter, exec_is_async, arguments, strict=strict
                )
        except PydanticValidationError as e:
            # Body errors are re-raised as _ToolBodyError, so a bare pydantic
            # ValidationError here is an argument-validation failure (a bad call).
            # Convert it to fastmcp's ValidationError so the middleware chain and
            # downstream error taxonomy (e.g. Sentry filters) can treat it as a
            # client error rather than a server bug.
            raise ValidationError(str(e), log_level=logging.WARNING) from e
        except _ToolBodyError as e:
            # The tool's own body raised a pydantic ValidationError. Surface the
            # original so it is treated as a server-side error, hiding the
            # internal sentinel while preserving the error's own chained cause.
            original = e.__cause__
            assert original is not None
            raise original from original.__cause__

        return result

    async def _execute(
        self,
        type_adapter: TypeAdapter[Any],
        exec_is_async: bool,
        arguments: dict[str, Any],
        *,
        strict: bool = False,
    ) -> Any:

View on GitHub (pinned to 1f02114297)