Commit Graph

22 Commits

Author SHA1 Message Date
Max Isbey 2716d09e7a Pin the decorator's refusal messages and describe two wire-base tests
The two `@deferred_model` refusals asserted only the exception type;
they now snapshot the message so a wording change is a deliberate diff
rather than a silent one. The first two wire-base tests gain the
provenance docstrings the rest of the file carries.
2026-07-29 22:38:08 +00:00
Max Isbey 6da7ba4f09 Spell warm()'s every-version flag as all_versions and return a frozen dataclass
`warm(everything=True)` warmed every protocol version's routing surface -
not "everything the SDK could ever build": the mcp package's own model
roots (mcpserver settings, tool/prompt argument models) belong to the mcp
layer that mcp-types cannot see, and they build during server construction
and tool registration anyway, which is startup work rather than
first-request work. Naming the flag `all_versions` says what it does; the
recommended host recipe is unchanged (`warm(version)` for the version the
transport negotiates).

WarmReport becomes a frozen dataclass instead of a NamedTuple, so a later
field addition does not disturb code that reads the existing fields, and
`mcp.warm` stays the very same function as `mcp_types.methods.warm` (pinned
by a test).
2026-07-29 22:34:45 +00:00
Max Isbey b592f09f4b Generate a deferred model's JSON schema under the rebuild lock too
Completing a deferred model under the process-wide rebuild lock and then
letting pydantic generate its JSON schema outside it left one interleaving:
schema generation for a model reads the core schema of every model it
references, and for the mutually recursive wire JSON models (JSONObject /
JSONArray / JSONValue) a sibling could still be mid-build on another
thread, surfacing as a rare KeyError inside pydantic's schema generation
on some pydantic versions when many threads generated their first schema
of that family at once. No SDK-internal path did this (registration is
single-threaded), but user code may.

The generation now runs under the same lock as the build. Schema
generation is a cold path, so it just serializes with the deferred first
builds; steady state is unchanged. The concurrency ratchet also covers a
concurrent first schema over the recursive JSON family and the 2026 wire
package.
2026-07-29 22:17:24 +00:00
Max Isbey fc4f4e6d1d Cover the decorator's refusal paths and the runtime-only lazy-init branches
The two TypeError guards in deferred_model and plain warm() were
untested, and coverage.py counts the never-taken false arc of the
`if not TYPE_CHECKING:` guards around the lazy module attributes; that
arc belongs to the type checker, so declare it partial by design next
to the existing exclusion rules.
2026-07-29 22:09:54 +00:00
Max Isbey 33471c77fd Add mcp.warm(): opt-in prewarming for the deferred validators
The wire schemas load on the first message per protocol version and the
pydantic validators build on first use, which keeps imports fast but puts a
one-time bill on a process's first messages. A long-running host that
would rather pay that at startup than on its first request had only a DIY
recipe of model_rebuild() loops.

mcp_types.methods.warm(version=None, *, everything=False) is the supported
version: with no argument it builds the version-independent set (the
exported mcp_types models, the JSON-RPC envelopes and the routing union
adapters, ~50 ms); with a version it also imports that version's wire
package and builds the routing surface a connection at that version uses
(so its first messages then build nothing); everything=True covers every
known version. Model classes are always completed before the adapters
that reference them, so no schema is generated twice, and repeat calls
are no-ops. It returns a small WarmReport for logging and is re-exported
as mcp.warm. Nothing in the SDK calls it. The import-cost docs recipe now
uses it.
2026-07-29 22:08:37 +00:00
Max Isbey 8c9f2a2ed8 Serialize the deferred first build of the SDK's pydantic models
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).
2026-07-29 22:07:43 +00:00
Max Isbey d515c85a1f Cover the deferred-signature edge cases: instance access and defer_build turned off 2026-07-29 22:07:43 +00:00
Max Isbey 08f438584b Defer building the monolith protocol models and routing adapters
Set defer_build=True on MCPModel, the JSON-RPC envelope models and every
module-level TypeAdapter (the six union adapters in mcp_types, the message
adapter, and the four in mcp.client.session), so importing mcp_types no longer
pays for ~150 models' core schemas, validators and serializers: each is built
once, on first use. The explicit model_rebuild() calls go away (they force an
eager build), and the request-parameter models that carry InputResponses plus
the ContentBlock members are defined before their users so their field
metadata is complete without a first-use rebuild.

Under defer_build pydantic derives a model's __signature__ only when it
builds; a class-level lazy __signature__ (mcp_types._deferred, public pydantic
API only) completes the one-time build via model_rebuild() on first
inspect.signature() access, so introspection matches an eagerly-built model.
MCPModel and the generated wire bases install it for all their subclasses;
models deriving from BaseModel directly opt in with @deferred_model.
2026-07-29 22:05:05 +00:00
Max Isbey b8bc17d787 Defer building the generated wire models and emit them in dependency order
The generated _v2025_11_25 and _v2026_07_28 packages spent ~100-115 ms
each at import building pydantic core schemas, validators and serializers
for ~175 models per version, of which a connection touches only a handful.

Both generated bases now carry defer_build=True: WireModel, and a new
generic WireRootModel[T] the RootModel aliases derive from (deferring only
the object models made things slower, as the eager union roll-ups then
regenerated every deferred model's schema inline). The generator drops
codegen's trailing model_rebuild() epilogue and emits classes in dependency
order - failing if any class names a class defined later - so every
non-recursive generated class still exposes complete field metadata at
import. Building each model's validator now happens once, on first use.
2026-07-29 22:04:51 +00:00
Max Isbey 0237ed9411 Load per-version wire packages lazily from the methods surface maps
mcp_types.methods imported both generated wire packages (_v2025_11_25 and
_v2026_07_28, roughly 200 pydantic models each) at module import solely to
fill the surface maps' values, although a connection negotiates exactly one
protocol version and nothing reads a value at import time.

The map rows now record the wire type's attribute name; the package a row
lives in follows from the key's protocol version and is imported by a cached
lookup on the first row read, then cached per row. The public maps stay
MappingProxyType objects over the resolved rows (same protocol, repr and
equality as before), so importing mcp_types.methods - and therefore mcp,
mcp.types and every entry point above them - no longer builds any wire
package; the first message parsed for a version builds that version once.

The elicitation module's single wire-schema import moves into the function
that uses it for the same reason.
2026-07-29 19:46:20 +00:00
Max b9422f1c9b Make the per-version wire packages private (mcp_types._v*) (#3191) 2026-07-27 22:16:48 +01:00
Max 837ef904f8 Align with spec #3002: optional clientInfo, serverInfo in result _meta (#3143)
Deploy Docs / deploy-docs (push) Has been cancelled
CI / checks (push) Failing after 24m23s
CI / all-green (push) Has been cancelled
Conformance Tests / server-conformance (push) Has been cancelled
Conformance Tests / client-conformance (push) Has been cancelled
GitHub Actions Security Analysis / zizmor (push) Has been cancelled
2026-07-23 12:00:36 +01:00
Max 4df609119f Add a client extension API (#3034) 2026-06-30 21:31:02 +01:00
Max 7322ca56f4 Require integrity protection for MRTR requestState (#3032) 2026-06-30 21:30:32 +01:00
Max b15b1d5f07 Add a client-side response cache honoring SEP-2549 caching hints (#3023) 2026-06-30 11:31:06 +01:00
Max e942d00b98 Re-vendor 2026-07-28 schema at spec ead35b59 (SubscriptionsListenResult) (#3006) 2026-06-27 10:15:34 +02:00
Max 3a8da8c0c3 Fix docs/release follow-ups from the mcp-types package split (#2977) 2026-06-26 13:16:09 +02:00
Marcelo Trylesinski 0ee7f1b293 Split protocol types into a standalone mcp-types package (#2973) 2026-06-25 19:18:38 +02:00
Max 84bf9bde05 First end-to-end 2026-07-28 stateless tools/call (experimental entry + ClientSession pin) (#2917) 2026-06-20 14:55:59 +01:00
Max 1cec2d60f4 Relax monolith ElicitRequestURLParams.elicitation_id for 2026-07-28 (#2913) 2026-06-19 16:06:08 +01:00
Max 510832aa45 Re-vendor 2026-07-28 schema and absorb spec #2907 error-code renumber (#2912) 2026-06-19 15:46:15 +01:00
Max 65be5a7147 Protocol types for 2026-07-28: superset monolith, committed per-version packages, and wire-method maps (#2849) 2026-06-16 17:40:14 +01:00