fix(mcpserver): keep Annotated field constraints on ordinary-class structured output (#3712)
Fixes #3712
Motivation and Context
_create_output_model (in func_metadata.py) resolves an ordinary class's
field hints with get_type_hints(type_annotation), which strips Annotated
metadata. As a result a field declared Annotated[int, Field(gt=0)] collapses
to a bare int when the return annotation is turned into a structured-output
model:
- the generated output schema loses
exclusiveMinimum: 0(onlytype: integerremains), and convert_result(...)accepts an out-of-bound value such as0and returnsstructured_content={"value": 0}instead of raisingValidationError.
Pydantic itself rejects 0 for the exact same annotation, and the equivalent
BaseModel/TypedDict paths preserve the constraint, so the behaviour is
inconsistent and a declared bound is silently discarded.
The fix passes include_extras=True so the Annotated metadata survives into
the 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 on main and passes with this change.
uv run pytest tests/server/mcpserver/test_func_metadata.py -q
# 66 passed
uv run pytest tests/server/mcpserver -q
# 651 passed
ruff check, ruff format --check, and pyright are 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
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to change)
- Documentation update
Checklist
- I am assigned to the linked issue (or it is labeled
help wanted, or I'm a maintainer) - I have disclosed any AI assistance and can explain the change in my own words
- I have read the MCP Documentation
- My code follows the repository's style guidelines
- New and existing tests pass locally
- I have added appropriate error handling
- I have added or updated documentation as needed
Additional context
I am not assigned to #3712 and it is not labeled help wanted, so I have left
that 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.