docs: refresh translations for recent English changes (#3379)

This commit is contained in:
Max 2026-08-24 19:29:54 +01:00 • committed by GitHub
commit c5d7d0b6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,13 +1,13 @@
---
translation:
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
tool: 1
---
# 错误处理 {#handling-errors}
工具失败有两种方式,SDK 对它们的处理截然不同。
工具失败有三种方式,SDK 对每一种的处理都不一样。
抛出普通异常,看到它的是**模型**。抛出 `MCPError`,看到它的是**协议**。
抛出 `ToolError`,看到你消息的是**模型**。抛出 `MCPError`,看到它的是**协议**。抛出其他任何东西就是崩溃:模型只知道调用失败了,traceback 进你的日志。
这一页讲的就是怎么选。
@ -15,11 +15,11 @@ translation:
拿一个查东西的工具来说,让它查不到:
```python title="server.py" hl_lines="11-12"
```python title="server.py" hl_lines="2 12-13"
--8<-- "docs_src/handling_errors/tutorial001.py"
```
这两行没有任何 MCP 特有的东西。`get_author` 抛出一个普通的 `ValueError`,和任何 Python 函数一样。
`ToolError` 来自 `mcp.server.mcpserver.exceptions`,是工具告诉模型出了问题的方式。
用一个书目里没有的书名去调用它,看看结果:
@ -30,19 +30,21 @@ result.structured_content # None
```
* 请求**成功了**。有一个结果;调用方这边什么也没抛出。
* `is_error` 为 `True`,你的异常消息(前面加了工具名)就在 `content` 里,正是模型读取的位置。
* `is_error` 为 `True`,你的消息(前面加了工具名)就在 `content` 里,正是模型读取的位置。
* `structured_content` 为 `None`。失败的调用没有返回值可供结构化。
这就是**工具错误**,也是工具抛出的**任何**异常的默认归宿。而且它几乎总是你想要的效果。
这就是**工具错误**,而且它几乎总是你想要的效果。
调用工具的是模型,参数也是它挑的。所以工具错误就是对话里的一个回合:模型读到“No book titled 'Nothing' in the catalog.”,发现自己猜错了书名,就换个更好的再调一次。你只写了一个 `raise`,就得到了一个会自我纠正的智能体。
在服务器上,一个 `ToolError` 就是日志里的一行 `INFO`,没有 traceback。这是你预料之中的,所以没什么可查的。
!!! tip
永远不要从工具里 `return` 错误消息。返回的字符串带的是 `is_error=False`,所以在模型(以及每个客户端 UI)看来,工具运行正常,那个字符串就是答案。要 `raise`。这个标志才是信号。
## 模型纠正不了的错误 {#an-error-the-model-cannot-fix}
现在把 `ValueError` 换成 `MCPError`。
现在把 `ToolError` 换成 `MCPError`。
```python title="server.py" hl_lines="1 3 14"
--8<-- "docs_src/handling_errors/tutorial002.py"
@ -74,16 +76,35 @@ result.structured_content # None
两条路径回答的是两个不同的问题。
* **抛出任意异常**,对应**执行**层面的失败:工具想做的事没做成。调用是模型选的,所以后果也该让模型看到,给它补救的机会。拼错的书名、超时的上游 API、不存在的数据行:全是工具错误。
* **抛出 `ToolError`**,对应**执行**层面的失败:工具想做的事没做成。调用是模型选的,所以后果也该让模型看到,给它补救的机会。拼错的书名、超时的上游 API、不存在的数据行:全是工具错误。
* **抛出 `MCPError`**,对应**请求本身**就该被拒绝的情况:客户端缺少工具所依赖的某项能力,服务器当前的状态没法为任何人服务,调用方跳过了某个必需步骤。这些问题模型怎么重试都修不好,所以把消息交给它没有任何好处。
一个问题就能定夺:**换个更聪明的模型,能避免这个问题吗?** 能 -> 普通异常。不能 -> `MCPError`。
一个问题就能定夺:**换个更聪明的模型,能避免这个问题吗?** 能 -> `ToolError`。不能 -> `MCPError`。
按这个标准,第二版 `get_author` 选错了:换个更好的书名就能解决,所以模型理应看到那条消息。它放在这里是为了让你看清机制,而不是推荐这种写法。
!!! info
`MCPError` 通过 `from mcp import MCPError` 导入,接受 `code`、`message` 和可选的 `data` 载荷。你往里放什么,客户端就收到什么:SDK 会把抛出的 `MCPError` 原样转发,不做任何清理。
## 任何其他异常 {#any-other-exception}
现在把检查去掉,让字典查找自己失败:
```python title="server.py" hl_lines="11"
--8<-- "docs_src/handling_errors/tutorial004.py"
```
`CATALOG[title]` 抛出 `KeyError`。这不在你的计划之内,所以 SDK 把它当作崩溃:
```python
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author")]
```
调用仍然返回 `is_error=True`,所以模型知道它失败了,可以继续往下走。它拿不到的是异常的文本:你代码里的一个 `KeyError`,或者隔着三层库的某个驱动吐出的一堆 SQL,都可能暴露服务器的内部细节,所以它永远不会离开服务器。
拿到它的是你。服务器以 `ERROR` 级别记录这次崩溃,附带完整的 traceback,消息是 `Tool 'get_author' raised an unexpected exception`。所以一个设在 `WARNING` 级别的生产日志,遇到每个 `ToolError` 都保持安静,一旦真有东西坏了就会出声。
## 不存在的资源 {#a-resource-that-doesnt-exist}
资源也划出同样的界线,并为常见情况自带了一个具名异常。
@ -104,7 +125,7 @@ result.structured_content # None
}
```
注意这里没有 `is_error=True` 式的“半个结果”。资源读取要么返回内容,要么失败:资源只有协议这一条路径。模板以及资源的其他方方面面,详见 **[资源](resources.md)**。
注意这里没有 `is_error=True` 式的“半个结果”。资源读取要么返回内容,要么失败:资源只有协议这一条路径。`ResourceError` 是同样的东西,用于不属于“未找到”的失败(`-32603`,带你的消息),两者在日志里都是一行 `INFO`。除 `MCPError` 之外的任何其他异常都是崩溃:客户端收到只写明 URI 的 `-32603`,traceback 以 `ERROR` 级别进你的日志。模板以及资源的其他方方面面,详见 **[资源](resources.md)**。
## 你永远不用抛的错误 {#errors-you-never-raise}
@ -115,16 +136,17 @@ result.structured_content # None
这意味着有一整类 `raise` 语句不用你写:不要重复校验自己的类型注解。
!!! info
这一页上的一切都是**客户端**看到的样子,而你写测试时用的内存中的 `Client` 看到的也一模一样。就连 `raise_exceptions=True` 也不会把工具错误变回 traceback:等那个标志能起作用的时候,你的异常早已是 `is_error=True` 的结果了。对结果做断言。这个模式详见 **[测试](../get-started/testing.md)**。
这一页上**客户端**看到的一切,你写测试时用的内存中的 `Client` 看到的也一模一样。就连 `raise_exceptions=True` 也不会把失败工具的异常交还给调用方:等那个标志能起作用的时候,你的异常早已是 `is_error=True` 的结果了。对结果做断言。如果需要崩溃的 traceback,它在服务器的日志里,pytest 的 `caplog` 能捕获到。这个模式详见 **[测试](../get-started/testing.md)**。
## 回顾 {#recap}
* 在工具里抛出**任意异常** -> 调用返回 `is_error=True`,你的消息在 `content` 里。模型读到后可以重试。这是默认行为。
* 在工具里抛出 **`ToolError`** -> 调用返回 `is_error=True`,你的消息在 `content` 里。模型读到后可以重试。
* 抛出 **`MCPError`** -> 调用本身以 JSON-RPC 错误失败。模型什么也看不到;由宿主处理。`code`、`message` 和 `data` 原封不动地保留。
* 决定性的问题:“换个更聪明的模型,能避免这个问题吗?”能 -> 异常。不能 -> `MCPError`。
* 决定性的问题:“换个更聪明的模型,能避免这个问题吗?”能 -> `ToolError`。不能 -> `MCPError`。
* 任何**其他异常**都是崩溃 -> `is_error=True`,模型只看到 `Error executing tool <name>`,你拿到一条带 traceback 的 `ERROR` 记录。
* 资源处理函数抛出 `ResourceNotFoundError` -> 协议的 `-32602`,URI 在 `data` 里。
* 不合法的参数在你的函数运行之前就会对照模式被拒掉;这些不用你 `raise`。
* `from mcp import MCPError`;错误码常量来自 `mcp.types`。
* 导入:`from mcp import MCPError`、`from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`,以及来自 `mcp.types` 的错误码常量。
错误处理完毕。服务器**对外暴露**的内容就是这些。每个处理函数在运行期间能读到什么、又能反过来对客户端做什么,是下一部分的内容:**[在处理函数内部](../handlers/index.md)**。