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
18 changed files with 3149 additions and 1047 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# 低階 Server {#the-low-level-server}
@ -116,6 +116,17 @@ asyncio.run(main())
伺服器從不比對這兩個欄位。這個 SDK 的 `Client` 會:回傳的 `structured_content` 如果不符合你宣告的 `output_schema`,`call_tool` 就會引發 `RuntimeError`,訊息以 `Invalid structured content returned by tool search_books` 開頭,接著引用 `jsonschema` 的失敗內容。承諾一個 schema 很便宜;守住承諾是你的事。回傳型別與 schema 的完整階梯請見 **[結構化輸出](../servers/structured-output.md)**。
## 方言是 JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
`input_schema` 和 `output_schema` 是 JSON Schema,而 [MCP 規範](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage)把方言定了下來:沒有 `$schema` 鍵的 schema 就是 **JSON Schema 2020-12**。`MCPServer` 產生的 schema 依賴這個預設值(Pydantic 寫的是 2020-12,並省略這個鍵),手寫的 dict 也同樣受它約束,所以整套 2020-12 詞彙都可以用:
```python title="server.py" hl_lines="8 14-15"
--8<-- "docs_src/lowlevel/tutorial007.py"
```
* `input_schema` 的根必須是 `"type": "object"`。在它旁邊,`oneOf`、`additionalProperties`、`anyOf`、`if`/`then`/`else`、`prefixItems`、帶本地 `$ref` 的 `$defs`,以及其餘的 2020-12 關鍵字,都會照你寫的一字不差地送到用戶端。
* 不需要 `$schema` 鍵。只有想改用較舊的草案時才加:這個 SDK 的 `Client` 會依工具的 `output_schema` 驗證 `structured_content`,它從 `$schema` 挑選驗證器,沒有的話就用 2020-12。
## `_meta`:給應用程式,不是給模型 {#\_meta-for-the-application-not-the-model}
`content` 是答案中模型會讀的部分。`structured_content` 是同一個答案的型別化資料。`_meta` 是第三個管道:跟著結果一起送給**用戶端應用程式**的資料,完全不屬於答案的一部分。
@ -166,7 +177,7 @@ asyncio.run(main())
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* 第一個引數是方法字串。通知有個孿生的 `add_notification_handler`。
* 第一個引數是方法字串。通知有個孿生的 `add_notification_handler`。它的處理函式在 stdio 和交握世代的 HTTP 連線上會觸發;在 `2026-07-28` 的 Streamable HTTP 路徑上,用戶端的通知 POST 會收到 `202` 確認而不會被分派,因為該修訂版沒有定義任何透過 HTTP 的用戶端到伺服器通知。
* `params_type` 是傳入的 `params` 在處理函式執行**之前**用來驗證的模型,所以自訂方法**確實**享有工具沒有的驗證。繼承 `RequestParams`,讓 `_meta` 欄位和其他方法一樣解析。
* 處理函式回傳 `BaseModel`、`dict` 或 `None`。SDK 會把它序列化成 JSON-RPC 結果。
@ -179,7 +190,7 @@ ValueError: 'initialize' is handled by the server runner and cannot be overridde
use Server.middleware to observe or wrap initialization
```
交握屬於執行器。`server/discover`、`ping`,以及其他所有內建方法,都可以替換。
交握屬於執行器。`server/discover`、`ping`,以及其他所有內建方法,都可以由你替換。
!!! tip
那則錯誤裡提到的 `Server.middleware` 會包住**每一則**傳入訊息,包括 `initialize`。如果想做的是觀察或改寫流量,而不是回應新方法,請從 **[中介軟體](middleware.md)** 開始。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# 中介軟體 {#middleware}
@ -42,7 +42,7 @@ tools/call took 0.1 ms
重點就在這裡。中介軟體包住**每一則**傳入的訊息:
* 連線建立階段:`server/discover`,或在舊版工作階段(session)上的 `initialize` 和 `notifications/initialized`。
* 每一個請求和每一則通知。對通知而言,`ctx.request_id is None`,`call_next(ctx)` 回傳 `None`,而你回傳的任何東西都會被丟棄。
* 每一個抵達伺服器的請求和每一則通知。對通知而言,`ctx.request_id is None`,`call_next(ctx)` 回傳 `None`,而你回傳的任何東西都會被丟棄。(在 `2026-07-28` 的 Streamable HTTP 路徑上,用戶端以 POST 送出的通知會在傳輸層直接以 `202` 確認收到、從不分派,所以也不會抵達中介軟體;該修訂版沒有定義任何透過 HTTP 由用戶端送往伺服器的通知。)
* 連伺服器沒有處理函式的方法也一樣:`call_next` 會引發 `MCPError(-32601, "Method not found")`,**穿過**你的中介軟體一路送到用戶端。
## 在裡面能做什麼 {#what-you-can-do-inside-one}
@ -75,7 +75,7 @@ SDK 只附帶一個中介軟體,而且它已經在伺服器的清單上了:
## 重點回顧 {#recap}
* 中介軟體是 `async (ctx, call_next) -> result`,以 `MCPServer(middleware=[...])` 傳入(或附加到 `mcp.middleware`),在低階的 `Server` 上則附加到 `server.middleware`。
* 它包住**每一則**傳入的訊息(`server/discover`、`initialize`、請求、通知、未知的方法),並由最外層開始執行。
* 它包住**每一則**抵達伺服器的傳入訊息(`server/discover`、`initialize`、請求、通知、未知的方法),並由最外層開始執行。
* 用 `ctx.request_id is None` 區分通知和請求。
* 不呼叫 `call_next` 改為引發例外,就能拒絕一則訊息;連線會存活下來。
* SDK 自己的 OpenTelemetry 追蹤也是一個中介軟體,已經在清單上。請見 **[OpenTelemetry](../run/opentelemetry.md)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
tool: 1
---
# 用戶端 {#the-client}
@ -27,9 +27,10 @@ Python 程式要和 MCP 伺服器對話,靠的就是 **`Client`**。
* `MCPServer`(或低階的 `Server`)實例:在**同一個處理程序內**連線。
* URL 字串(`Client("http://localhost:8000/mcp")`):Streamable HTTP,也就是正式環境的路徑。
* 一個**傳輸**:任何可以 `async with ... as (read, write)` 的東西,例如包住子處理程序的 `stdio_client(...)`。
* `StdioServerParameters`:要當作**子處理程序**啟動的命令,透過它的 stdin 和 stdout 溝通。
* 一個**傳輸**:任何可以 `async with ... as (read, write)` 的東西,例如用 `streamable_http_client(url, http_client=...)` 包住你自己的 HTTP 用戶端。
這一頁其餘的內容在這三種情況下完全相同。標頭、子處理程序、逾時,以及 `Transport` 協定另外有專屬的頁面:**[用戶端傳輸方式](transports.md)**。
這一頁其餘的內容在這四種情況下完全相同。標頭、子處理程序、逾時,以及 `Transport` 協定另外有專屬的頁面:**[用戶端傳輸方式](transports.md)**。
### 連線後的用戶端上有什麼 {#whats-on-a-connected-client}
@ -82,7 +83,7 @@ tool.description # 'Search the catalog by title or author.'
`call_tool(name, arguments)` 會執行工具,並回傳一個 `CallToolResult`。
```python title="client.py" hl_lines="26-33"
```python title="client.py" hl_lines="27-34"
--8<-- "docs_src/client/tutorial003.py"
```
@ -113,7 +114,7 @@ result.is_error # False
會引發例外的工具,在用戶端這邊**不會**引發例外。它會以一個普通的結果回來,帶著 `is_error=True`。
!!! check
向 `lookup_book` 要 `"Solaris"`(目錄裡沒有的書名),函式會引發 `ValueError`。呼叫仍然正常回傳:
向 `lookup_book` 要 `"Solaris"`(目錄裡沒有的書名),函式會引發 `ToolError`。呼叫仍然正常回傳:
```python
result.is_error # True
@ -121,7 +122,7 @@ result.is_error # False
result.structured_content # None
```
例外的訊息落在 `content` 裡,**模型**可以讀到它並再試一次。這是刻意的設計:工具錯誤是對話的一部分,不是當機。在信任 `structured_content` 之前,一定要先看 `is_error`。
`ToolError` 的訊息落在 `content` 裡,**模型**可以讀到它並再試一次。這是刻意的設計:工具錯誤是對話的一部分,不是當機。(假如工具是因為其他例外而當掉,`content` 就只會寫 `Error executing tool lookup_book`。)在信任 `structured_content` 之前,一定要先看 `is_error`。
!!! warning
`is_error=True` 涵蓋的不只是你自己的 `raise`。要一個伺服器根本沒有的工具(`call_tool("does_not_exist", {})`),也不會引發任何例外。你會拿回同樣的形狀:`is_error=True`,`content` 裡是 `Unknown tool: does_not_exist`。只有在伺服器回的是 JSON-RPC **錯誤**而不是結果時,`Client` 的方法才會引發 `MCPError`;伺服器什麼時候產生哪一種,請見 **[處理錯誤](../servers/handling-errors.md)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
tool: 1
---
# 用戶端傳輸方式 {#client-transports}
@ -76,15 +76,15 @@ translation:
**stdio** 伺服器是一個子處理程序。用戶端啟動它,把 JSON-RPC 寫進它的 stdin,再從它的 stdout 讀取 JSON-RPC。桌面版 MCP 主機(host)就是這樣在你的機器上執行伺服器的:主機**就是**這段程式碼加上一個 UI,而 **[連接到真正的主機](../get-started/real-host.md)** 則是從主機那一側、以設定檔的形式看同一個關係。
用 `StdioServerParameters` 描述這個處理程序,用 `stdio_client` 把它變成傳輸,再把**那個**交給 `Client`:
用 `StdioServerParameters` 描述這個處理程序,再把它交給 `Client`:
```python title="client.py" hl_lines="4-8 12"
```python title="client.py" hl_lines="3-7 11"
--8<-- "docs_src/client_transports/tutorial004.py"
```
`Client` 不接受單獨的參數物件。`StdioServerParameters` 是設定;`stdio_client(server)` 才是知道怎麼依據它啟動處理程序的傳輸。一定要包起來。
進入區塊時會啟動處理程序。離開區塊時,子處理程序也會一併關閉:關掉 stdin、等待、拖太久就強制終止。你永遠不需要自己清理。
離開 `async with` 區塊時,子處理程序也會一併關閉:關掉 stdin、等待、拖太久就強制終止。你永遠不需要自己清理。
子處理程序的 stderr 會接到你的 stderr。想送到別的地方,就自己用 `stdio_client`(來自 `mcp`)建立傳輸,改傳入那個:`Client(stdio_client(server, errlog=log_file))`。
!!! warning
子處理程序**不會**繼承你的環境。它拿到的是一份精簡的允許清單(POSIX 上是 `HOME`、`LOGNAME`、`PATH`、`SHELL`、`TERM` 和 `USER`),這樣敏感的東西才不會洩漏到一個可能不是你寫的處理程序裡。
@ -99,16 +99,16 @@ translation:
對 `Client` 來說,上面這些全都是同一種東西。
**傳輸**是任何會產出一對 `(read, write)` 訊息串流的非同步 context manager:正式地說,就是 `mcp.client` 裡的 `Transport` 協定。`Client` 依型別解析它的引數:伺服器物件就在處理程序內連線,`str` 會變成 `streamable_http_client(url)`,其他任何東西則直接當成傳輸進入。最後這條規則就是為什麼 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放進同一個位置,也是為什麼你可以自己寫一個。
**傳輸**是任何會產出一對 `(read, write)` 訊息串流的非同步 context manager:正式地說,就是 `mcp.client` 裡的 `Transport` 協定。`Client` 依型別解析它的引數:伺服器物件就在處理程序內連線,`str` 會變成 `streamable_http_client(url)`,`StdioServerParameters` 會變成 `stdio_client(params)`,其他任何東西則直接當成傳輸進入。最後這條規則就是為什麼 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放進同一個位置,也是為什麼你可以自己寫一個。
## 重點回顧 {#recap}
* `Client(mcp)`(伺服器物件)在記憶體內連線。用在測試和嵌入。
* `Client("http://.../mcp")`(URL)透過 Streamable HTTP 連線,也就是正式環境用的傳輸方式。
* 標頭、驗證、proxy 和逾時都放在你傳給 `streamable_http_client(url, http_client=...)` 的 `httpx2.AsyncClient` 上。沒有 `headers=` 這個關鍵字引數。
* stdio 是 `Client(stdio_client(StdioServerParameters(...)))`,絕對不是單獨的參數物件。
* stdio 是 `Client(StdioServerParameters(...))`。只有要把子處理程序的 stderr 導到別處時,才需要自己用 `stdio_client(...)` 包起來。
* 子處理程序拿到的是允許清單上的環境,不是你的環境;`env=` 會往上加。
* 傳輸就是任何可以 `async with x as (read, write)` 的東西。只要不是伺服器物件或 URL,`Client` 就會直接交給那個協定處理。
* 傳輸就是任何可以 `async with x as (read, write)` 的東西。只要不是伺服器物件、URL 或 `StdioServerParameters`,`Client` 就會直接交給那個協定處理。
* 建立 `Client` 是選定傳輸方式。`async with` 才是開啟它。
傳輸開啟之後,兩邊得對協定版本達成一致。平常根本不需要去想這件事;真的需要的時候,請看 **[協定版本](../protocol-versions.md)**。

View file

@ -1,11 +1,11 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
tool: 1
---
# 已棄用的功能 {#deprecated-features}
2026-07-28 規格讓五樣東西退場。SDK 仍然實作了其中每一項,而每一項現在都帶有**棄用警告**。
2026-07-28 規格讓五樣東西退場。SDK 仍然實作了其中每一項,而每一項現在都帶有**棄用警告**。另外有一個 SDK 輔助函式是因為自身的原因棄用,列在[最後](#deprecated-sdk-helpers)。
下表列出每一項已棄用的功能、它為什麼要退場,以及應該改用的替代做法。
@ -49,6 +49,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
兩個訊號,依這個順序出現。`MCPDeprecationWarning` 在呼叫方法的那一刻就會發出,任何連線都一樣。錯誤則是 SDK 接著嘗試傳送時回傳來的東西。這兩者只有在用戶端註冊了對應回呼的 `mode="legacy"` 連線上,才能從頭到尾正常運作。
## 舊版工作階段上的 `ping` {#ping-on-a-legacy-session}
**ping** 是一個空的請求,任何一方都可以送出,用來確認對方還有回應。2026-07-28 規格移除了它([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)):現代用戶端送出的每個請求本身就已經證明伺服器還在,而現代伺服器也沒有通道可以送出 ping。兩個 SDK 方法在交握世代的工作階段上仍然有效。從用戶端:
```python
async def main() -> None:
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
await client.send_ping() # warns; returns an EmptyResult
```
從伺服器端,在任何處理函式內:
```python
@mcp.tool()
async def check_client(ctx: Context) -> str:
"""A tool that still pings the client mid-call."""
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
return "client answered"
```
* `client.send_ping()` 每次呼叫都會發出 `MCPDeprecationWarning`。在預設(`2026-07-28`)的連線上,伺服器則改為回應 `MCPError: Method not found`。
* `ctx.session.send_ping()` 不帶警告。在現代連線上,它會和其他任何伺服器發起的請求一樣,引發沒有反向通道(back-channel)的錯誤。
* 兩邊都不需要註冊任何東西來回應 ping。
## 根目錄變更通知 {#roots-change-notifications}
宣告了根目錄能力的 2025 世代用戶端,可以送出 `notifications/roots/list_changed` 告訴伺服器它的工作區資料夾變了;伺服器的回應是再次請求 `roots/list`。2026-07-28 規格把這個通知連同其餘推送式的根目錄流程一起移除。在用戶端,傳入 `list_roots_callback=`(**[用戶端回呼](client/callbacks.md)**)就是宣告 `"roots": {"listChanged": true}` 的那一步,而兌現這個承諾只需要一次呼叫:
```python
async def open_folder(client: Client, uri: str, name: str) -> None:
"""The user opened another folder: expose it through the roots callback, then tell the server."""
workspace.append(Root(uri=FileUrl(uri), name=name))
await client.send_roots_list_changed()
```
在伺服器端,接收端的處理函式由低階 `Server` 接手:
```python
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
"""The client's roots changed: ask for the new list."""
roots = (await ctx.session.list_roots()).roots
server = Server("Bookshop", on_roots_list_changed=roots_changed)
```
* `workspace` 是 `list_roots_callback` 回傳的那個清單。`client.send_roots_list_changed()` 會發出警告,而且需要 `mode="legacy"` 的用戶端:在現代連線上,這個通知會被默默丟棄。之後要讓工作階段保持開啟,因為伺服器後續的 `roots/list` 會從這條工作階段送來。
* `MCPServer` 沒有對應這個通知的掛鉤。在低階 `Server` 上,`on_roots_list_changed=` 用來註冊處理函式(同樣已棄用,建構時就會發出警告)。這個通知不帶任何酬載,所以處理函式要呼叫 `ctx.session.list_roots()` 取得新清單。
## 讓警告靜音 {#silencing-the-warning}
新程式碼裡,不要這麼做。
@ -66,21 +115,30 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning)
整個 API 就這樣。沒有逐方法的開關,你也不會想要:只用一個類別的意義在於,一行就能關掉它,一行就能把它叫回來。
!!! check
把過濾器反過來用,就免費得到一個回歸測試。在 pytest 設定的 `filterwarnings` 裡加上 `"error::mcp.MCPDeprecationWarning"`,已棄用的呼叫就會**引發例外**而不是發出警告。一個名為 `old_log`、還在呼叫 `ctx.info()` 的工具會不再通過,開始回報:
把過濾器反過來用,就免費得到一個回歸測試。在 pytest 設定的 `filterwarnings` 裡加上 `"error::mcp.MCPDeprecationWarning"`,已棄用的呼叫就會**引發例外**而不是發出警告。一個名為 `old_log`、還在呼叫 `ctx.info()` 的工具會不再通過:呼叫回來時是 `is_error=True`,帶著 `Error executing tool old_log`,而擷取到的伺服器記錄會點名元凶:
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
一行 pytest 設定,已棄用的呼叫就再也沒辦法在不讓測試失敗的情況下溜回程式碼庫。
## 已棄用的 SDK 輔助函式 {#deprecated-sdk-helpers}
這些不是規格變更,只是有了更好替代做法的 SDK 內部實作。它們用同樣的 `MCPDeprecationWarning` 發出警告,並會在 3.0 移除。
| 已棄用項目 | 替代做法 |
|---|---|
| `FuncMetadata.call_fn_with_arg_validation()` | 先 `FuncMetadata.validate_arguments()`,再 `FuncMetadata.call_fn()`。只有直接操作 `FuncMetadata` 的程式碼(例如自訂的 `Tool` 子類別)才會呼叫過它。 |
## 重點回顧 {#recap}
* 2026-07-28 規格棄用了**根目錄**、伺服器發起的**取樣**和協定**記錄**(全部來自 [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)),把**進度**限制為只能從伺服器到用戶端,並移除了 **`ping`**。
* 替代做法那一欄指引你接下來往哪走:取樣和根目錄看 **[多輪往返請求](handlers/multi-round-trip.md)**,記錄看 **[記錄](handlers/logging.md)**,進度看 **[進度](handlers/progress.md)**。`ping` 什麼都不需要。
* 棄用只是勸告性質:線路沒有變更,一切在 2026 之前的工作階段上都能繼續運作,而且你會看到明顯的 `MCPDeprecationWarning`(它是 `UserWarning`,所以預設就會顯示)。
* 取樣和根目錄還額外需要一條反向通道(back-channel),而 2026-07-28 的工作階段沒有。在現代連線上,它們會先警告,再引發例外。
* 取樣和根目錄還額外需要一條反向通道,而 2026-07-28 的工作階段沒有。在現代連線上,它們會先警告,再引發例外。
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` 會讓整個類別靜音;pytest 裡的 `"error::mcp.MCPDeprecationWarning"` 則把它變成測試失敗。
* 有一個 SDK 輔助函式 `FuncMetadata.call_fn_with_arg_validation()` 另外單獨棄用,預計在 3.0 移除。
* 新程式碼不應該建立在這些東西之上。
這份說明文件的其他每一頁教的都是目前的 API。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# 連接到真正的主機 {#connect-to-a-real-host}
@ -11,7 +11,7 @@ translation:
## 一個伺服器,所有主機 {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
```python title="server.py" hl_lines="4 34-35"
--8<-- "docs_src/real_host/tutorial001.py"
```
@ -41,7 +41,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
!!! note "這一頁講的是本機情境"
這裡的一切都是在主機所在的那台機器上執行伺服器:主機透過 stdio 啟動你的檔案。對個人用或單機工具來說,這完全正確。要把伺服器交給**沒有**你這個檔案的人,給出去的是 **URL** 而不是指令:同一個 `mcp` 物件,改用 Streamable HTTP 提供服務。**[執行伺服器](../run/index.md)** 用一張表講清楚這個抉擇,**[部署與擴展](../run/deploy.md)** 則是從那裡走到真正主機名稱的路。
而主機不過就是內含 MCP 用戶端的應用程式,所以你自己的 Python 也能扮演主機的角色:**[用戶端傳輸方式](../client/transports.md)** 用 `stdio_client(...)` 把同一個檔案當成子處理程序啟動,**[測試](testing.md)** 則在記憶體內連接它,完全不需要處理程序。
而主機不過就是內含 MCP 用戶端的應用程式,所以你自己的 Python 也能扮演主機的角色:**[用戶端傳輸方式](../client/transports.md)** 用 `Client(StdioServerParameters(...))` 把同一個檔案當成子處理程序啟動,**[測試](testing.md)** 則在記憶體內連接它,完全不需要處理程序。
## Claude Desktop {#claude-desktop}

View file

@ -1,6 +1,6 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
tool: 1
---
# 測試 {#testing}
@ -80,7 +80,7 @@ async def test_call_add_tool(client: Client):
可能出錯的地方有兩種,而這個旗標只影響其中一種。
**你的工具**內部引發的例外不算協定失敗。它會變成一個帶有 `is_error=True` 的正常結果,模型會讀到那則訊息。`raise_exceptions` 不會改變這一點:不管有沒有設定,`call_tool` 都回傳同樣的 `is_error=True` 結果。這部分有一整頁的說明:**[處理錯誤](../servers/handling-errors.md)**。
**你的工具**內部引發的例外不算協定失敗。它會變成一個帶有 `is_error=True` 的正常結果(如果是 `ToolError`,模型會讀到你寫的訊息)。`raise_exceptions` 不會改變這一點:不管有沒有設定,`call_tool` 都回傳同樣的 `is_error=True` 結果。這部分有一整頁的說明:**[處理錯誤](../servers/handling-errors.md)**。
發生在工具本體**之外**的失敗就不一樣了。在 `Client(mcp)` 給你的連線上,伺服器會先把它淨化成通用的 `"Internal server error"`,用戶端才看得到。意外當掉的細節本來就不該洩漏給遠端呼叫端。但在測試裡,這正是你**不**想要的,也正是 `raise_exceptions=True` 改變的地方:測試會看到真正的訊息,而不是淨化過的版本。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
tool: 1
---
# 徵詢 {#elicitation}
@ -83,7 +83,7 @@ translation:
那份 schema 就是表單。`Field(description=...)` 是標籤;預設值會預先填入輸入框,並讓該欄位變成選填。這和**[工具](../servers/tools.md)**頁面描述工具引數時用的是同一套 Pydantic 轉 JSON Schema 機制。
!!! warning
徵詢用的 schema 表達能力不如工具的輸入 schema。只能用扁平的基本型別欄位:`str`、`int`、`float`、`bool`,或是字串組成的 `Literal`(會變成 `enum`)。如果在模型裡再放一個模型,`ctx.elicit` 會在送出任何東西給用戶端之前就引發例外:
徵詢用的 schema 表達能力不如工具的輸入 schema。只能用扁平的基本型別欄位:`str`、`int`、`float`、`bool`,或是字串組成的 `Literal`(會變成 `enum`)。如果在模型裡再放一個模型,`ctx.elicit` 會在送出任何東西給用戶端之前就引發例外。工具呼叫會以 `Error executing tool <name>` 失敗,原因則在伺服器記錄裡:
```text
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
@ -104,7 +104,7 @@ translation:
拒絕不是錯誤。拒絕代表什麼由工具決定(這裡是不訂位),然後照常回答模型。
!!! tip
回答在你的程式碼看到之前,就會先依照模型驗證。用戶端若在 `bool` 欄位送來 `"maybe"`,也不會弄壞你的訂位:呼叫會以 schema 不符的錯誤失敗,你的 `if` 根本不會執行。
回答在你的程式碼看到之前,就會先依照模型驗證。用戶端若在 `bool` 欄位送來 `"maybe"`,也不會弄壞你的訂位:`ctx.elicit` 會引發 `ValueError`,呼叫失敗,你的 `if` 根本不會執行。
## 把使用者送往一個 URL {#send-the-user-to-a-url}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
tool: 1
---
# 記錄 {#logging}
@ -49,6 +49,8 @@ MCP 有一個協定層級的 **logging 能力**:伺服器可以透過 `Context
`logging.basicConfig()` 永遠不會取代已經存在的 handler。如果在建立伺服器之前就自己設定好記錄,以你的設定為準。
也不需要只為了記下失敗,就在每個處理函式裡包一層 `try`/`except`。工具或資源函式引發例外時,SDK 會替你記錄下來。記錄了什麼、用哪個層級,請見 **[處理錯誤](../servers/handling-errors.md#any-other-exception)**。
## 試試看 {#try-it}
用 MCP Inspector 執行伺服器:

View file

@ -1,6 +1,6 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# 執行伺服器 {#running-your-server}
@ -70,7 +70,7 @@ Inspector 做的事和真正的主機一模一樣:把 `server.py` 當成子處
* `streamable_http_path`:MCP 端點的位置。預設為 `/mcp`。
* `json_response=True`:每個 POST 都用單一 JSON 本體回應,而不是 SSE 串流。那個本體只裝得下回應本身,別的都沒有,所以在請求中途回頭呼叫用戶端的工具(`ctx.elicit()`、取樣(sampling))在這一段會引發 `NoBackChannelError`,而綁在進行中呼叫上的通知(`ctx.report_progress()` 的進度、每次呼叫的記錄訊息)會被丟棄;獨立的 `GET` 串流仍會承載不相關的那些。
* `stateless_http=True`:每個請求一個全新的傳輸,不追蹤工作階段(session)。
* `max_request_body_size`:可接受的最大 POST 本體,以位元組計。預設為 4 MiB;更大的請求在解析或建立工作階段之前就會收到 HTTP 413。只有在合法的 MCP 訊息超過這個大小時才調高它。
* `max_request_body_size`:可接受的最大請求本體,以位元組計。預設為 4 MiB;更大的請求在解析或建立工作階段之前就會收到 HTTP 413。只有在合法的 MCP 訊息超過這個大小時才調高它。
* `event_store`、`retry_interval`、`transport_security`:可續傳性與 DNS 重新綁定防護。這些可以先放著,等到部署到 localhost 以外的地方再說;`transport_security` 在 **[部署與擴展](deploy.md)** 有說明。
!!! warning

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`,就得到一個會自我修正的 agent。
在伺服器上,一個 `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` 裡。
* 錯誤的引數會在函式執行前依 schema 被拒絕;這些不用 `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)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
tool: 1
---
# 媒體 {#media}
@ -81,6 +81,24 @@ result.structured_content # None
!!! check
用 `data=` 時沒有檔名,也就沒有東西可以猜。忘了 `format=`,SDK 就會退回預設值:圖片是 `image/png`,音訊是 `audio/wav`。這樣用 MP3 位元組建立 `Audio`,用戶端會被告知 `mime_type="audio/wav"`,然後老老實實地解碼失敗。傳 `data=` 的時候,就一起傳 `format=`。
## 內嵌資源 {#embedding-a-resource}
工具也可以回傳一份文件:一些文字或位元組,連同它所在的 URI 和 MIME 型別。這就是 **`EmbeddedResource`**,另一種內容區塊。它跟普通的 `str` 不同,會告訴用戶端這段內容是什麼,讓用戶端可以把它顯示成附件,或認出這是它已經知道的資源。
```python title="server.py" hl_lines="7 14 16-18"
--8<-- "docs_src/media/tutorial005.py"
```
* `brand://guidelines` 是一個普通的資源(**[資源](resources.md)** 有完整介紹)。模型要求時,工具會交出同一份文件;直接呼叫 `guidelines()`,真實來源就只有一個。
* `EmbeddedResource` 和 `TextResourceContents` 來自 `mcp.types`。這裡沒有像圖片那樣的輔助工具:建立的區塊會原封不動放進結果,也沒有 `structured_content`。
* 使用資源註冊時的 URI,用戶端才能知道附件和 `brand://guidelines` 是同一份文件。任何 URI 都合法,不論有沒有註冊。
```python
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
```
二進位內容就用 `BlobResourceContents(uri=..., mime_type=..., blob=...)` 取代 `TextResourceContents`,把位元組經 base64 編碼後放進 `blob`。如果只想送出一個指標,讓用戶端之後再用 `resources/read` 讀取,就改回傳 `ResourceLink(name=..., uri=...)`;它也是一種內容區塊。
## 圖示 {#icons}
`Icon` 是中繼資料,不是內容。它不帶圖片本身,而是用一個 URI 指向圖片;用戶端可以去抓取並顯示在伺服器名稱、工具、資源或提示詞旁邊。
@ -110,6 +128,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_
* 從工具回傳 `Image` 或 `Audio`,用戶端就會收到一個 `ImageContent`/`AudioContent` 區塊:位元組經 base64 編碼,附上 MIME 型別。
* 可以用 `path=` 建立並讓副檔名決定 MIME 型別,或用記憶體內的 `data=` 加上明確的 `format=`。
* 回傳 `EmbeddedResource` 可以把一份文件(文字或 base64 blob,附上 URI 和 MIME 型別)放進結果;只想送出指標的話,回傳 `ResourceLink`。
* 媒體結果沒有 `structured_content`,也沒有輸出 schema。
* `Icon` 是個指標:一個 `src` URI,加上選用的 `mime_type`、`sizes` 和 `theme`。
* `icons=[...]` 在伺服器、工具、資源和提示詞上都能用,用戶端會在對應的物件上找到它們。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
tool: 1
---
# 提示詞 {#prompts}
@ -137,7 +137,52 @@ uv run mcp dev server.py
```
!!! info
如果讀過 **[工具](tools.md)**,這一頁的內容你都已經會了。同樣的裝飾器、同樣以 docstring 當描述、同樣的 `Annotated`/`Field`。唯一不同的是由誰觸發(使用者),以及結果去哪裡(進入對話)。
如果讀過 **[工具](tools.md)**,到這裡為止的內容你都已經會了。同樣的裝飾器、同樣以 docstring 當描述、同樣的 `Annotated`/`Field`。唯一不同的是由誰觸發(使用者),以及結果去哪裡(進入對話)。
## 不只是文字 {#more-than-text}
`UserMessage` 和 `AssistantMessage` 凡是接受 `str` 的地方,也都接受內容區塊,或 `Image`/`Audio` 輔助類別。提示詞裡常見兩種情況:附上一份文件,以及附上一張圖片。
### 嵌入檔案 {#embedding-a-file}
```python title="server.py" hl_lines="5 12 21 23"
--8<-- "docs_src/prompts/tutorial004.py"
```
* 風格指南是位於 `style://python` 的資源(**[資源](resources.md)** 會介紹),從 `server.py` 旁邊的 `style-guide.md` 讀取。放任何一個 Markdown 檔案在那裡都可以。
* `EmbeddedResource(resource=TextResourceContents(...))`(兩者都來自 `mcp.types`)把檔案連同 URI 和 MIME 類型當成第一則訊息帶上;引用它的請求以純文字接在後面。
* 用嵌入而不是把指南貼進 f-string,用戶端就能把它顯示成附件,之後還能重新打開 `style://python`,而模型收到的是原封不動的檔案。二進位檔案則改用 `BlobResourceContents` 搭配 base64 的 `blob`。
算繪之後,第一則訊息的 `content` 是一個 `resource` 區塊:
```json
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
```
### 附上圖片 {#attaching-an-image}
```python title="server.py" hl_lines="4 15"
--8<-- "docs_src/prompts/tutorial005.py"
```
* `Image` 是 **[圖片、音訊與圖示](media.md)** 裡的輔助類別。提示詞算繪時,`UserMessage` 會把它轉成 `ImageContent` 區塊(檔案以 base64 編碼,MIME 類型從 `.png` 推測);`Audio` 也以同樣方式變成 `AudioContent`。
* 在 `server.py` 旁邊放任何一張名為 `architecture.png` 的 PNG。提示詞引數是字串,所以圖片一定來自伺服器;`component` 只提供文字。
```json
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
```
## 執行時變更清單 {#changing-the-list-at-runtime}
用戶端連著的時候也可以新增提示詞,例如讓使用者把一段指示存成自己的選單項目。先註冊提示詞,再發通知:
```python title="server.py" hl_lines="5 23-27"
--8<-- "docs_src/prompts/tutorial006.py"
```
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` 註冊函式的效果和 `@mcp.prompt()` 完全一樣,`mcp.remove_prompt(name)` 則是反過來。`add_prompt` 遇到同名的既有項目會保留而不覆寫,所以這個工具會先移除舊的,讓儲存變成取代。`prompts/list` 會立即反映變更。
* `await ctx.notify_prompts_changed()` 把 `notifications/prompts/list_changed` 送給每個在 `subscriptions/listen` 串流上監聽的 `2026-07-28` 用戶端(**[訂閱](../handlers/subscriptions.md)**)。呼叫端是 2026 之前的用戶端時,`await ctx.session.send_prompt_list_changed()` 會把通知送給它(**[服務舊版用戶端](../run/legacy-clients.md)**)。兩個都呼叫;沒有人可通知時,各自什麼都不做。
* 收到通知的用戶端會再呼叫一次 `prompts/list`。在 Python 的 `Client` 裡寫成 `async with client.listen(prompts_list_changed=True) as sub:`,會產出 `PromptsListChanged` 事件。
## 重點回顧 {#recap}
@ -147,5 +192,7 @@ uv run mcp dev server.py
* 回傳 `str` 會變成一則使用者訊息。回傳 `UserMessage`/`AssistantMessage` 的清單,可以替多輪對話起頭。
* `title=` 和 `Field(description=...)` 是用戶端放在 UI 上的內容。
* 缺少必填引數會讓整個請求失敗,沒有個別提示詞的錯誤結果。
* 把 `EmbeddedResource` 或 `Image` 包進 `UserMessage`,就能附上文件或圖片。
* 執行時用 `mcp.add_prompt(...)`/`mcp.remove_prompt(...)` 新增或移除提示詞,接著 `await ctx.notify_prompts_changed()` 和 `await ctx.session.send_prompt_list_changed()`。
伺服器端替提示詞(或資源範本)引數做自動完成,請見 **[自動完成](completions.md)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
tool: 1
---
# 結構化輸出 {#structured-output}
@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions
--8<-- "docs_src/structured_output/tutorial003.py"
```
`TypedDict` 在執行時就是普通的 `dict`,所以建立並回傳的就是它。schema、驗證和 `structured_content` 都跟 `BaseModel` 版本一模一樣(少了描述,因為 `TypedDict` 沒有地方放)。
`TypedDict` 在執行時就是普通的 `dict`,所以建立並回傳的就是它。schema、驗證和 `structured_content` 都遵循跟 `BaseModel` 版本一樣的規則:加上類別 docstring 或 `Annotated[..., Field(description=...)]`,它們就成為描述;dict 裡沒放進去的 `NotRequired` 鍵,也不會出現在 `structured_content` 裡。
## dataclass {#a-dataclass}
@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
註記承諾的是 `WeatherData`,但上游回應不再送 `humidity` 了。
!!! check
呼叫 `get_weather`,它不會默默把一個半空的物件交給用戶端。呼叫會失敗,錯誤的頭幾行就點名了那個欄位:
呼叫 `get_weather`,它不會默默把一個半空的物件交給用戶端。呼叫會失敗:用戶端收到 `is_error=True` 和 `Error executing tool get_weather`,所以模型知道呼叫失敗了,而不是信心滿滿地讀一份根本不存在的天氣。欄位名稱是留給你看的,在伺服器記錄裡以 `ERROR` 層級出現:
```text
Error executing tool get_weather: 1 validation error for WeatherData
Tool 'get_weather' raised an unexpected exception
...
pydantic_core._pydantic_core.ValidationError: 1 validation error for WeatherData
humidity
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
```
這段文字會以 `is_error=True` 的工具結果回傳,所以模型知道呼叫失敗了,而不是信心滿滿地讀一份根本不存在的天氣。
順帶一提,從 `-> WeatherData` 的工具回傳普通的 `dict` 沒問題,`json.loads` 產生的正是這個。驗證看的是值,不是 Python 型別。
## 選擇退出 {#opting-out}
@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
反過來,`structured_output=True` 會把自動偵測變成硬性要求:回傳型別產生不出 schema 的工具,會在匯入時引發例外,而不是退回文字。
## 內容區塊與媒體 {#content-blocks-and-media}
內容區塊和媒體(`TextContent`、`EmbeddedResource`、`Image`、`Audio` 這一類,不論是單獨出現、作為 `list`、`tuple` 或 `Sequence` 的元素,還是作為 union 的分支)會自動幫你退出:它們是給模型讀的,所以自動偵測不會從中推導出 schema(`Image` 和 `Audio` 在 **[圖片、音訊與圖示](media.md)** 說明)。對內容區塊類別,`structured_output=True` 仍然會強制產生一個 schema。
## 沒有型別提示的類別 {#a-class-without-type-hints}
有一種情況會在沒有要求的前提下變成非結構化:回傳一個**本體上沒有任何註記**的類別。
@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
* 純量、串列、tuple 和 union 會包進 `{"result": ...}`。模型、`TypedDict`、dataclass、帶註記的類別和 `dict[str, ...]` 本來就是物件,維持原樣。
* 每個結果都帶有 `content`(文字,給模型)**和** `structured_content`(資料,給應用程式)。
* 回傳的東西會拿 schema 驗證。不符合就是工具錯誤,不會是一份壞掉的結果。
* `structured_output=False` 讓工具退出。沒有型別提示的類別會默默退出,要留意。
* `structured_output=False` 讓工具退出。內容區塊、`Image` 和 `Audio` 預設就退出;沒有型別提示的類別會默默退出,要留意。
工具能回覆的一切,現在都掌握在你手上了。接下來是第二個基本元件:**[資源](resources.md)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
tool: 1
---
# 工具 {#tools}
@ -39,6 +39,8 @@ SDK 從這些型別提示產生一份 JSON Schema,並在 `tools/list` 時送
兩個引數都在 `required` 裡,因為都沒有預設值。等一下就會修正這點。(`title` 鍵是 Pydantic 產生的附帶產物;屬性、它們的型別和 `required` 才是契約。)
也沒有 `$schema` 鍵:MCP 把沒有這個鍵的 schema 當作 **JSON Schema 2020-12**,而這正是 Pydantic 產生的格式,所以在你到 **[低階 Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** 上親手寫 schema 之前,沒有什麼需要選的。
!!! tip
這裡的型別提示不是說明文件,而是**契約**。如果用戶端送來 `"limit": "ten"`,SDK 會在函式執行之前就拒絕它。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
tool: 1
---
# URI 範本與路徑安全 {#uri-templates-and-path-safety}
@ -98,7 +98,7 @@ translation:
內建檢查擋得住常見情況,但無從得知你的沙箱邊界。存取檔案系統時,用 `safe_join` 解析路徑,並確認它仍在基底目錄之內:
```python title="server.py" hl_lines="4 14"
```python title="server.py" hl_lines="5 15"
--8<-- "docs_src/uri_templates/tutorial002.py"
```
@ -127,7 +127,7 @@ translation:
這些檢查只是啟發式的前置過濾;存取檔案系統時,`safe_join` 仍然是真正的隔離邊界。
!!! tip
如果處理函式無法完成請求(檔案不存在、id 不認識),就引發例外。SDK 會把它轉成錯誤回應。協定錯誤和工具錯誤的差別請見 **[處理錯誤](handling-errors.md)**。
如果處理函式無法完成請求(檔案不存在、id 不認識),就像上面的 `read_manual` 那樣引發 `ResourceNotFoundError`。用戶端會收到 `-32602`,附上你的訊息和 URI。非預期的例外則會變成通用的 `-32603`。請見 **[處理錯誤](handling-errors.md#a-resource-that-doesnt-exist)**。
## 低階 Server 上的資源 {#resources-on-the-low-level-server}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
tool: 1
---
# 疑難排解 {#troubleshooting}
@ -79,11 +79,11 @@ async def main() -> None:
`__aexit__` 就是斷線,這也是為什麼沒有 `client.close()` 可以忘記。**[測試](get-started/testing.md)** 正是建立在這個模式上。
## `Error executing tool <name>: <message>` 與 `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
## `Error executing tool <name>: <message>`、`Error executing tool <name>` 與 `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
你讀到的是**結果**,不是例外。`call_tool` 沒有引發例外,而且遇到失敗的工具它永遠不會引發。
用伺服器不認識的城市呼叫 `forecast`,它引發的例外會跟著一個標記為**成功**的請求一起回來:
用伺服器不認識的城市呼叫 `forecast`,它引發的 `ToolError` 會跟著一個標記為**成功**的請求一起回來:
```python
result.is_error # True
@ -95,6 +95,8 @@ result.structured_content # None
修正在用戶端:**檢查 `result.is_error`**。包在 `call_tool` 外面的 `try/except` 一個都攔不到,因為根本沒有東西可以攔。這是刻意的設計,也是這一頁最值得內化的一件事:是**模型**選擇了這個呼叫,所以訊息交給模型,讓它有機會再試一次。完整說明請見 **[處理錯誤](servers/handling-errors.md)**,包括**確實會**引發例外的 `MCPError` 路徑。
不帶訊息的簡略形式 `Error executing tool <name>` 表示工具**當掉了**:一個它沒預料到的例外逃了出來(或是它的回傳值沒通過輸出 schema),而那個例外的文字不會送上線路。traceback 在**伺服器的記錄**裡,以 `ERROR` 層級記錄為 `Tool '<name>' raised an unexpected exception`。
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
你寫了 `@mcp.tool` 而不是 `@mcp.tool()`。`tool()` 是裝飾器**工廠**:少了括號,Python 會把你的函式交給它的 `name=` 參數。
@ -393,7 +395,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
## 重點回顧 {#recap}
* `ExceptionGroup: unhandled errors in a TaskGroup` 永遠不是錯誤本身。讀**最後一行**;在 `async with Client(...)` 區塊**裡面**攔截 `MCPError` 就完全跳過包裝。
* `call_tool` 不會因為工具失敗而引發例外。`Error executing tool ...` 和 `Unknown tool: ...` 是結果:檢查 `result.is_error`。
* `call_tool` 不會因為工具失敗而引發例外。`Error executing tool ...` 和 `Unknown tool: ...` 是結果:檢查 `result.is_error`。工具名稱後面沒有訊息表示它當掉了,traceback 在伺服器記錄裡。
* `Client must be used within an async context manager` -> 用 `async with`。`Use @tool() instead of @tool` -> 加上括號。
* 伺服器記錄裡的 `Tool already exists:` 是兩個同名工具合併成一個的唯一跡象。
* 一個 421,三種寫法:`Server returned an error response`(python `Client`)、`421 Misdirected Request` / `Invalid Host header`(其他所有東西)、`Invalid Host header: <host>`(伺服器記錄)。修正:`transport_security=TransportSecuritySettings(allowed_hosts=[...])`。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
tool: 1
---
# v2 的新功能 {#whats-new-in-v2}
@ -41,9 +41,9 @@ v1 交給你的是三層巢狀結構:一個產出原始串流的傳輸 context
--8<-- "docs_src/client/tutorial001.py"
```
`Client` 接受一個伺服器物件(記憶體內、沒有傳輸,也就是測試的做法)、一個 URL(Streamable HTTP),或任何傳輸 context manager,例如 `stdio_client(...)`。進入 `async with` 就會連線並協商協定版本,不管伺服器講的是哪個世代;之後 `client.server_capabilities` 和 `client.protocol_version` 就直接在那裡,伺服器有表明身分時 `client.server_info` 也在(它現在是 `Implementation | None`,因為 2026 世代的身分是選用的)。在 v1 註冊的取樣和徵詢回呼仍然有效(回呼本體會遇到跟本頁其他地方一樣的 snake_case 屬性改名),現在也會回應 2026 風格的「結果中夾帶請求」(見下文),而且是並行執行,不再一次一個。想要低階介面的人,`ClientSession` 仍在底下,`client.session` 會把它交給你;它也有變動(跑在新的分派器引擎上,自己的部分簽章也改了),所以往下鑽之前先讀 **[遷移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。
`Client` 接受一個伺服器物件(記憶體內、沒有傳輸,也就是測試的做法)、一個 URL(Streamable HTTP)、一個 `StdioServerParameters`(stdio 子處理程序),或任何其他傳輸 context manager,例如 `sse_client(...)`。進入 `async with` 就會連線並協商協定版本,不管伺服器講的是哪個世代;之後 `client.server_capabilities` 和 `client.protocol_version` 就直接在那裡,伺服器有表明身分時 `client.server_info` 也在(它現在是 `Implementation | None`,因為 2026 世代的身分是選用的)。在 v1 註冊的取樣和徵詢回呼仍然有效(回呼本體會遇到跟本頁其他地方一樣的 snake_case 屬性改名),現在也會回應 2026 風格的「結果中夾帶請求」(見下文),而且是並行執行,不再一次一個。想要低階介面的人,`ClientSession` 仍在底下,`client.session` 會把它交給你;它也有變動(跑在新的分派器引擎上,自己的部分簽章也改了),所以往下鑽之前先讀 **[遷移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。
**[用戶端](client/index.md)** 介紹它,**[用戶端傳輸方式](client/transports.md)** 說明三種連線形式,**[用戶端回呼](client/callbacks.md)** 說明回呼本身,**[測試](get-started/testing.md)** 示範取代 v1 `create_connected_server_and_client_session()` 輔助函式的記憶體內模式。
**[用戶端](client/index.md)** 介紹它,**[用戶端傳輸方式](client/transports.md)** 說明四種連線形式,**[用戶端回呼](client/callbacks.md)** 說明回呼本身,**[測試](get-started/testing.md)** 示範取代 v1 `create_connected_server_and_client_session()` 輔助函式的記憶體內模式。
### 低階 `Server` 是重寫,不是改名 {#the-low-level-server-was-rebuilt-not-renamed}
@ -129,7 +129,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB
改名會自己跳出來提醒你。下面這些不會:
* **同步函式在工作執行緒上執行。** `def` 的工具(或資源、提示詞、解析器)不再阻塞事件迴圈;代價是它的本體不再**在**事件迴圈執行緒上執行,這對綁定執行緒的程式碼有影響。`async def` 處理函式不受影響。**[遷移指南](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**。
* **在工具裡引發的 `MCPError`(v1 的 `McpError`)現在是協定錯誤。** 模型永遠看不到它。其他所有例外仍然會變成模型讀得到、能做出反應的 `is_error=True` 結果。兩者的分界請見 **[處理錯誤](servers/handling-errors.md)**。
* **在工具裡引發的 `MCPError`(v1 的 `McpError`)現在是協定錯誤。** 模型永遠看不到它。其他所有例外仍然會變成 `is_error=True` 的結果,但只有 `ToolError` 的訊息會送到模型面前:其他例外現在一律顯示為 `Error executing tool <name>`,traceback 則留在伺服器記錄裡。兩者的分界請見 **[處理錯誤](servers/handling-errors.md)**。
* **結果送出前會先驗證。** 手動建立、`input_schema` 為 `{}` 的 `Tool` 現在會讓 `tools/list` 失敗(規格要求 `"type": "object"`)。用 `@mcp.tool()` 建的伺服器不會遇到;它們的 schema 是 SDK 寫的。
* **用戶端會驗證收到的東西。** `list_tools()` 和 `call_tool()` 會用協商好的協定版本檢查伺服器的回答,所以 v1 寬鬆解析還能容忍的不太合規伺服器,現在會引發 `pydantic.ValidationError`。如果連到的是自己無法控制的伺服器,要有心理準備,發現問題的人會是你;細節請見 **[遷移指南](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**。
* **URI 範本現在是真正的 RFC 6570。** `{+path}`、`{?query}` 這些都能用,比對是精確的而不是正規表示式那種寬鬆,擷取出的值若含路徑穿越,預設會被拒絕。更嚴格的範本會在裝飾時就失敗,而不是等到第一個請求。**[URI 範本](servers/uri-templates.md)**。