Repository navigation
fix(mcpserver): keep Annotated field constraints on ordinary-class structured output (#3712) - #3816
Conversation
…ructured output (modelcontextprotocol#3712) _create_output_model resolved an ordinary class's field hints with get_type_hints(type_annotation), which drops Annotated metadata. A field declared Annotated[int, Field(gt=0)] collapsed to a bare int: the generated output schema lost exclusiveMinimum and convert_result accepted out-of-bound values such as 0 without raising ValidationError. Pass include_extras=True so the Annotated metadata is retained and carried into the synthesized Pydantic model, matching the TypedDict path which already resolves hints with extras. Fixes modelcontextprotocol#3712 Signed-off-by: Pratik Dhanave <i.pratikdhanave@gmail.com>
|
This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3712. If a maintainer assigns you to #3712, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take. You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way. CONTRIBUTING.md has the full reasoning, but in short:
Maintainers: reopen, remove |
Fixes #3712
Motivation and Context
_create_output_model(infunc_metadata.py) resolves an ordinary class'sfield hints with
get_type_hints(type_annotation), which stripsAnnotatedmetadata. As a result a field declared
Annotated[int, Field(gt=0)]collapsesto a bare
intwhen the return annotation is turned into a structured-outputmodel:
exclusiveMinimum: 0(onlytype: integerremains), andconvert_result(...)accepts an out-of-bound value such as0and returnsstructured_content={"value": 0}instead of raisingValidationError.Pydantic itself rejects
0for the exact same annotation, and the equivalentBaseModel/TypedDictpaths preserve the constraint, so the behaviour isinconsistent and a declared bound is silently discarded.
The fix passes
include_extras=Trueso theAnnotatedmetadata survives intothe synthesized Pydantic model — the same way the TypedDict path already
resolves its hints (
get_type_hints(td_type, include_extras=True)).How Has This Been Tested?
Added
test_structured_output_ordinary_class_keeps_annotated_field_constraints,which asserts the constraint appears in the schema (
exclusiveMinimum: 0),that a valid value round-trips, and that an out-of-bound value raises
ValidationError. The test fails onmainand passes with this change.ruff check,ruff format --check, andpyrightare clean on the touched files.Breaking Changes
None. Output models for ordinary classes now enforce the constraints their
authors already declared; code that previously relied on invalid values
silently passing would have been emitting an out-of-spec schema.
Types of changes
Checklist
help wanted, or I'm a maintainer)Additional context
I am not assigned to #3712 and it is not labeled
help wanted, so I have leftthat checkbox unchecked; I understand the linked-issue bot may close this PR
until a maintainer assigns the issue.
AI assistance: the fix and regression test were prepared with AI assistance
(Claude Code), with a human in the loop who reviewed the diff and the
reproduction.