You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Dataclass structured outputs mishandle slots and InitVar fields #3711
Two standard dataclass configurations fail when used as structured tool outputs. With @dataclass(slots=True), schema derivation silently falls back to unstructured output: func_metadata(produce).output_schema is None. With an InitVar[str] constructor parameter, metadata construction instead raises PydanticForbiddenQualifier because the constructor-only annotation is treated as a model field.
I expected the schema and structured result to contain the dataclass's instance fields. A constructor-only InitVar should not become an output property. The ordinary dataclass control passes, and Pydantic's own TypeAdapter serializes both variants to {"value": "ok"} in the same environments. These cases matter when an existing dataclass is reused as a tool return value.
Two deterministic, intentionally failing tests are on this test-only branch; no production changes. Run either case independently, for example:
uv run --frozen pytest tests/server/mcpserver/test_func_metadata.py::test_slotted_dataclass_structured_output_has_instance_fields -q
Python & MCP Python SDK
Python 3.14.7; Pydantic 2.12.5; MCP 2.3.0 (tag v2.3.0, 2118f14f8a19bc158d8a1cf90af58d85d187f849). Also reproduced using main source at 754254c08fe1c3eda18b69da3d336106fe8d72d6.
AI-assisted investigation and test reproduction, with a human in the loop.
Reproduced both cases on main and confirmed the failure modes, then verified a fix locally against your repro branch (inanna-malick/python-sdk@repro/dataclass-output-fields):
Root cause — both live in _create_model_from_class (src/mcp/server/mcpserver/utilities/func_metadata.py):
Slotted dataclasses: getattr(cls, field_name, _no_default) returns the slot member descriptor, which the code mistakes for a default value. create_model then fails and schema derivation silently falls back to unstructured output (output_schema=None).
InitVar: get_type_hints keeps token: InitVar[str] in the hints and it is fed straight into create_model, which pydantic rejects with PydanticForbiddenQualifier (raised outside the FuncMetadata try block, so it propagates).
Fix (verified locally): in _create_model_from_class,
skip hints where isinstance(field_type, dataclasses.InitVar) — constructor-only, not instance state, so never an output property;
read dataclass defaults from dataclasses.fields() instead of attribute access (slot descriptors are not defaults); non-dataclass classes keep the old getattr path, with member descriptors additionally treated as "no default".
Results: both repro tests now pass (test_slotted_dataclass_structured_output_has_instance_fields, test_dataclass_initvar_is_excluded_from_structured_output — confirmed failing before the fix), the existing test_structured_output_dataclass control still passes, and the full tests/server/mcpserver/test_func_metadata.py module is 60/60 green; broader tests/server/mcpserver/ is 643 passed with 1 unrelated environmental failure (test_file_resources.py::test_permission_error, fails when run as root). ruff check and ruff format --check are clean on the touched file.
I have the patch ready as a branch. Happy to open a PR if the issue gets assigned (per the repo gate, unassigned PRs are auto-closed) — just say the word.
Disclosure: this investigation and fix verification were AI-assisted.
Thanks @nosmile99 for reproducing both cases and checking the broader suite. I'm happy for you to take the fix forward, subject to maintainer assignment; I'm not reserving it for myself.
Could you link your patch branch here? Reading dataclass fields instead of treating slot descriptors as defaults sounds like the right direction. Alongside the two original repros, inherited fields and default_factory fields would be useful checks.
The fix is in _create_model_from_class (src/mcp/server/mcpserver/utilities/func_metadata.py). For dataclasses, defaults are now read from dataclasses.fields() instead of attribute access — that fixes both reported cases at once: slot descriptors are no longer mistaken for defaults, and InitVar/ClassVar annotations are excluded since they never appear as dataclass fields. Non-dataclass slotted classes get the member-descriptor guard too, so they no longer silently fall back to unstructured output.
Verification:
Both of your repro tests pass (I included them in the branch, attributed to your repro branch), confirmed failing on main before the fix
Your requested extras: inherited fields, plain defaults, and default_factory defaults all land correctly in the schema (inherited fields required, defaults optional)
Full tests/server/mcpserver/test_func_metadata.py module: 63/63 green; broader tests/server/mcpserver/: 646 passed, 1 unrelated environmental failure (test_file_resources.py::test_permission_error, fails as root on a clean tree too)
ruff check and ruff format --check clean on both touched files
Happy to open the PR whenever a maintainer assigns the issue — the repo gate auto-closes unassigned outside PRs, so I will hold it until then.
Activity
nosmile99 commentedon Oct 16, 2026
Reproduced both cases on main and confirmed the failure modes, then verified a fix locally against your repro branch (
inanna-malick/python-sdk@repro/dataclass-output-fields):Root cause — both live in
_create_model_from_class(src/mcp/server/mcpserver/utilities/func_metadata.py):getattr(cls, field_name, _no_default)returns the slot member descriptor, which the code mistakes for a default value.create_modelthen fails and schema derivation silently falls back to unstructured output (output_schema=None).InitVar:get_type_hintskeepstoken: InitVar[str]in the hints and it is fed straight intocreate_model, which pydantic rejects withPydanticForbiddenQualifier(raised outside theFuncMetadatatry block, so it propagates).Fix (verified locally): in
_create_model_from_class,isinstance(field_type, dataclasses.InitVar)— constructor-only, not instance state, so never an output property;dataclasses.fields()instead of attribute access (slot descriptors are not defaults); non-dataclass classes keep the oldgetattrpath, with member descriptors additionally treated as "no default".Results: both repro tests now pass (
test_slotted_dataclass_structured_output_has_instance_fields,test_dataclass_initvar_is_excluded_from_structured_output— confirmed failing before the fix), the existingtest_structured_output_dataclasscontrol still passes, and the fulltests/server/mcpserver/test_func_metadata.pymodule is 60/60 green; broadertests/server/mcpserver/is 643 passed with 1 unrelated environmental failure (test_file_resources.py::test_permission_error, fails when run as root).ruff checkandruff format --checkare clean on the touched file.I have the patch ready as a branch. Happy to open a PR if the issue gets assigned (per the repo gate, unassigned PRs are auto-closed) — just say the word.
Disclosure: this investigation and fix verification were AI-assisted.
inanna-malick commentedon Oct 16, 2026
Thanks @nosmile99 for reproducing both cases and checking the broader suite. I'm happy for you to take the fix forward, subject to maintainer assignment; I'm not reserving it for myself.
Could you link your patch branch here? Reading dataclass fields instead of treating slot descriptors as defaults sounds like the right direction. Alongside the two original repros, inherited fields and default_factory fields would be useful checks.
AI-assisted follow-up.
nosmile99 commentedon Oct 16, 2026
Patch branch is up: https://github.com/nosmile99/python-sdk/tree/fix/dataclass-structured-output
The fix is in
_create_model_from_class(src/mcp/server/mcpserver/utilities/func_metadata.py). For dataclasses, defaults are now read fromdataclasses.fields()instead of attribute access — that fixes both reported cases at once: slot descriptors are no longer mistaken for defaults, andInitVar/ClassVarannotations are excluded since they never appear as dataclass fields. Non-dataclass slotted classes get the member-descriptor guard too, so they no longer silently fall back to unstructured output.Verification:
mainbefore the fixdefault_factorydefaults all land correctly in the schema (inherited fields required, defaults optional)tests/server/mcpserver/test_func_metadata.pymodule: 63/63 green; broadertests/server/mcpserver/: 646 passed, 1 unrelated environmental failure (test_file_resources.py::test_permission_error, fails as root on a clean tree too)ruff checkandruff format --checkclean on both touched filesHappy to open the PR whenever a maintainer assigns the issue — the repo gate auto-closes unassigned outside PRs, so I will hold it until then.
Disclosure: AI-assisted, as before.