Repository navigation
fix(server): wrap pydantic ValidationError from tool arg validation as fastmcp ValidationError - #4172
SarthakB11 wants to merge 1 commit into
Conversation
|
Edited to reflect the latest CI run — the original ruff issue is fixed; new failures are test expectations that weren't migrated alongside the behavior change. tl;dr: Three tests still expect Root Cause: The PR's whole point is to wrap pydantic's Fix: In each of the three locations, replace
All three failing jobs ( Log excerptsFrom Why this happenedThe PR description correctly identifies |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c9b7295bf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| e.errors(include_url=False), | ||
| ) | ||
| raise | ||
| raise ValidationError(str(e)) from e |
There was a problem hiding this comment.
Don't classify tool-body validation failures as bad input
When a tool itself raises a pydantic.ValidationError (for example by constructing a Pydantic model from bad upstream data), that exception also exits tool._run() through the same PydanticValidationError handler because FunctionTool.run() performs argument validation and invokes the function inside one TypeAdapter.validate_python(...) call. Wrapping every such error as fastmcp.exceptions.ValidationError means middleware or Sentry filters intended to ignore caller argument mistakes will now also suppress real tool-body bugs. Please only wrap validation errors known to come from argument binding, or leave body-raised Pydantic errors on the normal tool-error path.
Useful? React with 👍 / 👎.
|
Please follow the contributing guide and do not raise PRs without discussion on the issue first. |
Description
When invalid arguments are passed to a tool,
pydantic-core's call validator raises a rawpydantic.ValidationErrorwhichFastMCP.call_toolre-raises unchanged. The error then propagates through the middleware chain as a non-fastmcp type, so consumers filtering on the framework's own error taxonomy (e.g. a Sentryignore_errors=[fastmcp.exceptions.ValidationError]filter) can't match it. Thecall_tooldocstring already advertisesValidationError: If arguments fail validation, so the implementation had drifted from the contract.This wraps the caught
PydanticValidationErrorinfastmcp.exceptions.ValidationErrorwithfrom e, preserving the structured pydantic error as__cause__so consumers that still want.errors()can walk the chain. The warning log just above the re-raise is unchanged, and the client-side wire contract (aToolErrorreaches the client) is preserved.The 10 existing
pytest.raises(ValidationError)blocks intests/server/providers/local_provider_tools/test_parameters.pythat targetmcp.call_tool(...)are migrated frompydantic.ValidationErrortofastmcp.exceptions.ValidationErrorin the same PR, since they assert against the new error type. NoTypeAdapter-direct tests exist in that file, so nopydantic.ValidationErrorraises are left intact.The reporter notes in the issue thread that tool bodies can also raise
pydantic.ValidationErrorfrom their own bugs, and those should arguably stay distinguishable from bad-call errors. Distinguishing the two requires splitting argument validation from invocation insideFunctionTool.run(todaytype_adapter.validate_python(arguments)does both), which is a deeper refactor than this bug warrants.Closes #4128.
Contribution type
Checklist
uv run prek run --all-filesand all checks pass