Skip to content

fix(mcpserver): keep Annotated field constraints on ordinary-class structured output (#3712) - #3816

Closed
PratikDhanave wants to merge 1 commit into
modelcontextprotocol:mainfrom
PratikDhanave:fix/ordinary-class-annotated-constraints-3712
Closed

PratikDhanave wants to merge 1 commit into
modelcontextprotocol:mainfrom
PratikDhanave:fix/ordinary-class-annotated-constraints-3712

Conversation

@PratikDhanave

Copy link
Copy Markdown

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 (only type: integer remains), and
  • convert_result(...) accepts an out-of-bound value such as 0 and returns structured_content={"value": 0} instead of raising ValidationError.

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.

…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>
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

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:

  • We're a small team with very little capacity to review community PRs right now.
  • Many recent PRs are AI-generated with little human review, and reviewing one carefully still costs a maintainer as much time as it ever did. A well-described issue is usually more useful to us than the code.

Maintainers: reopen, remove missing-issue-link, or add bypass-issue-check to override.

@github-actions github-actions Bot closed this Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ordinary-class structured outputs discard Annotated field constraints and accept invalid values

1 participant