Add mcp.types as a permanent alias for mcp_types (#3190)

This commit is contained in:
Max 2026-07-27 23:47:04 +01:00 • committed by GitHub
commit b7c9a916d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
172 changed files with 467 additions and 364 deletions

View file

@ -128,7 +128,7 @@ The same file's `main()` is the whole client story, both halves of it:
The one interceptive hook. Override `intercept_tool_call` to observe, short-circuit,
or veto a tool call:
```python title="server.py" hl_lines="18-25"
```python title="server.py" hl_lines="17-24"
--8<-- "docs_src/extensions/tutorial005.py"
```
@ -158,7 +158,7 @@ A **client extension** is the same contract from the consuming side: a bundle of
client-side behaviour behind one identifier. Pass instances to
`Client(extensions=[...])` and call tools normally:
```python title="client.py" hl_lines="67-69"
```python title="client.py" hl_lines="66-68"
--8<-- "docs_src/extensions/tutorial006.py"
```
@ -188,7 +188,7 @@ client = Client(mcp, extensions=[advertise("com.example/search")])
Subclass `ClientExtension` and override only what you need. Three contribution
kinds, each with a default: `settings()`, `claims()`, and `notifications()`.
```python title="client.py" hl_lines="18-19 44-45 47-48"
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```
@ -226,12 +226,12 @@ claimed shape reaching a session-tier caller raises `UnexpectedClaimedResult`.
### Extension verbs
An extension's own request methods need no client-side registration. A vendor request
type subclasses `mcp_types.Request` and goes through `client.session.send_request`,
type subclasses `mcp.types.Request` and goes through `client.session.send_request`,
as in [Serving your own methods](#serving-your-own-methods). One addition: when a
params key must ride the `Mcp-Name` header (extension specs such as tasks require
this for their verbs), the request type declares `name_param`:
```python title="client.py" hl_lines="23-26 47-48"
```python title="client.py" hl_lines="22-25 46-47"
--8<-- "docs_src/extensions/tutorial007.py"
```

View file

@ -14,7 +14,7 @@ For everything else, stay on `MCPServer`.
This is the `search_books` tool that **[Tools](../servers/tools.md)** writes in nine lines of `@mcp.tool()`, with the sugar removed:
```python title="server.py" hl_lines="23 27 33"
```python title="server.py" hl_lines="22 26 32"
--8<-- "docs_src/lowlevel/tutorial001.py"
```
@ -80,7 +80,7 @@ That generalises. An exception raised from a low-level handler is **always** a p
`on_call_tool` is the single entry point for every tool on the server. You route on `params.name`:
```python title="server.py" hl_lines="39-44"
```python title="server.py" hl_lines="38-43"
--8<-- "docs_src/lowlevel/tutorial002.py"
```
@ -91,7 +91,7 @@ That generalises. An exception raised from a low-level handler is **always** a p
Declare `output_schema` on the `Tool` and put `structured_content` on the result. Both are yours:
```python title="server.py" hl_lines="20-24 37"
```python title="server.py" hl_lines="19-23 36"
--8<-- "docs_src/lowlevel/tutorial003.py"
```
@ -117,7 +117,7 @@ The server never compares the two fields. This SDK's `Client` does: return `stru
Use it for record IDs, trace IDs, anything your UI needs and your prompt doesn't:
```python title="server.py" hl_lines="38"
```python title="server.py" hl_lines="37"
--8<-- "docs_src/lowlevel/tutorial004.py"
```
@ -144,7 +144,7 @@ No `resources`, no `prompts`: there is nothing to back them. Pass `on_list_promp
`Server` is generic in the type its lifespan yields. Annotate it once and the object is typed everywhere it surfaces:
```python title="server.py" hl_lines="25-27 45-46 51"
```python title="server.py" hl_lines="24-26 44-45 50"
--8<-- "docs_src/lowlevel/tutorial005.py"
```

View file

@ -16,7 +16,7 @@ If `Server(name, on_call_tool=...)` is new to you, read **[The low-level Server]
One server, one tool, one middleware that logs how long each message took:
```python title="server.py" hl_lines="40-46 50"
```python title="server.py" hl_lines="39-45 49"
--8<-- "docs_src/middleware/tutorial001.py"
```

View file

@ -10,7 +10,7 @@ Pagination is for the server whose resource list is really a database: thousands
## A server that pages
```python title="server.py" hl_lines="13 16-17"
```python title="server.py" hl_lines="12 15-16"
--8<-- "docs_src/pagination/tutorial001.py"
```
@ -38,7 +38,7 @@ The tenth page comes back with `next_cursor` set to `None`. Done.
Every `list_*` method on `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) takes a `cursor=` keyword. Draining a paged list is one `while True`:
```python title="client.py" hl_lines="27-33"
```python title="client.py" hl_lines="26-32"
--8<-- "docs_src/pagination/tutorial002.py"
```

View file

@ -25,7 +25,7 @@ Out of the box every result says `ttlMs: 0, cacheScope: "private"`: immediately
On the low-level `Server`, handlers build their results by hand, and `ttl_ms` / `cache_scope` are just fields on the result models. A handler that sets them explicitly always wins over the constructor map, field by field:
```python title="server.py" hl_lines="11 17"
```python title="server.py" hl_lines="10 16"
--8<-- "docs_src/caching/tutorial002.py"
```
@ -39,7 +39,7 @@ One caveat on paginated lists: the protocol requires the **same `cacheScope` on
On a 2026-07-28 session, `Client` honors the hints for you: it has a built-in response cache, on by default. A result that arrives carrying a `ttlMs` is stored, and an identical call within that TTL is served from the cache with no round trip. A result that carries *no* hint is not cached: hint-less results get `CacheConfig.default_ttl_ms`, which defaults to `0` (immediately stale), so a server that declares nothing sees exactly the call-for-call traffic it always did.
```python title="client.py" hl_lines="34 36 39"
```python title="client.py" hl_lines="33 35 38"
--8<-- "docs_src/caching/tutorial003.py"
```

View file

@ -19,7 +19,7 @@ That is the server half, and the **[Elicitation](../handlers/elicitation.md)** p
## The elicitation callback
```python title="client.py" hl_lines="7-11 17-18"
```python title="client.py" hl_lines="6-10 16-17"
--8<-- "docs_src/client_callbacks/tutorial002.py"
```
@ -55,7 +55,7 @@ result.content # [TextContent(type='text', text='Card issued to Ada Lovelace.')
One `tools/call` from you, one `elicitation/create` back from the server, answered by your function, all inside a single tool call.
!!! info
`mode="legacy"` on line 17 is doing real work. By default `Client(...)` negotiates the modern
`mode="legacy"` on the `Client(...)` call is doing real work. By default `Client(...)` negotiates the modern
protocol path, and that path has no back-channel for server-to-client requests: `ctx.elicit`
fails before your callback ever runs. The transport doesn't decide that; the negotiated
protocol does, in-memory and over a URL alike. Pin `mode="legacy"` whenever your client has

View file

@ -135,7 +135,7 @@ A tool that raises does **not** raise in your client. It comes back as an ordina
The resource verbs come in pairs: two ways to list, one way to read.
```python title="client.py" hl_lines="23-32"
```python title="client.py" hl_lines="22-31"
--8<-- "docs_src/client/tutorial004.py"
```
@ -174,7 +174,7 @@ A host hands those messages straight to the model. That is the whole feature.
A server with a completion handler can autocomplete prompt and resource-template arguments as the user types.
```python title="client.py" hl_lines="28-32"
```python title="client.py" hl_lines="27-31"
--8<-- "docs_src/client/tutorial006.py"
```
@ -187,7 +187,7 @@ The answer is in `result.completion.values`. Type `"p"` and the server comes bac
Every `list_*` method takes a `cursor=` keyword and every result carries a `next_cursor`. When `next_cursor` is `None`, you have everything.
```python title="client.py" hl_lines="23-31"
```python title="client.py" hl_lines="22-30"
--8<-- "docs_src/client/tutorial007.py"
```

View file

@ -42,7 +42,7 @@ Create a `ClientSessionGroup` and call **`connect_to_server`** once per server:
You fix this at the group, not at the servers. Pass a function of `(name, server_info)` and the group runs it on every name it registers:
```python title="client.py" hl_lines="8-9 16"
```python title="client.py" hl_lines="7-8 15"
--8<-- "docs_src/session_groups/tutorial004.py"
```

View file

@ -8,7 +8,7 @@ This page is the client end: opening the stream, watching it beside your main fl
A subscription is one context manager. Entering it sends the request, with your keyword arguments as the subscription filter, and waits for the server's acknowledgment, so the stream is live by the time the block starts.
```python title="client.py" hl_lines="16 19 29"
```python title="client.py" hl_lines="15 18 28"
--8<-- "docs_src/subscriptions/tutorial003.py"
```

View file

@ -31,9 +31,9 @@ These docs describe **v2**, currently a release candidate, so the version pin is
You don't need to know any of this to use the SDK, but if you're wondering what each dependency is for:
* `mcp-types`: every protocol type (requests, results, content blocks) as its own package, versioned in lockstep with the SDK. Every `from mcp_types import ...` in these docs is this package.
* `mcp-types`: every protocol type (requests, results, content blocks) as its own package, versioned in lockstep with the SDK. Code that depends on `mcp` imports it through the `mcp.types` alias (every `from mcp.types import ...` in these docs); import `mcp_types` directly only in a project that installs `mcp-types` without the SDK.
* [`anyio`](https://anyio.readthedocs.io/): the async runtime. The whole SDK is written against anyio, so it runs on either `asyncio` or `trio`.
* [`pydantic`](https://docs.pydantic.dev/): what every `mcp_types` model is built on, plus all schema generation and validation.
* [`pydantic`](https://docs.pydantic.dev/): what every `mcp.types` model is built on, plus all schema generation and validation.
* [`httpx2`](https://pypi.org/project/httpx2/): the HTTP client behind the Streamable HTTP and SSE *client* transports, with server-sent events support built in.
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/), and [`python-multipart`](https://pypi.org/project/python-multipart/): the HTTP *server* transports.
* [`jsonschema`](https://pypi.org/project/jsonschema/): validates a tool's structured output against its declared output schema.

View file

@ -40,7 +40,7 @@ Now the test:
import pytest
from inline_snapshot import snapshot
from mcp import Client
from mcp_types import CallToolResult, TextContent
from mcp.types import CallToolResult, TextContent
from server import mcp

View file

@ -138,7 +138,7 @@ That's the right default for a precondition: no answer, no order. When declining
Elicitation is one of the three questions a resolver can ask, and the multi-round-trip flow allows no others. The other two go to the **client** rather than the user: return `Sample(...)` to run an LLM call through the client (a `sampling/createMessage` request), or `ListRoots()` to fetch the client's current roots. Neither has an accept/decline outcome; the consumer annotates the result type directly, `CreateMessageResult` (`CreateMessageResultWithTools` when the request carries `tools` or `tool_choice`) or `ListRootsResult`:
```python title="server.py" hl_lines="11-16 22"
```python title="server.py" hl_lines="10-15 21"
--8<-- "docs_src/dependencies/tutorial004.py"
```

View file

@ -128,7 +128,7 @@ Look at the second tool. When your server learns the out-of-band flow finished (
Servers ask. Clients answer by passing an **`elicitation_callback`** to `Client(...)`:
```python title="client.py" hl_lines="7-8 19"
```python title="client.py" hl_lines="6-7 18"
--8<-- "docs_src/elicitation/tutorial003.py"
```

View file

@ -21,7 +21,7 @@ That's the whole protocol. Every leg is an ordinary request from the client to t
On `@mcp.tool()` you rarely build this by hand: declare a dependency that asks the user (`Elicit`), samples the client's LLM (`Sample`), or lists its roots (`ListRoots`) and the SDK returns the `InputRequiredResult` for you; that form is the **[Dependencies](dependencies.md)** page. The two forms don't mix: a call has one `input_responses`/`request_state` channel, so a tool that uses `Resolve(...)` parameters cannot also return `InputRequiredResult` from its body. A declared `InputRequiredResult` return is rejected at registration (`InvalidSignature`), and an undeclared one fails the call at runtime. The manual form is the **low-level** `Server`, whose `on_call_tool` handler is allowed to return either result type:
```python title="server.py" hl_lines="44-47"
```python title="server.py" hl_lines="43-46"
--8<-- "docs_src/mrtr/tutorial001.py"
```
@ -35,7 +35,7 @@ Everything else in that file (the explicit `input_schema`, the hand-built `CallT
`tools/call` is not special: at 2026-07-28 a server may answer `prompts/get` and `resources/read` the same way. On `MCPServer`, an `@mcp.prompt()` function — or an `@mcp.resource()` **template** function — returns the `InputRequiredResult` itself and reads the retry's answers off the context:
```python title="server.py" hl_lines="21 23 25"
```python title="server.py" hl_lines="20 22 24"
--8<-- "docs_src/mrtr/tutorial004.py"
```
@ -51,7 +51,7 @@ Everything else in that file (the explicit `input_schema`, the hand-built `CallT
Register the callbacks the server might ask for (`elicitation_callback`, `sampling_callback`, `list_roots_callback`) and call the tool. When an `InputRequiredResult` arrives, `Client` dispatches each entry in `input_requests` to the matching callback, retries with the answers and the echoed `request_state`, and keeps going until a `CallToolResult` comes back:
```python title="client.py" hl_lines="12 13"
```python title="client.py" hl_lines="11 12"
--8<-- "docs_src/mrtr/tutorial003.py"
```
@ -76,7 +76,7 @@ The auto-loop is enough for a single-process client. Own the loop instead when:
Drop to the underlying session, where `allow_input_required=True` hands you the union directly:
```python title="client.py" hl_lines="13 14 20"
```python title="client.py" hl_lines="12 13 19"
--8<-- "docs_src/mrtr/tutorial002.py"
```

View file

@ -11,7 +11,7 @@ Both still work, on every protocol version the SDK speaks. But read the warning
A resolver returns `Sample(...)` and the tool receives the completion, through the same dependency mechanism that runs `Elicit` in **[Dependencies](dependencies.md)**:
```python title="server.py" hl_lines="11-16 20"
```python title="server.py" hl_lines="10-15 19"
--8<-- "docs_src/sampling_and_roots/tutorial001.py"
```
@ -24,7 +24,7 @@ A resolver returns `Sample(...)` and the tool receives the completion, through t
Roots are the folders the client says the server may operate on. They are informational guidance, not an access-control mechanism. A resolver returns `ListRoots()`:
```python title="server.py" hl_lines="11-12 16"
```python title="server.py" hl_lines="10-11 15"
--8<-- "docs_src/sampling_and_roots/tutorial002.py"
```

View file

@ -56,7 +56,7 @@ Two things the stream is *not*:
Here is a client on the other side of that stream, following the board:
```python title="client.py" hl_lines="16"
```python title="client.py" hl_lines="15"
--8<-- "docs_src/subscriptions/tutorial003.py"
```
@ -119,7 +119,7 @@ async def tools_reloaded() -> None:
Down on the low-level `Server` there is no pre-wired anything, and the same parts assemble in three lines:
```python title="server.py" hl_lines="9-10 48"
```python title="server.py" hl_lines="8-9 47"
--8<-- "docs_src/subscriptions/tutorial002.py"
```

View file

@ -14,7 +14,7 @@ Every section heading below names the API it affects, so searching this page for
|---|---|---|
| `FastMCP` renamed to `MCPServer` | `ModuleNotFoundError: No module named 'mcp.server.fastmcp'` | [`FastMCP` renamed](#fastmcp-renamed-to-mcpserver) |
| Fields renamed from camelCase to snake_case | `AttributeError: 'Tool' object has no attribute 'inputSchema'` | [snake_case fields](#field-names-changed-from-camelcase-to-snake_case) |
| `mcp.types` moved to the `mcp-types` package | `ModuleNotFoundError: No module named 'mcp.types'` | [`mcp.types` moved](#mcptypes-moved-to-the-mcp-types-package) |
| `mcp.types` names removed | `ImportError: cannot import name 'Content' from 'mcp.types'` | [Removed types](#removed-type-aliases-and-classes) |
| `McpError` renamed to `MCPError` | `ImportError: cannot import name 'McpError' from 'mcp'` | [`McpError` renamed](#mcperror-renamed-to-mcperror) |
| Resource URIs are `str`, not `AnyUrl` | `AttributeError: 'str' object has no attribute 'host'` | [URI type](#resource-uri-type-changed-from-anyurl-to-str) |
| Message unions (`ServerNotification`, `JSONRPCMessage`, ...) are plain unions, not `RootModel` | `AttributeError: 'LoggingMessageNotification' object has no attribute 'root'` | [`RootModel` → unions](#replace-rootmodel-by-union-types-with-typeadapter-validation) |
@ -196,21 +196,38 @@ nothing on PyPI to pin to, keep the unpinned form.
### `mcp.types` moved to the `mcp-types` package
The protocol wire types now live in a standalone distribution, `mcp-types`, imported as
`mcp_types`. Its only runtime dependencies are `pydantic` and `typing-extensions`, so code
that just needs to (de)serialize MCP traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
continues to re-export the type names at the top level, so `from mcp import Tool` is
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed. The
package's API reference is at [`mcp_types`](api/mcp_types/index.md).
The protocol wire types now live in a standalone distribution, `mcp-types` (import package
`mcp_types`). Its only runtime dependencies are `pydantic` and `typing-extensions`, so code
that just needs to (de)serialize MCP traffic can install it without the full SDK. Its API
reference is at [`mcp_types`](api/mcp_types/index.md).
The supported import surface is `mcp_types`, `mcp_types.jsonrpc`, `mcp_types.methods`, and
`mcp_types.version`. Underscore-prefixed submodules (`mcp_types._types`, and the generated
**If your project depends on `mcp`, nothing changes for you.** `import mcp.types`,
`from mcp.types import ...`, `from mcp import types`, and `import mcp` followed by
`mcp.types.Tool` all keep working: `mcp.types` is a permanent alias that mirrors `mcp_types`
exactly (every name is the same object), and `mcp.types.version` mirrors
`mcp_types.version` the same way. Keep importing through `mcp` — the package you actually
depend on — rather than writing `import mcp_types`, which would reach past your declared
dependency into a transitive one. The old `mcp.shared.version` module was removed; import the
version registry from `mcp.types.version` instead. The top-level `from mcp import Tool`
re-exports are unchanged too.
**Import `mcp_types` directly only in a project that depends on `mcp-types` without the
SDK.** That is the point of the split: tooling and lightweight clients can depend on the
protocol schema without pulling in `httpx2`, `starlette`, `uvicorn`, and the rest of the
server/transport stack.
Names that no longer exist (listed under
[Removed type aliases and classes](#removed-type-aliases-and-classes)) fail on import or
attribute access with an ordinary `ImportError` / `AttributeError`; the table below names each
replacement.
The supported import surface is the package plus its `jsonrpc`, `methods`, and `version`
submodules, and each has both spellings: `mcp.types` / `mcp_types`, `mcp.types.jsonrpc` /
`mcp_types.jsonrpc`, `mcp.types.methods` / `mcp_types.methods`, and `mcp.types.version` /
`mcp_types.version` (each `mcp.types` module mirrors its `mcp_types` counterpart, name for
name, the same objects). Underscore-prefixed submodules (`mcp_types._types`, and the generated
per-protocol-version packages `mcp_types._v2025_11_25` / `mcp_types._v2026_07_28`) are internal
validators with unstable class names; don't import from them.
**Why:** keeping the wire types in their own package lets tooling and lightweight clients
depend on the protocol schema without pulling in `httpx2`, `starlette`, `uvicorn`, and the
rest of the server/transport stack.
validators with unstable class names; don't import from them, under either spelling.
**Before (v1):**
@ -219,19 +236,23 @@ from mcp.types import Tool, Resource
from mcp.shared.version import LATEST_PROTOCOL_VERSION
```
**After (v2):**
**After (v2), depending on `mcp`:**
```python
from mcp.types import Tool, Resource # unchanged
from mcp.types.version import LATEST_PROTOCOL_VERSION
```
**After (v2), depending only on `mcp-types` (no SDK):**
```python
from mcp_types import Tool, Resource
from mcp_types.version import LATEST_PROTOCOL_VERSION
# Names `mcp` already re-exported at the top level are unchanged:
from mcp import Tool, Resource
```
### Removed type aliases and classes
The following type aliases and classes have been removed from `mcp_types`:
The following type aliases and classes have been removed from the protocol types (`mcp.types` / `mcp_types`):
| Removed | Replacement |
|---------|-------------|
@ -254,13 +275,13 @@ from mcp.types import Content, ResourceReference, Cursor
**After (v2):**
```python
from mcp_types import ContentBlock, ResourceTemplateReference
from mcp.types import ContentBlock, ResourceTemplateReference
# Use `str` instead of `Cursor` for pagination cursors
```
### Field names changed from camelCase to snake_case
All Pydantic model fields in `mcp_types` now use snake_case names for Python attribute access. The JSON wire format is unchanged — traffic the SDK sends still uses camelCase via Pydantic aliases, but your own `model_dump()` calls now need `by_alias=True` to produce it.
All Pydantic model fields in the protocol types now use snake_case names for Python attribute access. The JSON wire format is unchanged — traffic the SDK sends still uses camelCase via Pydantic aliases, but your own `model_dump()` calls now need `by_alias=True` to produce it.
**Before (v1):**
@ -320,7 +341,7 @@ In v1, MCP protocol types were configured with `extra="allow"`: unknown fields p
In v2, MCP types silently ignore extra fields. Unknown constructor keyword arguments and unknown keys in wire data are dropped during validation — no error is raised, and the values do not round-trip:
```python
from mcp_types import CallToolRequestParams
from mcp.types import CallToolRequestParams
params = CallToolRequestParams(
name="my_tool",
@ -356,7 +377,7 @@ resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validatio
**After (v2):**
```python
from mcp_types import Resource
from mcp.types import Resource
# Plain strings accepted
resource = Resource(name="test", uri="users/me") # Works
@ -426,7 +447,7 @@ actual_notification = notification.root
**After (v2):**
```python
from mcp_types import client_request_adapter, server_notification_adapter
from mcp.types import client_request_adapter, server_notification_adapter
# Using TypeAdapter.validate_python()
request = client_request_adapter.validate_python(data)
@ -469,7 +490,7 @@ await session.send_notification(
| `ServerResult` | `server_result_adapter` |
| `JSONRPCMessage` | `jsonrpc_message_adapter` |
All adapters are exported from `mcp_types`.
All adapters are exported from `mcp.types`.
These are ordinary `X | Y` unions of the concrete pydantic classes, so `isinstance(msg, ServerNotification)`, `isinstance(msg, LoggingMessageNotification)`, and `match`/`case` on the member classes keep working (unlike `ElicitationResult`, which became a `TypeAliasType` — see [`isinstance()` checks against `ElicitationResult` raise `TypeError`](#isinstance-checks-against-elicitationresult-raise-typeerror)).
@ -525,7 +546,7 @@ attribute access. The JSON wire format is unchanged.
### `SUPPORTED_PROTOCOL_VERSIONS` deprecated; `LATEST_PROTOCOL_VERSION` changed meaning
`SUPPORTED_PROTOCOL_VERSIONS` is deprecated — it's now the union of `HANDSHAKE_PROTOCOL_VERSIONS` (initialize-handshake versions) and `MODERN_PROTOCOL_VERSIONS` (per-request-envelope versions). If you were using it to mean "versions the initialize handshake accepts", switch to `HANDSHAKE_PROTOCOL_VERSIONS`. Named scalars derived from these tuples are now exported alongside them — `LATEST_HANDSHAKE_VERSION`, `LATEST_MODERN_VERSION`, `OLDEST_SUPPORTED_VERSION` — so prefer those over indexing the tuples directly. All of these live in `mcp_types.version` (previously `mcp.shared.version`): `from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS`.
`SUPPORTED_PROTOCOL_VERSIONS` is deprecated — it's now the union of `HANDSHAKE_PROTOCOL_VERSIONS` (initialize-handshake versions) and `MODERN_PROTOCOL_VERSIONS` (per-request-envelope versions). If you were using it to mean "versions the initialize handshake accepts", switch to `HANDSHAKE_PROTOCOL_VERSIONS`. Named scalars derived from these tuples are now exported alongside them — `LATEST_HANDSHAKE_VERSION`, `LATEST_MODERN_VERSION`, `OLDEST_SUPPORTED_VERSION` — so prefer those over indexing the tuples directly. All of these live in `mcp.types.version` (an alias of `mcp_types.version`; previously `mcp.shared.version`): `from mcp.types.version import HANDSHAKE_PROTOCOL_VERSIONS`.
`LATEST_PROTOCOL_VERSION` also changed value and meaning. In v1 it was `"2025-11-25"`, the version the client offered during initialization. In v2 it is the newest revision the SDK speaks in any era, currently `"2026-07-28"`, which the initialize handshake cannot negotiate. If you offered it in a hand-built `initialize` request or compared the negotiated version against it, use `LATEST_HANDSHAKE_VERSION` instead. These tuples really are tuples now (`SUPPORTED_PROTOCOL_VERSIONS` was a `list` in v1), so list-only operations such as concatenating with a list raise `TypeError`.
@ -576,7 +597,7 @@ raise McpError(ErrorData(code=INVALID_REQUEST, message="bad input"))
```python
from mcp.shared.exceptions import MCPError
from mcp_types import INVALID_REQUEST
from mcp.types import INVALID_REQUEST
raise MCPError(INVALID_REQUEST, "bad input")
# or, if you already have an ErrorData:
@ -601,7 +622,7 @@ JSONRPCMessage.model_validate(
**After (v2):**
```python
from mcp_types import PARSE_ERROR, ErrorData, JSONRPCError, jsonrpc_message_adapter
from mcp.types import PARSE_ERROR, ErrorData, JSONRPCError, jsonrpc_message_adapter
message = jsonrpc_message_adapter.validate_python(
{"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
@ -641,7 +662,7 @@ mcp = MCPServer("Demo")
All submodules under `mcp.server.fastmcp.*` are now under `mcp.server.mcpserver.*` with the same structure. Common imports:
- `Image`, `Audio` — from `mcp.server.mcpserver` (or `.utilities.types`)
- `Icon` — from `mcp.server.mcpserver` or `mcp_types` (not a top-level `mcp` export); its `mimeType` field is now `mime_type` per the [snake_case renames](#field-names-changed-from-camelcase-to-snake_case), though the `mimeType=` kwarg still constructs
- `Icon` — from `mcp.server.mcpserver` or `mcp.types` (not a top-level `mcp` export); its `mimeType` field is now `mime_type` per the [snake_case renames](#field-names-changed-from-camelcase-to-snake_case), though the `mimeType=` kwarg still constructs
- `Message`, `UserMessage`, `AssistantMessage` — from `mcp.server.mcpserver.prompts.base`
- `ToolError`, `ResourceError` — from `mcp.server.mcpserver.exceptions`
- `MCPServerError` (renamed from `FastMCPError`) — from `mcp.server.mcpserver.exceptions`
@ -844,7 +865,7 @@ Lifespans that set up process-wide state (connection pools, caches, background t
Beyond the constructor parameters that moved to `run()`/`streamable_http_app()` and the lifespan change above, the server-side Streamable HTTP machinery is as in v1:
- `mcp.server.streamable_http` still exports the `EventStore` ABC (`store_event()`, `replay_events_after()`), `EventMessage`, `EventCallback`, `EventId`, and `StreamId` with unchanged signatures; a custom `EventStore` only needs `JSONRPCMessage` imported from `mcp_types` instead of `mcp.types`.
- `mcp.server.streamable_http` still exports the `EventStore` ABC (`store_event()`, `replay_events_after()`), `EventMessage`, `EventCallback`, `EventId`, and `StreamId` with unchanged signatures; a custom `EventStore` keeps importing `JSONRPCMessage` from `mcp.types`, unchanged.
- `StreamableHTTPSessionManager` keeps its constructor and its `run()` / `handle_request()` methods (see [Lowlevel `Server`: what did not change](#lowlevel-server-what-did-not-change)); its `stateless=` parameter is unrelated to the removed [`Server.run(stateless=)` flag](#serverrun-no-longer-takes-a-stateless-flag).
- `mcp.session_manager` still returns the manager once `streamable_http_app()` has been called, with the same `stateless`, `json_response`, `event_store`, and `retry_interval` attributes.
- `stateless_http=True` still serves each request with a fresh transport, no `Mcp-Session-Id`, and no state carried between requests; `ctx.close_sse_stream()` and `ctx.close_standalone_sse_stream()` are still available on the handler `Context`.
@ -1205,7 +1226,7 @@ if isinstance(result, AcceptedElicitation):
... # result.data is a Confirm
```
Narrowing on `result.action` (`"accept"` / `"decline"` / `"cancel"`) is unaffected. The `TypeError` is specific to `TypeAliasType` aliases like `ElicitationResult`; the `mcp_types` message unions (`ClientRequest`, `ServerNotification`, `JSONRPCMessage`, ...) are ordinary unions and stay `isinstance`-compatible (see [Replace `RootModel` by union types with `TypeAdapter` validation](#replace-rootmodel-by-union-types-with-typeadapter-validation)).
Narrowing on `result.action` (`"accept"` / `"decline"` / `"cancel"`) is unaffected. The `TypeError` is specific to `TypeAliasType` aliases like `ElicitationResult`; the `mcp.types` message unions (`ClientRequest`, `ServerNotification`, `JSONRPCMessage`, ...) are ordinary unions and stay `isinstance`-compatible (see [Replace `RootModel` by union types with `TypeAdapter` validation](#replace-rootmodel-by-union-types-with-typeadapter-validation)).
### Registering lowlevel handlers from `MCPServer`
@ -1227,7 +1248,7 @@ In v2, the lowlevel `Server` supports arbitrary request handlers directly via `a
```python
from mcp.server import ServerRequestContext
from mcp_types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
from mcp.types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
@ -1296,7 +1317,7 @@ async def handle_call_tool(name: str, arguments: dict):
```python
from mcp.server import Server, ServerRequestContext
from mcp_types import (
from mcp.types import (
CallToolRequestParams,
CallToolResult,
ListToolsResult,
@ -1345,13 +1366,13 @@ All handlers receive `ctx: ServerRequestContext` as the first argument. The seco
| `@server.progress_notification()` | `on_progress` | `ProgressNotificationParams` | `None` |
| — | `on_roots_list_changed` | `NotificationParams \| None` | `None` |
All `params` and return types are importable from `mcp_types`.
All `params` and return types are importable from `mcp.types`.
**Notification handlers:**
```python
from mcp.server import Server, ServerRequestContext
from mcp_types import ProgressNotificationParams
from mcp.types import ProgressNotificationParams
async def handle_progress(ctx: ServerRequestContext, params: ProgressNotificationParams) -> None:
@ -1474,7 +1495,7 @@ async def call_tool(name: str, arguments: dict):
```python
from mcp.server import Server
from mcp_types import CallToolResult, TextContent
from mcp.types import CallToolResult, TextContent
async def handle_call_tool(ctx, params) -> CallToolResult:
@ -1618,7 +1639,7 @@ async def handle_call_tool(name: str, arguments: dict):
```python
from mcp.server import ServerRequestContext
from mcp_types import CallToolRequestParams, CallToolResult, TextContent
from mcp.types import CallToolRequestParams, CallToolResult, TextContent
async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
@ -1813,7 +1834,7 @@ result = await session.list_tools(cursor="next_page_token")
**After (v2):**
```python
from mcp_types import PaginatedRequestParams
from mcp.types import PaginatedRequestParams
result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token"))
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
@ -1901,7 +1922,7 @@ To migrate, replace `timedelta(...)` with plain seconds, or mechanically append
### Client request timeouts now raise `-32001` (`REQUEST_TIMEOUT`) instead of `408`
A client request that exceeds `read_timeout_seconds` still raises the SDK's protocol error (`MCPError`, previously `McpError`), but the error code changed from the HTTP status `408` (`httpx.codes.REQUEST_TIMEOUT`) to the JSON-RPC code `-32001` (`REQUEST_TIMEOUT`, importable from `mcp_types`), matching the TypeScript SDK. The message changed too: v1 said `"Timed out while waiting for response to ClientRequest. Waited 5.0 seconds."`, v2 says `"Request 'tools/call' timed out"`. `MCPError.error` still exists, so a migrated `e.error.code == 408` check runs without error and silently never matches; timeouts fall through to whatever generic-error handling follows. Code that matched on the old message text breaks too. Compare against `REQUEST_TIMEOUT` instead.
A client request that exceeds `read_timeout_seconds` still raises the SDK's protocol error (`MCPError`, previously `McpError`), but the error code changed from the HTTP status `408` (`httpx.codes.REQUEST_TIMEOUT`) to the JSON-RPC code `-32001` (`REQUEST_TIMEOUT`, importable from `mcp.types`), matching the TypeScript SDK. The message changed too: v1 said `"Timed out while waiting for response to ClientRequest. Waited 5.0 seconds."`, v2 says `"Request 'tools/call' timed out"`. `MCPError.error` still exists, so a migrated `e.error.code == 408` check runs without error and silently never matches; timeouts fall through to whatever generic-error handling follows. Code that matched on the old message text breaks too. Compare against `REQUEST_TIMEOUT` instead.
**Before (v1):**
@ -1922,7 +1943,7 @@ except McpError as e:
```python
from mcp.shared.exceptions import MCPError
from mcp_types import REQUEST_TIMEOUT # -32001
from mcp.types import REQUEST_TIMEOUT # -32001
try:
result = await client.call_tool("slow_tool", {})
@ -1933,7 +1954,7 @@ except MCPError as e:
raise
```
`e.error.code` also still works; `e.code` is the v2 convenience property. `mcp.types` no longer exists, so the constant comes from `mcp_types`. The example uses the high-level `Client`; `ClientSession.call_tool()` raises the same `MCPError`.
`e.error.code` also still works; `e.code` is the v2 convenience property. The constant is importable from `mcp.types` (or from `mcp_types` in a project that uses that package without the SDK). The example uses the high-level `Client`; `ClientSession.call_tool()` raises the same `MCPError`.
### `ClientSession` now runs on `JSONRPCDispatcher`; `BaseSession` removed
@ -1951,7 +1972,7 @@ Behavior changes:
- **Client callbacks now receive `mcp.client.ClientRequestContext`** (its `request_id` is always populated); the `mcp.shared.context.RequestContext` generic is deleted. Annotations spelled `RequestContext[ClientSession, Any]` become `ClientRequestContext` (details in [`RequestContext` type parameters simplified](#requestcontext-type-parameters-simplified)). Otherwise the callback surface is unchanged: the `sampling_callback=`, `elicitation_callback=`, `list_roots_callback=`, `logging_callback=`, and `message_handler=` keywords; the `SamplingFnT`, `ElicitationFnT`, `ListRootsFnT`, `LoggingFnT`, and `MessageHandlerFnT` protocols (still in `mcp.client.session`); and the params/result types (`CreateMessageRequestParams` → `CreateMessageResult | CreateMessageResultWithTools | ErrorData`, `ElicitRequestParams` → `ElicitResult | ErrorData`, `ListRootsResult | ErrorData` — returning `ErrorData` is not new). The `mcp.client.session`, `mcp.client.stdio`, `mcp.client.sse`, and `mcp.client.streamable_http` module paths are unchanged too, so `unittest.mock.patch` string targets still resolve.
- **`message_handler` no longer receives requests.** Server-initiated requests are answered by the typed callbacks (`sampling_callback`, `elicitation_callback`, `list_roots_callback`), so the handler's parameter is now `IncomingMessage = ServerNotification | Exception`, exported from `mcp.client`. Replace the hand-written v1 union `RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception` with `IncomingMessage`; `RequestResponder` is gone (below), so the old annotation no longer imports. Delivered notifications are the concrete member instances rather than the v1 `RootModel` wrapper, so drop `.root` (`message.params`, not `message.root.params`); see [Replace `RootModel` by union types with `TypeAdapter` validation](#replace-rootmodel-by-union-types-with-typeadapter-validation).
The `mcp.shared.session` module is gone. `RequestResponder` is removed — `respond()`, the cancellation-tracking members (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument) and `BaseSession._in_flight` have no replacement; inbound cancellation is handled by `JSONRPCDispatcher`. `ProgressFnT` now lives only in `mcp.shared.dispatcher`, and `RequestId` in `mcp_types`. The module's generic typing helpers (`SendRequestT`, `SendResultT`, `SendNotificationT`, `ReceiveRequestT`, `ReceiveResultT`, `ReceiveNotificationT`) went with it and have no re-export — the sessions are no longer generic; `ClientSession.send_request` takes a concrete request model plus a result model class (or `pydantic.TypeAdapter`), so an override that needs a type parameter can declare its own `TypeVar` bound to `pydantic.BaseModel`.
The `mcp.shared.session` module is gone. `RequestResponder` is removed — `respond()`, the cancellation-tracking members (`cancel()`, the `cancelled` and `in_flight` properties, the `on_complete` constructor argument) and `BaseSession._in_flight` have no replacement; inbound cancellation is handled by `JSONRPCDispatcher`. `ProgressFnT` now lives only in `mcp.shared.dispatcher`, and `RequestId` in `mcp.types`. The module's generic typing helpers (`SendRequestT`, `SendResultT`, `SendNotificationT`, `ReceiveRequestT`, `ReceiveResultT`, `ReceiveNotificationT`) went with it and have no re-export — the sessions are no longer generic; `ClientSession.send_request` takes a concrete request model plus a result model class (or `pydantic.TypeAdapter`), so an override that needs a type parameter can declare its own `TypeVar` bound to `pydantic.BaseModel`.
Subclassing `ClientSession` remains a valid interception point: every typed helper routes through `send_request`, and the notification helpers through `send_notification`, so overriding those two still sees that traffic (the 2026-era `discover()`/`send_discover()` are the exception — they call the dispatcher directly). For wire-level interception, use the `dispatcher=` argument instead (with the caveat above on hand-written dispatchers).
@ -1969,7 +1990,7 @@ async def elicitation_callback(
```python
from mcp.client import ClientRequestContext
from mcp_types import ElicitRequestParams, ElicitResult, ErrorData
from mcp.types import ElicitRequestParams, ElicitResult, ErrorData
async def elicitation_callback(
@ -1979,7 +2000,7 @@ async def elicitation_callback(
### Experimental Tasks support removed
Tasks ([SEP-1686](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686)) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp_types` as types-only definitions, except the `TaskExecutionMode` alias, whose literal is now inlined on `ToolExecution.task_support`.
Tasks ([SEP-1686](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686)) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp.types` as types-only definitions, except the `TaskExecutionMode` alias, whose literal is now inlined on `ToolExecution.task_support`.
The 2026-07-28 revision reintroduces Tasks as an official extension: [SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663), `io.modelcontextprotocol/tasks`, redesigned around polling (`tasks/get`) instead of a blocking `tasks/result`. This SDK does not implement the extension yet.
@ -2025,7 +2046,7 @@ result = await client.call_tool("long_running_task", {}, progress_callback=on_pr
**Detached work** (create the task now, fetch its result on a later connection or after a client restart) has no v2 equivalent until the SEP-2663 extension is implemented.
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.
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.
## Transports
@ -2187,7 +2208,7 @@ while True:
```python
from mcp import ClientSession, MCPError
from mcp.client.streamable_http import streamable_http_client
from mcp_types import INVALID_REQUEST # -32600
from mcp.types import INVALID_REQUEST # -32600
async with streamable_http_client(url) as (read, write):
async with ClientSession(read, write) as session:
@ -2626,7 +2647,7 @@ Validation runs when the result is serialized onto the wire, not when the model
### Client validates inbound traffic against the protocol schema
`ClientSession` now validates server requests, notifications, and results against the negotiated protocol version's schema before parsing them into `mcp_types` models. Spec-invalid server output that the previous monolith parse tolerated may now raise `pydantic.ValidationError` from `list_tools()`, `call_tool()`, and similar calls. `_meta` remains the sanctioned place for result extras (and `experimental` for capability extras).
`ClientSession` now validates server requests, notifications, and results against the negotiated protocol version's schema before parsing them into `mcp.types` models. Spec-invalid server output that the previous monolith parse tolerated may now raise `pydantic.ValidationError` from `list_tools()`, `call_tool()`, and similar calls. `_meta` remains the sanctioned place for result extras (and `experimental` for capability extras).
### Unknown request methods now return `-32601` (Method not found)

View file

@ -76,7 +76,7 @@ A **[multi-round-trip](../handlers/multi-round-trip.md)** tool needs something t
Here is a tool that asks before it acts, on a server that configures nothing:
```python title="server.py" hl_lines="15 21"
```python title="server.py" hl_lines="14 20"
--8<-- "docs_src/deploy/tutorial002.py"
```
@ -111,7 +111,7 @@ The two rounds are two independent HTTP requests, and several ordinary things se
The fix is one argument. It has **two** halves.
```python title="server.py" hl_lines="3 13 15"
```python title="server.py" hl_lines="1 12 14"
--8<-- "docs_src/deploy/tutorial003.py"
```

View file

@ -21,7 +21,7 @@ Nothing here is about completions yet.
Add **one** function decorated with `@mcp.completion()`:
```python title="server.py" hl_lines="22-30"
```python title="server.py" hl_lines="21-29"
--8<-- "docs_src/completions/tutorial002.py"
```
@ -91,7 +91,7 @@ You didn't list `completions` anywhere. The SDK saw the handler and declared the
That's what `context` is for. It carries the arguments the user has **already resolved**:
```python title="server.py" hl_lines="9-12 35-39"
```python title="server.py" hl_lines="8-11 34-38"
--8<-- "docs_src/completions/tutorial003.py"
```

View file

@ -41,7 +41,7 @@ The model is the one calling your tool. It picked the arguments. So a tool error
Now swap `ValueError` for `MCPError`.
```python title="server.py" hl_lines="1 3 15"
```python title="server.py" hl_lines="1 3 14"
--8<-- "docs_src/handling_errors/tutorial002.py"
```
@ -56,7 +56,7 @@ Now swap `ValueError` for `MCPError`.
* There is **no result**. No `content`, no `is_error`: nothing for the model to read.
* The **host** application gets the error instead, the same way it would if the tool didn't exist at all.
* `code`, `message`, and `data` arrive intact. `INVALID_PARAMS` is `-32602`; `mcp_types` exports it and the other JSON-RPC error codes (`INVALID_REQUEST`, `INTERNAL_ERROR`, ...) as constants so you never type a magic number.
* `code`, `message`, and `data` arrive intact. `INVALID_PARAMS` is `-32602`; `mcp.types` exports it and the other JSON-RPC error codes (`INVALID_REQUEST`, `INTERNAL_ERROR`, ...) as constants so you never type a magic number.
!!! check
Same lookup, same miss, but now the call *raises* on the client side instead of returning:
@ -127,7 +127,7 @@ It means a whole class of `raise` statements you don't write: don't re-validate
* The deciding question: *could a smarter model have avoided this?* Yes -> exception. No -> `MCPError`.
* `ResourceNotFoundError` from a resource handler -> the protocol's `-32602`, with the URI in `data`.
* Bad arguments are rejected against the schema before your function runs; you don't `raise` for those.
* `from mcp import MCPError`; the error-code constants come from `mcp_types`.
* `from mcp import MCPError`; the error-code constants come from `mcp.types`.
Errors handled. That is everything a server *exposes*. What every handler can read, and do back to the client while it runs, is the next section: **[Inside your handler](../handlers/index.md)**.

Some files were not shown because too many files have changed in this diff Show more