Skip to content

Commit ae6caf0

Browse files
authored
Merge branch 'main' into fix-value-error-logs
2 parents ae720ad + 56af447 commit ae6caf0

48 files changed

Lines changed: 1456 additions & 312 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/client/index.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ That schema is everything a UI needs to render an argument form, and everything
8181

8282
`call_tool(name, arguments)` runs the tool and gives you back a `CallToolResult`.
8383

84-
```python title="client.py" hl_lines="26-33"
84+
```python title="client.py" hl_lines="27-34"
8585
--8<-- "docs_src/client/tutorial003.py"
8686
```
8787

@@ -113,17 +113,18 @@ A tool that raises does **not** raise in your client. It comes back as an ordina
113113

114114
!!! check
115115
Ask `lookup_book` for `"Solaris"` (a title that isn't in the catalog) and the function raises
116-
`ValueError`. The call still returns normally:
116+
`ToolError`. The call still returns normally:
117117

118118
```python
119119
result.is_error # True
120120
result.content # [TextContent(type='text', text="Error executing tool lookup_book: No book titled 'Solaris' in the catalog.")]
121121
result.structured_content # None
122122
```
123123

124-
The exception's message landed in `content`, where the **model** can read it and try again. That
125-
is deliberate: a tool error is part of the conversation, not a crash. Always look at `is_error`
126-
before you trust `structured_content`.
124+
The `ToolError`'s message landed in `content`, where the **model** can read it and try again. That
125+
is deliberate: a tool error is part of the conversation, not a crash. (Had the tool crashed with
126+
some other exception, `content` would say only `Error executing tool lookup_book`.) Always look at
127+
`is_error` before you trust `structured_content`.
127128

128129
!!! warning
129130
`is_error=True` covers more than your own `raise`. Ask for a tool the server doesn't even have

docs/deprecated.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Deprecated features
22

3-
The 2026-07-28 spec retires five things. The SDK still implements every one of them, and every one of them now carries a **deprecation warning**.
3+
The 2026-07-28 spec retires five things. The SDK still implements every one of them, and every one of them now carries a **deprecation warning**. One SDK helper is deprecated on its own account and is listed [at the end](#deprecated-sdk-helpers).
44

55
The table below names each deprecated feature, why it is going away, and the replacement to build on.
66

@@ -119,22 +119,32 @@ That is the whole API. There is no per-method switch, and you don't want one: th
119119
Run the filter the other way and you get a free regression test. Add
120120
`"error::mcp.MCPDeprecationWarning"` to the `filterwarnings` setting in your pytest
121121
configuration and the deprecated call **raises** instead of warning. A tool named
122-
`old_log` that still calls `ctx.info()` stops passing and starts reporting:
122+
`old_log` that still calls `ctx.info()` stops passing: the call comes back `is_error=True` with
123+
`Error executing tool old_log`, and the captured server log names the culprit:
123124

124125
```text
125-
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
126+
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
126127
```
127128

128129
One line of pytest configuration, and a deprecated call can never sneak back into your
129130
codebase without failing a test.
130131

132+
## Deprecated SDK helpers
133+
134+
These are not spec changes, only SDK internals with a better replacement. They warn with the same `MCPDeprecationWarning` and will be removed in 3.0.
135+
136+
| Deprecated | What you do instead |
137+
|---|---|
138+
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` and then `FuncMetadata.call_fn()`. Only code that drives `FuncMetadata` directly (a custom `Tool` subclass, say) ever called it. |
139+
131140
## Recap
132141

133142
* The 2026-07-28 spec deprecates **roots**, server-initiated **sampling**, and protocol **logging** (all [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restricts **progress** to server-to-client, and removes **`ping`**.
134143
* The replacement column points you onward: **[Multi-round-trip requests](handlers/multi-round-trip.md)** for sampling and roots, **[Logging](handlers/logging.md)** for logging, **[Progress](handlers/progress.md)** for progress. `ping` needs nothing at all.
135144
* Deprecated is advisory: no wire changes, everything keeps working against pre-2026 sessions, and you get a visible `MCPDeprecationWarning` (a `UserWarning`, so it is on by default).
136145
* Sampling and roots additionally need a back-channel that a 2026-07-28 session does not have. On a modern connection they warn and then they raise.
137146
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` silences the whole category; `"error::mcp.MCPDeprecationWarning"` in pytest turns it into a test failure.
147+
* One SDK helper, `FuncMetadata.call_fn_with_arg_validation()`, is deprecated separately for removal in 3.0.
138148
* New code should not be built on any of these.
139149

140150
Every other page in these docs teaches the current API.

docs/get-started/real-host.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Which means connecting to a host is one act: you tell it **the command that star
66

77
## One server, every host
88

9-
```python title="server.py" hl_lines="3 33-34"
9+
```python title="server.py" hl_lines="4 34-35"
1010
--8<-- "docs_src/real_host/tutorial001.py"
1111
```
1212

docs/get-started/testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ There you go! You can now extend your tests to cover more scenarios.
7979
Two different things can go wrong, and this flag only touches one of them.
8080

8181
An exception inside one of **your tools** is not a protocol failure. It becomes a normal result with
82-
`is_error=True`, and the model reads the message. `raise_exceptions` doesn't change that: with or
83-
without it, `call_tool` returns the same `is_error=True` result. There's a whole page on it:
82+
`is_error=True` (and if it was a `ToolError`, the model reads your message). `raise_exceptions` doesn't
83+
change that: with or without it, `call_tool` returns the same `is_error=True` result. There's a whole page on it:
8484
**[Handling errors](../servers/handling-errors.md)**.
8585

8686
A failure **outside** a tool body is different. On the connection `Client(mcp)` gives you, the

docs/handlers/elicitation.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ That schema is the form. `Field(description=...)` is the label; a default pre-fi
8484
!!! warning
8585
An elicitation schema is not as expressive as a tool's input schema. Flat, primitive fields
8686
only: `str`, `int`, `float`, `bool`, or a `Literal` of strings (it becomes an `enum`).
87-
Put a model inside the model and `ctx.elicit` raises before anything is sent to the client:
87+
Put a model inside the model and `ctx.elicit` raises before anything is sent to the client.
88+
The tool call fails with `Error executing tool <name>`, and your server log has the reason:
8889

8990
```text
9091
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
@@ -107,8 +108,8 @@ A refusal is not an error. The tool decides what declining means (here, no booki
107108

108109
!!! tip
109110
The answer is validated against your model before your code sees it. A client that sends
110-
`"maybe"` for a `bool` doesn't corrupt your booking: the call fails with a
111-
schema-mismatch error, your `if` never runs.
111+
`"maybe"` for a `bool` doesn't corrupt your booking: `ctx.elicit` raises `ValueError`, the call
112+
fails, and your `if` never runs.
112113

113114
## Send the user to a URL
114115

docs/handlers/logging.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ The default is `"INFO"`.
4949

5050
`logging.basicConfig()` never replaces handlers that already exist. If you configure logging yourself before creating the server, your configuration wins.
5151

52+
You also don't need a `try`/`except` in every handler just to record failures. When a tool or resource function raises, the SDK logs it for you. **[Handling errors](../servers/handling-errors.md#any-other-exception)** explains what gets logged and at which level.
53+
5254
## Try it
5355

5456
Run the server with the MCP Inspector:

docs/migration.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -992,8 +992,8 @@ its behavior is unchanged.
992992
`MCPError` carries `ErrorData` and is the SDK's protocol-error type — raise it
993993
when the request itself should be rejected (missing client capability,
994994
elicitation required, invalid parameters). For tool *execution* failures the
995-
calling LLM should see and react to, raise any other exception or return
996-
`CallToolResult(is_error=True, ...)` directly; that path is unchanged.
995+
calling LLM should see and react to, raise `ToolError` or return
996+
`CallToolResult(is_error=True, ...)` directly.
997997

998998
The client sees this change too. `Client.call_tool()` and
999999
`ClientSession.call_tool()` raise on a JSON-RPC error response, so a tool that
@@ -1016,7 +1016,7 @@ except MCPError as e:
10161016

10171017
### Resource not found returns `-32602` and resource lookups raise typed exceptions (SEP-2164)
10181018

1019-
Reading a missing resource now returns JSON-RPC error code `-32602` (invalid params) with the requested URI in `error.data` (`{"uri": ...}`), per [SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164). Previously the server returned code `0` with no `data`. Clients can now reliably distinguish not-found from other errors; a template handler that raises `ResourceNotFoundError` (from `mcp.server.mcpserver.exceptions`) produces this same response.
1019+
Reading a missing resource now returns JSON-RPC error code `-32602` (invalid params) with the requested URI in `error.data` (`{"uri": ...}`), per [SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164). Previously the server returned code `0` with no `data`. Clients can now reliably distinguish not-found from other errors; a resource handler (static or template) that raises `ResourceNotFoundError` (from `mcp.server.mcpserver.exceptions`) produces this same response.
10201020

10211021
The underlying lookups now raise typed exceptions instead of `ValueError`. `ResourceManager.get_resource()` raises `ResourceNotFoundError` when no resource or template matches the URI, and `ResourceTemplate.create_resource()` raises `ResourceError` when the template function fails. Neither subclasses `ValueError`, so callers catching `ValueError` should switch to `ResourceNotFoundError` / `ResourceError` (both importable from `mcp.server.mcpserver.exceptions`; `ResourceNotFoundError` subclasses `ResourceError`).
10221022

@@ -2737,7 +2737,7 @@ One behavioral caveat when moving progress-reporting handlers onto `Client(serve
27372737

27382738
Every deprecation below is a runtime warning as well as a type-checker one: deprecated methods and helpers emit `mcp.MCPDeprecationWarning` on each call, and the deprecated `Server(...)` constructor parameters (`on_set_logging_level`, `on_roots_list_changed`, `on_progress`) emit it at construction time. The category subclasses `UserWarning`, not `DeprecationWarning`, so it is visible by default; [Deprecated features](deprecated.md) has the full list and each replacement.
27392739

2740-
Under pytest's `filterwarnings = ["error"]`, that warning becomes an exception at the first deprecated call. Inside an `@mcp.tool()` handler the exception is caught like any other and returned as `CallToolResult(is_error=True)` (`Error executing tool ...: The logging capability is deprecated as of 2026-07-28 (SEP-2577).`), which reads as a failing tool rather than a warning. Keep the warnings visible but non-fatal with:
2740+
Under pytest's `filterwarnings = ["error"]`, that warning becomes an exception at the first deprecated call. Inside an `@mcp.tool()` handler the exception is caught like any other and returned as `CallToolResult(is_error=True)` (`Error executing tool ...`, with the `MCPDeprecationWarning` traceback in the server log), which reads as a failing tool rather than a warning. Keep the warnings visible but non-fatal with:
27412741

27422742
```toml
27432743
[tool.pytest.ini_options]

0 commit comments

Comments
 (0)