Skip to content

fix: resolve list[dict] return type producing Root() instead of dicts#3880

Merged
jlowin merged 1 commit intoPrefectHQ:mainfrom
KeWang0622:fix/list-dict-return-type-3867
Apr 12, 2026
Merged

fix: resolve list[dict] return type producing Root() instead of dicts#3880
jlowin merged 1 commit intoPrefectHQ:mainfrom
KeWang0622:fix/list-dict-return-type-3867

Conversation

@KeWang0622
Copy link
Copy Markdown
Contributor

Description

Fixes #3867

When a tool returns list[dict], the client deserializes each dict as a Root() dataclass with no fields instead of preserving the original dict data:

@server.tool
def get_temperatures() -> list[dict]:
    return [{"city": "NYC", "temp": 72}, {"city": "LA", "temp": 85}]

# Before fix:  result.data == [Root(), Root()]
# After fix:   result.data == [{"city": "NYC", "temp": 72}, {"city": "LA", "temp": 85}]

Root cause

_get_from_type_handler's "object" branch always fell through to _create_dataclass for schemas without properties (like {"type": "object", "additionalProperties": true} — the JSON Schema for bare dict). This created an empty dataclass named Root.

The top-level json_schema_to_type already handled this correctly — returning dict[str, Any] — but that logic was not reachable from _schema_to_type, which is used for nested schemas (e.g., array items).

Fix

Extract _object_schema_to_type to unify the four object-schema cases so both top-level and nested paths produce the correct type:

  1. No properties + additionalPropertiesdict[str, T]
  2. No properties + no additionalPropertiesdict[str, Any]
  3. properties + additionalProperties is True — Pydantic BaseModel
  4. properties only — dataclass

Contribution type

  • Bug fix (simple, well-scoped fix for a clearly broken behavior)
  • Documentation improvement
  • Enhancement

Checklist

When a tool returns `list[dict]`, the client deserializes each dict as a
`Root()` dataclass with no fields instead of preserving the original dict
data.

The root cause is in `_get_from_type_handler`: its `"object"` branch
always fell through to `_create_dataclass` for schemas without
`properties`, creating an empty dataclass named `Root`. The top-level
`json_schema_to_type` already handled this case correctly (returning
`dict[str, Any]`), but that logic was not shared with `_schema_to_type`
which is used when converting nested schemas (e.g., array items).

Extract `_object_schema_to_type` to unify the four object-schema cases
(dict, typed dict, BaseModel with extra, dataclass) so both top-level
and nested paths produce the correct type.

Fixes PrefectHQ#3867
@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. labels Apr 12, 2026
Copy link
Copy Markdown
Member

@jlowin jlowin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clean diagnosis and tests on this one — the root cause is exactly right, and the list[dict[str, X]] parametrization plus the end-to-end client test give me a lot of confidence in the fix.

One small note for anyone reading this later: the PR description says the helper unifies top-level and nested paths, but in practice the new _object_schema_to_type is only wired into _get_from_type_handler. The top-level json_schema_to_type (around lines 180-198) and the anyOf dict-in-union branch (around lines 362-374) still carry their own copies of the four-case logic. That's fine — those sites aren't broken, and this is the narrowest fix for the reported bug — but it means the divergence risk that caused #3867 in the first place still exists at two other sites. I'll open a follow-up to route all three through the new helper.

Merging. Thanks again!

@jlowin jlowin merged commit f235992 into PrefectHQ:main Apr 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

list[dict] return type annotation becomes Root() in testing

3 participants