Add mcp.types as a permanent alias for mcp_types (#3190)
This commit is contained in:
parent
923341c98a
commit
b7c9a916d6
172 changed files with 467 additions and 364 deletions
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue