The startup page now leads its prewarming section with warm(version), the
form a host actually wants: it names the version the transport negotiates
and takes the first connection from ~200 ms of one-time builds to within a
millisecond of steady state; the no-argument and all_versions forms are
described as the narrower and wider variants. The magnitude table is
labelled as single-machine measurements.
Two consequences of holding one process-wide lock across a model's first
build are spelled out where they can be found - the lock's docstring and an
FAQ entry: user code inside pydantic schema/subclass hooks runs under the
lock (so ordinary lock-ordering rules apply), a thread mid-build during
fork() leaves the child's lock held, warm() at startup sidesteps both, and
the SDK's lock goes once a pydantic release with a thread-safe
model_rebuild is the dependency floor. The FAQ also notes that
get_type_hints() imports what the hints name (evaluating mcp.Client's hints
imports mcp.server), and the migration guide's list of incidental changes
is no longer titled "two" now that it holds four.
docs/advanced/import-cost.md now states the size of every deferred bill (the
per-version wire package, the first HTTP app, the first span, cryptography,
the OAuth provider models, and the ~130 ms a fresh process pays once on its
first connection), and grows an FAQ: an installed pydantic plugin such as
logfire still loads at `import mcp.types` (PYDANTIC_DISABLE_PLUGINS is the
lever), __pydantic_complete__ reads False until first use, function-local
model subclasses keep the generic signature until first use, what to hand a
static bundler, and the get_type_hints waiver for the app builders.
AGENTS.md's import-cost bullets are corrected to match the code: only
mcp/__init__ resolves exports lazily while the other package inits resolve
submodules; cryptography and the OAuth provider models load with their
first user rather than at import; the wire packages also load for the first
rendered elicitation schema. The migration guide notes the removed
McpHttpClientFactory re-export from mcp.client.streamable_http.
Released pydantic (through 2.13) does not lock model_rebuild(), so the
first use of a defer_build model from several threads at once - sync tools
running on worker threads, or one client session per thread at process
start - could raise (AttributeError: __pydantic_core_schema__ and friends)
or install a stale validator instead of just building twice. A narrow
version of the same hazard already existed for the handful of models
that were incomplete at import; deferring every model made it reachable
from every entry point.
One process-wide RLock now serializes that one-time build. The
@deferred_model decorator becomes the single mechanism for a deferred
model root: it installs the lazy __signature__, the subclass hook, a
locked model_rebuild (public pydantic API; the extra wrapper frame is
accounted for via _parent_namespace_depth + 1) and a model_json_schema
that completes the class under the same lock before pydantic reads it
(pydantic re-reads the mock schema around a concurrent build).
MCPModel, WireModel and WireRootModel are decorated rather than
hand-writing the hook, and the remaining private deferred roots get the
decorator too, so no deferred model in the SDK builds outside the lock.
The decorator refuses a class that lacks defer_build or that defines its
own __pydantic_init_subclass__ (which it would otherwise replace).
Steady state is untouched: an already-built model never takes the lock.
A subprocess ratchet test races 8 threads over the monolith models, a
wire package, the JSON-RPC envelopes, the SDK's own models, the
module-level adapters and first schema/signature access, and asserts
zero exceptions (it fails without the lock).
- AGENTS.md: state the import-cost exception to the imports-at-top rule,
listing which stacks stay off which import paths and pointing at the
ratchet test that enforces it.
- New docs page "Imports & startup time" (Advanced): what loads when, the
one-time first-use costs, and a tested prewarm recipe for hosts that want
the deferred work paid at startup.
- Migration guide: the lazy import graph, the incidental namespace bindings
that moved to their defining modules, and defer_build inheritance for user
subclasses of SDK models.
- What's new: one paragraph on imports paying only for what they use.