Log MCPServer handler exceptions by kind and keep crash details off t… · modelcontextprotocol/python-sdk@56af447 · GitHub
Skip to content

Commit 56af447

Browse files
maxisbeyKludex
andauthored
Log MCPServer handler exceptions by kind and keep crash details off the wire (#3314)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
1 parent f1c40b0 commit 56af447

45 files changed

Lines changed: 1427 additions & 310 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

‎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

0 commit comments

Comments
 (0)