This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Delete any shim that accepted or synthesized null-id error responses. Code that assumed `error.id` was always a `str | int` must now handle `None`, and tests that pinned v1's rejection of `"id": null` now fail because validation succeeds.
### Protocol models build their validators on first use
The protocol models (`mcp.types` / `mcp_types`, including the JSON-RPC envelopes) now build
their pydantic validators on a model's first use in the process instead of at import
(`defer_build`), which is most of the SDK's startup cost. Validation, serialization, JSON
schemas, and everything after a model's first use are unchanged (one wrinkle: `inspect.signature`
on the `model_rebuild` / `model_json_schema` classmethods shows the SDK's evaluated annotations
rather than pydantic's stringised type aliases — parameter names, kinds and defaults are
identical). Two things are observable *before* a model's first use:
* `inspect.signature(Model)` / `help(Model)` show pydantic's generic `(**data)` initializer
and `Model.__pydantic_complete__` is `False`. A few models (`CallToolRequest`,
behaved this way; it now applies to all of them until first use. Use the model once, or call
`Model.model_rebuild()`, when you need the resolved signature earlier.
* Every model's MRO gains one private base (`mcp_types._wire_base.DeferredModel`) between it
and `pydantic.BaseModel`, visible only to code that walks `__mro__`.
* The module-level parse adapters (`client_request_adapter`, ..., `jsonrpc_message_adapter`)
are instances of a private `TypeAdapter` subclass (`mcp_types._wire_base.DeferredAdapter`) so
their first-use build takes the same lock; `isinstance(adapter, TypeAdapter)` and every
documented `TypeAdapter` operation are unchanged.
The one-time build cost moves from `import` to each model's first use — a few milliseconds
for the first message a connection parses; see [Startup cost](advanced/startup.md).
The per-version wire types behind the `mcp_types.methods` surface maps
(`CLIENT_REQUESTS`, `SERVER_RESULTS`, ...) go further: a version's wire types load on the
first row read for that version — in practice with the first message a connection parses —
rather than at `import mcp_types.methods`, so a process loads only the protocol version it
negotiates. Every documented map operation is unchanged (lookup, `in`, iteration, `len`,
`get`, `==`, spreading into an extension map, `repr`); the whole-map reads among them
load both versions at that moment. Three obscurities are observable: the maps'
non-`Mapping` dict extras are gone (`.copy()`, `|`, `reversed()`, and `keys()`/`values()`/
`items()` return view objects), the internal `mcp_types.methods.v2025`/`v2026` attributes
are import-free stand-ins rather than the wire-package modules, and the wire types are no
longer bound in `mcp.server.elicitation`. Import the generated packages
(`mcp_types._v2025_11_25`, `mcp_types._v2026_07_28`) directly if you need them, though the
version-free `mcp.types` models remain the supported surface.
## MCPServer (formerly FastMCP)
### `FastMCP` renamed to `MCPServer`
Expand Down
Expand Up
@@ -877,6 +936,27 @@ Beyond the constructor parameters that moved to `run()`/`streamable_http_app()`
Only private attributes moved: `mcp._mcp_server` is now `mcp._lowlevel_server` (see [Registering lowlevel handlers from `MCPServer`](#registering-lowlevel-handlers-from-mcpserver)), and `_session_manager` now lives on that lowlevel `Server`. Prefer the public `mcp.session_manager` property to either.
### The server modules no longer import the HTTP stack
`mcp.server.lowlevel.server` and `mcp.server.mcpserver.server` used to import the Streamable
HTTP / SSE stack at module top, so any server — including a stdio one — loaded starlette,
`sse_starlette`, and `uvicorn` at import. That stack now loads inside `streamable_http_app()`,
`sse_app()`, and `custom_route()`, their only users; a stdio server never pays for it. Two
things follow:
* The HTTP names that were only incidentally reachable as attributes of those two modules
* `typing.get_type_hints()` on the HTTP-app methods (`streamable_http_app`, `sse_app`,
`run_sse_async`, `run_streamable_http_async`, and the `session_manager` properties) raises
`NameError`, because their annotations name types those modules import for type checkers only;
pass them yourself as `localns={...}` if you evaluate the hints at runtime. See
[Startup cost](advanced/startup.md).
### `MCPServer.get_context()` removed
`MCPServer.get_context()` has been removed. Context is now injected by the framework and passed explicitly — there is no ambient ContextVar to read from.
Expand Down
Expand Up
@@ -2056,6 +2136,21 @@ result = await client.call_tool("long_running_task", {}, progress_callback=on_pr
Also drop `execution=ToolExecution(taskSupport=types.TASK_REQUIRED)` from tool definitions: the `TASK_REQUIRED` / `TASK_OPTIONAL` / `TASK_FORBIDDEN` constants are gone from `mcp.types` (`ToolExecution.task_support` takes the plain `"required"` / `"optional"` / `"forbidden"` literal), and no v2 client or server reads the field.
### `mcp.client.client` no longer imports the server stack
The client module no longer imports the server, so names that were only incidentally
reachable as attributes of `mcp.client.client` (`Server`, `MCPServer`, `modern_on_request`,
`InMemoryTransport`, `streamable_http_client`) are no longer bound there. Import and
`mock.patch` them at their own modules: `mcp.server.Server`, `mcp.server.mcpserver.MCPServer`,
Server-side transport entry points (`stdio_server()`, `SseServerTransport`, `StreamableHTTPSessionManager`) keep their v1 import paths and signatures (see [Lowlevel `Server`: what did not change](#lowlevel-server-what-did-not-change)), so the sections below are client-side apart from [`stdio_server` keeps the protocol streams on private descriptors](#stdio_server-keeps-the-protocol-streams-on-private-descriptors); the other server-side transport changes ([lifespan entered once](#streamable-http-lifespan-now-entered-once-at-manager-startup), the [4 MiB request-body limit](#streamable-http-request-bodies-are-limited-to-4-mib)) sit under MCPServer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cut import and startup cost with deferred model builds and lazy imports #3242
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Are you sure you want to change the base?
Uh oh!
There was an error while loading. Please reload this page.
Cut import and startup cost with deferred model builds and lazy imports #3242
Filter by extension
Only manifest files
Viewed files
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.