FuncMetadata now derives its structured-output validator (and the schema, when
none is given) from `output_model` when it is constructed, keeping it in a
private attribute instead of a public cached property guarded by an assert. The
fields are still read live and the validator is rebuilt if `output_model` is
reassigned, so code that clears or sets `output_schema`/`output_model` on a
registered tool keeps working. `func_metadata()` simply constructs the metadata
inside the existing "not serializable" fallback.
Results are validated with `by_name=True` as well as by alias, so a TypedDict or
model that declares `Field(alias=...)` accepts the Python-side keys a tool
returns while structured content still carries the aliases the schema advertises.
The `typing.TypedDict` rebuild for Python < 3.12 now runs inside that validator
build, so `output_model` stays the declared class and rebuild failures take the
same fallback as pydantic's own; it also carries over `__module__`,
`__qualname__`, `__pydantic_config__` and `ReadOnly`, and an unresolvable key
annotation (`NameError`) degrades like it does on 3.12+.
MCPServer used to mirror a TypedDict return type into a synthesized BaseModel by
hand. That mirror gave optional keys a `None` default and dumped them as `null`,
so a tool omitting a `NotRequired`/`total=False` key produced structuredContent
that violated its own outputSchema and was rejected by the client (#3224); it fed
un-stripped `NotRequired`/`Required` (3.10) and `ReadOnly` (3.10-3.12) qualifiers
to `create_model`, which raised at registration (#3227); and it dropped the
TypedDict's docstring and `Annotated[..., Field(...)]` metadata from the schema.
TypedDict returns are now validated and serialized through a `TypeAdapter` over
the TypedDict itself, so pydantic's own handling of qualifiers, totality,
docstrings and field metadata applies and omitted keys stay absent. Below Python
3.12 pydantic refuses `typing.TypedDict`, so those are rebuilt as an equivalent
`typing_extensions.TypedDict` first. The validator is built once at registration,
inside the existing "not serializable" fallback, and cached on `FuncMetadata` as
`output_adapter`; `output_model` is now the TypedDict class for such tools.
Observable schema change for TypedDict tools: optional keys no longer carry
`"default": null`, and docstring/Field descriptions and constraints now appear.
Fixes#3224Fixes#3227