docs: refresh translations, and translate pages in parallel (#3458)

This commit is contained in:
Max 2026-09-07 13:35:53 +01:00 • committed by GitHub
commit fd66270926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 4821 additions and 2277 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
sections: [0355618e5f4d5fe4, 0fefa31fb7c7b585, 5c53e7487c9c70cc, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
tool: 1
---
# MCP Apps {#mcp-apps}
@ -18,7 +18,7 @@ SDK 以內建的 `Apps` 擴充功能(`io.modelcontextprotocol/ui`)提供這
## 有錶面的時鐘 {#a-clock-with-a-face}
```python title="server.py" hl_lines="19 22 30 32"
```python title="server.py" hl_lines="17 20 28 30"
--8<-- "docs_src/apps/tutorial001.py"
```
@ -39,11 +39,31 @@ HTML 本身會監聽主機的 `postMessage` 並顯示結果。真正的 app 請
模型讀的是 `content`;iframe 是給人看的。支援 UI 的主機照樣會把文字結果餵給模型,而純文字用戶端**只**會拿到那個。所以標準做法是一個工具,兩種答案。再看一次 `get_time`:
```python title="server.py" hl_lines="23-27"
```python title="server.py" hl_lines="21-25"
--8<-- "docs_src/apps/tutorial001.py"
```
只有當用戶端宣告了 `io.modelcontextprotocol/ui` 擴充功能,**而且**在它的 `mimeTypes` 設定裡列出 `text/html;profile=mcp-app` 時,`client_supports_apps(ctx)` 才會是 `True`。這個欄位是必填的,所以省略它的用戶端不算數。同一個檔案裡的 `main()` 宣告的正是這些:協商的用戶端那一半,於是回來的是豐富版的答案。
只有當用戶端宣告了 `io.modelcontextprotocol/ui` 擴充功能,**而且**在它的 `mimeTypes` 設定裡列出 `text/html;profile=mcp-app` 時,`client_supports_apps(ctx)` 才會是 `True`。這個欄位是必填的,所以省略它的用戶端不算數。以下是協商中用戶端的那一半:
```python title="client.py" hl_lines="8 12"
--8<-- "docs_src/apps/tutorial001_client.py"
```
透過 HTTP 提供 `server.py`,然後在第二個終端機執行用戶端:
```console
uv run mcp run server.py --transport streamable-http
```
```console
python client.py
```
```text
2026-06-26T12:00:00Z
```
回來的是豐富版的答案。把 `extensions=[APPS_SUPPORT]` 從 `Client` 呼叫裡拿掉,同一個程式就會改印出 `The time is 2026-06-26T12:00:00Z.`,而這就是純文字用戶端所能看到的全部。
!!! warning
絕對不要把 `"[Rendered UI]"` 這類佔位文字當成唯一的內容回傳。如果後備文字沒有用,這個工具對每個純文字用戶端、對模型本身就都沒有用。好好寫那句話。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 6eba6ce094f4417e, 125f28588c85dd7b, ddd8969b698ca11c, ed6af2df4b656dff]
tool: 1
---
# 擴充功能 {#extensions}
@ -49,7 +49,7 @@ TypeError: Stamps.identifier must be a `vendor-prefix/name` string
最小的有用擴充功能是一個工具加上一個設定對映表:
```python title="server.py" hl_lines="17 19-20 22-23 26"
```python title="server.py" hl_lines="16 18-19 21-22 25"
--8<-- "docs_src/extensions/tutorial003.py"
```
@ -57,17 +57,23 @@ TypeError: Stamps.identifier must be a `vendor-prefix/name` string
* `settings()` 是在 `capabilities.extensions["com.example/stamps"]` 宣告的值。回傳 `{}`(預設值)表示宣告這個擴充功能但不帶任何設定。
* 擴充功能永遠不會拿到伺服器。它以資料的形式宣告貢獻,由 `MCPServer` 取用。沒有 `self.server` 可以修改。
而 `main()` 就是證明,一個記憶體內用戶端直接連上 `mcp`:
透過 HTTP 提供服務,用戶端就是證明:
```python title="server.py" hl_lines="29-34"
--8<-- "docs_src/extensions/tutorial003.py"
```console
uv run mcp run server.py --transport streamable-http
```
```python title="client.py" hl_lines="7-11"
--8<-- "docs_src/extensions/tutorial003_client.py"
```
本頁每個 `server.py` 都用這個指令提供服務,每個 `client.py` 則在第二個終端機用 `python client.py` 與它並行執行。
### 提供自己的方法 {#serving-your-own-methods}
擴充功能可以註冊**新的請求方法**:屬於它自己的動詞,和規格定義的方法並列提供:
```python title="server.py" hl_lines="16-22 31 40-48"
```python title="server.py" hl_lines="14-20 24 33-41"
--8<-- "docs_src/extensions/tutorial004.py"
```
@ -83,14 +89,15 @@ TypeError: Stamps.identifier must be a `vendor-prefix/name` string
### 用戶端這一側 {#the-client-side}
同一個檔案的 `main()` 就是完整的用戶端故事,兩半都在裡面:
用戶端是獨立的程式,用戶端故事的兩半都在裡面:
```python title="server.py" hl_lines="54-58"
--8<-- "docs_src/extensions/tutorial004.py"
```python title="client.py" hl_lines="21-23 27-30"
--8<-- "docs_src/extensions/tutorial004_client.py"
```
* `Client(..., extensions=[advertise(EXTENSION_ID)])` 宣告這個擴充功能。這些宣告會變成 `ClientCapabilities.extensions`:在 2026-07-28 連線上,這個對映表隨著每個請求的 `_meta` 信封傳送,所以伺服器在**每一個**請求上都看得到它;在舊版連線上,它則搭著 `initialize` 交握傳送。伺服器程式碼不用在意是哪一種:`require_client_extension(ctx, ...)` 和 `ctx.session.check_client_capability(...)` 在兩條路徑上都會讀取正確的來源。
* 廠商方法要往下一層用 `client.session.send_request(...)`;`Client` 只會為規格動詞長出一級方法。`send_request` 接受任何 `Request` 子類別,所以廠商請求原樣傳遞即可。
* `SearchRequest` 和它攜帶的兩個模型是這個擴充功能的線路契約,所以用戶端自己宣告一份。正式發布的擴充功能會把它們放進一個套件,讓兩端都匯入。
### 攔截 `tools/call` {#intercepting-toolscall}
@ -109,12 +116,18 @@ TypeError: Stamps.identifier must be a `vendor-prefix/name` string
## 使用用戶端擴充功能 {#using-a-client-extension}
**用戶端擴充功能**是從使用端看的同一份契約:掛在單一識別碼之下的一組用戶端行為。把實例傳給 `Client(extensions=[...])`,然後照常呼叫工具:
**用戶端擴充功能**是從使用端看的同一份契約:掛在單一識別碼之下的一組用戶端行為。這裡的伺服器回應 `buy` 時給的不是貨品,而是一張待兌換的收據,而且只對宣告了這個擴充功能的用戶端這樣做:
```python title="client.py" hl_lines="66-68"
```python title="server.py" hl_lines="22-25"
--8<-- "docs_src/extensions/tutorial006.py"
```
在用戶端,把實例傳給 `Client(extensions=[...])`,然後照常呼叫工具:
```python title="client.py" hl_lines="33-35"
--8<-- "docs_src/extensions/tutorial006_client.py"
```
`call_tool("buy", ...)` 回傳普通的 `CallToolResult`,和其他所有呼叫一樣。擴充功能改變的是:伺服器現在可以用 `receipt` **結果形狀**來回應 `buy`,而不是最終結果,而 `Receipts` 會在 `call_tool` 回傳之前把它完成(這裡是透過後續呼叫兌換收據)。呼叫端的程式碼完全不用動。
拿掉這個擴充功能,這一切就不存在:伺服器的關卡會拒絕沒有宣告它的用戶端(錯誤 -32021),而來自跳過關卡的伺服器的認領形狀會驗證失敗,完全符合規格對無法辨識的 `resultType` 的要求。預設關閉,線路的兩端都是。
@ -124,15 +137,15 @@ TypeError: Stamps.identifier must be a `vendor-prefix/name` string
```python
from mcp.client import advertise
client = Client(mcp, extensions=[advertise("com.example/search")])
client = Client("http://localhost:8000/mcp", extensions=[advertise("com.example/search")])
```
## 撰寫用戶端擴充功能 {#writing-a-client-extension}
繼承 `ClientExtension`,只覆寫需要的部分。三種貢獻類型,各有預設實作:`settings()`、`claims()` 和 `notifications()`。
```python title="client.py" hl_lines="17-18 43-44 46-47"
--8<-- "docs_src/extensions/tutorial006.py"
```python title="client.py" hl_lines="16-17 25-26 28-29"
--8<-- "docs_src/extensions/tutorial006_client.py"
```
* 識別碼遵循和伺服器相同的語法,在類別定義時驗證。
@ -153,12 +166,18 @@ def notifications(self) -> Sequence[NotificationBinding[Any]]:
### 擴充功能動詞 {#extension-verbs}
擴充功能自己的請求方法不需要用戶端註冊。廠商請求型別繼承 `mcp.types.Request`,並透過 `client.session.send_request` 送出,如[提供自己的方法](#serving-your-own-methods)所示。多一件事:當某個參數鍵必須搭上 `Mcp-Name` 標頭時(像 tasks 這類擴充功能規格對它們的動詞有此要求),請求型別要宣告 `name_param`:
擴充功能自己的請求方法不需要用戶端註冊。廠商請求型別繼承 `mcp.types.Request`,並透過 `client.session.send_request` 送出,如[提供自己的方法](#serving-your-own-methods)所示。以一個伺服器為例,它的擴充功能提供一個和具名工作有關的動詞:
```python title="client.py" hl_lines="22-25 46-47"
```python title="server.py" hl_lines="12-13 30"
--8<-- "docs_src/extensions/tutorial007.py"
```
用戶端多一件事:當某個參數鍵必須搭上 `Mcp-Name` 標頭時(像 tasks 這類擴充功能規格對它們的動詞有此要求),請求型別要宣告 `name_param`:
```python title="client.py" hl_lines="20-23 28-29"
--8<-- "docs_src/extensions/tutorial007_client.py"
```
工作階段會在每一條送出路徑上把 `params["jobId"]` 鏡射到 `Mcp-Name`,而缺少值時會明確失敗,而不是默默省略必要的標頭。
## 擴充功能不能做的事 {#what-an-extension-cannot-do}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 9d5d10a5f0405d0a, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, 0fde3bcea081ba3a]
tool: 1
---
# 低階 Server {#the-low-level-server}
@ -36,18 +36,22 @@ translation:
### 試試看 {#try-it}
這個沒有 Inspector 可用:`mcp dev` 和 `mcp run` 只接受 `MCPServer`。記憶體內的 `Client` 則不在乎;它接收低階 `Server` 的方式和接收 `MCPServer` 完全一樣:
`mcp dev` 和 `mcp run` 只接受 `MCPServer`,所以這個得自己提供服務。`server.py` 的最後一行用它建立一個普通的 ASGI 應用程式,再交給 uvicorn 執行:
```python title="main.py"
```console
uvicorn server:app --port 8000
```
把 Inspector 或任何用戶端指向 `http://localhost:8000/mcp`:
```python title="client.py"
import asyncio
from mcp import Client
from server import server
async def main() -> None:
async with Client(server) as client:
async with Client("http://localhost:8000/mcp") as client:
result = await client.call_tool("search_books", {"query": "dune", "limit": 5})
print(result.content)
@ -64,6 +68,8 @@ asyncio.run(main())
* `result.structured_content` 是 `None`。高階伺服器會幫你把 `-> str` 包成 `{"result": ...}`;在這裡,你沒建的東西,沒有人會替你建。
* `list_tools` 回傳的是**你**打出來的 schema,一字不差。高階版本每個屬性上都有 `"title": "Query"`,根部還有一個 `"title": "search_booksArguments"`:那是 Pydantic 的產物。在這一層,線路上有的東西,都是你放上去的。
在測試中可以跳過 uvicorn 和連接埠:`Client(server)` 在處理程序內接收低階 `Server` 的方式和接收 `MCPServer` 完全一樣,**[測試](../get-started/testing.md)** 講的就是這個模式。
## 沒有人替你檢查 {#nothing-is-checked-for-you}
`MCPServer` 會在函式執行之前就拒絕錯誤的引數,依照它產生的 schema 驗證這次呼叫(**[工具](../servers/tools.md)**)。
@ -214,4 +220,4 @@ use Server.middleware to observe or wrap initialization
* `add_request_handler(method, params_type, handler)` 可以服務任何方法。`initialize` 被保留。
* `Server` 公告的能力,由你註冊了哪些處理函式推導而來。
`Client(server)` 對兩種伺服器一視同仁,因為它們**就是**同一個協定,這正是重點所在。再往下一層根本不是類別:是 **[中介軟體](middleware.md)**。
用戶端對兩種伺服器一視同仁,因為它們**就是**同一個協定,這正是重點所在。再往下一層根本不是類別:是 **[中介軟體](middleware.md)**。

View file

@ -1,6 +1,6 @@
---
translation:
sections: [a9aba7a026c7bd85, ed32bda7ba9ae33a, 7e64cc5646abb91f, 22a0129ee78b3c63, d875373c06d8d2f9]
sections: [a9aba7a026c7bd85, 83e2a08b9d46a398, 9fd8a0aa384b3257, 22a0129ee78b3c63, d875373c06d8d2f9]
tool: 1
---
# 分頁 {#pagination}
@ -29,9 +29,13 @@ translation:
### 試試看 {#try-it}
`Client(server)` 在記憶體內連線到低階 `Server` 的方式,和連到 `MCPServer` 完全一樣。
`mcp run` 只接受 `MCPServer`,所以這個得自己提供服務。`server.py` 的最後一行從 `Server` 建出一個普通的 ASGI 應用程式,交給 uvicorn 執行:
不帶引數呼叫 `list_resources()`。會拿到十個資源,`book-1` 到 `book-10`,而 `next_cursor` 是字串 `"10"`。
```console
uvicorn server:app --port 8000
```
把任何用戶端(**[用戶端](../client/index.md)**,或 Inspector)指向 `http://localhost:8000/mcp`,不帶引數呼叫 `list_resources()`。會拿到十個資源,`book-1` 到 `book-10`,而 `next_cursor` 是字串 `"10"`。
用 `list_resources(cursor="10")` 把它交回去,第一個資源就是 `book-11`,新的 `next_cursor` 是 `"20"`。
@ -41,7 +45,7 @@ translation:
`Client` 上的每個 `list_*` 方法(`list_tools`、`list_resources`、`list_resource_templates`、`list_prompts`)都接受 `cursor=` 關鍵字引數。把分頁清單抓完只要一個 `while True`:
```python title="client.py" hl_lines="26-32"
```python title="client.py" hl_lines="9-15"
--8<-- "docs_src/pagination/tutorial002.py"
```
@ -49,7 +53,7 @@ translation:
* 先 extend,**再**看 `next_cursor`:最後一頁也有資源。
* `next_cursor is None` 就是出口。其他任何值都原封不動直接放回 `cursor=`。
執行它的 `main()`,會印出 `100 resources`:十頁、每頁十個,由一個從頭到尾不知道有十頁的迴圈接起來。
讓 uvicorn 繼續提供 `server.py`,在第二個終端機執行 `python client.py`。會印出 `100 resources`:十頁、每頁十個,由一個從頭到尾不知道有十頁的迴圈接起來。
這和 **[用戶端](../client/index.md)** 為每個 `list_*` 動詞示範的迴圈是同一個,而且對不分頁的伺服器也沒有任何代價:第一個回應的 `next_cursor` 就是 `None`,迴圈只執行一次。