docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
5 changed files with 89737 additions and 54 deletions
139
i18n/zh-hant/pages/get-started/first-steps.md
Normal file
139
i18n/zh-hant/pages/get-started/first-steps.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
translation:
|
||||
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
|
||||
tool: 1
|
||||
---
|
||||
# 第一步 {#first-steps}
|
||||
|
||||
**[首頁](../index.md)** 的節奏很快:寫一個伺服器、執行它、呼叫一個工具。
|
||||
|
||||
這一頁放慢腳步,把伺服器能公開的三種東西都走過一遍,沿途每樣東西都給個名字。
|
||||
|
||||
## 主機、用戶端與伺服器 {#host-client-and-server}
|
||||
|
||||
接下來每一頁都會看到這三個詞:
|
||||
|
||||
* **主機**(host)是 LLM 應用程式:Claude、IDE、代理執行環境。使用者對話的就是它。
|
||||
* **用戶端**位於主機內部,負責講 MCP。主機每連上一個伺服器,就執行一個用戶端。
|
||||
* **伺服器**是你用這個 SDK 打造的東西。它把東西公開給用戶端,從不直接和模型溝通。
|
||||
|
||||
伺服器由你來寫,主機是別人的產品。SDK 也提供一個 `Client`,用來測試你的伺服器,這一頁稍後就會出現。
|
||||
|
||||
## 三種基本元件 {#the-three-primitives}
|
||||
|
||||
伺服器公開的東西正好只有三種。區分它們的關鍵是**誰決定要用**:
|
||||
|
||||
| 基本元件 | 由誰控制 | 是什麼 | 範例 |
|
||||
|---------------|-----------------|-----------------------------------------------------|------------------------------------|
|
||||
| **工具** | 模型 | 模型呼叫來執行動作的函式 | API 呼叫、寫入資料庫 |
|
||||
| **資源** | 應用程式 | 主機載入到模型上下文的資料 | 檔案內容、API 回應 |
|
||||
| **提示詞** | 使用者 | 使用者依名稱叫用的可重複使用訊息範本 | 斜線指令、選單項目 |
|
||||
|
||||
「由誰控制」正是這樣拆分的重點所在。工具會執行,是因為**模型**決定呼叫它。資源會被附上,是因為**應用程式**判斷模型需要它。提示詞會執行,是因為**使用者**選了它。
|
||||
|
||||
!!! info
|
||||
如果做過 web API,大部分直覺你已經有了:**資源**是 `GET`(載入資料,不改變任何東西),**工具**是 `POST`(做事,可能有副作用)。**提示詞**沒有 HTTP 的對應物,比較像使用者依名稱執行的已儲存查詢。
|
||||
|
||||
## 一個伺服器,三種齊備 {#one-server-all-three}
|
||||
|
||||
```python title="server.py" hl_lines="6 12 18"
|
||||
--8<-- "docs_src/first_steps/tutorial001.py"
|
||||
```
|
||||
|
||||
三個普通函式,三個裝飾器。每個裝飾器就是完整的註冊動作:
|
||||
|
||||
* `@mcp.tool()` 讓 `add` 成為**工具**。
|
||||
* `@mcp.resource("greeting://{name}")` 讓 `greeting` 成為**資源範本**:URI 裡的 `{name}` 就是函式的參數。
|
||||
* `@mcp.prompt()` 讓 `summarize` 成為**提示詞**。它回傳的字串會變成一則使用者訊息。
|
||||
|
||||
其他一切(名稱、描述、引數 schema)SDK 都從函式本身讀取:函式名稱、docstring、型別提示。這些你從來沒有另外宣告過。
|
||||
|
||||
!!! tip
|
||||
SDK 的兩半各有自己的匯入路徑:`from mcp import Client` 和 `from mcp.server import MCPServer`。沒有 `from mcp import MCPServer` 這種寫法。
|
||||
|
||||
### 試試看 {#try-it}
|
||||
|
||||
用 MCP Inspector 執行它:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
開啟它印出的 URL。Inspector 每種基本元件各有一個分頁,依序走過一遍。
|
||||
|
||||
**Tools。**一個項目:`add`,描述為 *Add two numbers.*。表單有一個必填的整數欄位 `a`,另一個是 `b`。填好、呼叫,結果是 `3`。Inspector 是從 `a: int, b: int` 建出那張表單的,其他每個用戶端也一樣。
|
||||
|
||||
**Resources。**這裡的 *Resources* 清單是空的。`greeting` 在 **Resource Templates** 底下,因為 `greeting://{name}` 帶有參數:在有人提供 `name` 之前,沒有單一資源可以列出。給它 `World` 然後讀取:
|
||||
|
||||
```text
|
||||
Hello, World!
|
||||
```
|
||||
|
||||
**Prompts。**一個項目:`summarize`,只有一個必填的 `text` 引數。帶一段文字去取得它,會收到一則 `role: user` 的訊息,內容就是你組好的字串。提示詞就只是這樣:一個組出訊息的函式。
|
||||
|
||||
Inspector 透過 **stdio** 執行你的伺服器,這是 MCP 伺服器能使用的傳輸方式之一。現在還不用選;那是 **[執行伺服器](../run/index.md)** 那一頁的事。
|
||||
|
||||
## 能力 {#capabilities}
|
||||
|
||||
在 Inspector 裡看到了三個分頁。它怎麼知道有三個?
|
||||
|
||||
用戶端連線時,伺服器會宣告自己的**能力**:它會回應哪幾類請求。用戶端據此決定究竟該要求什麼。這份宣告不是你寫的,`MCPServer` 替你宣告好了。
|
||||
|
||||
自己看看吧。SDK 的 `Client` 直接接受伺服器物件,並在**記憶體內**與它連線(沒有子處理程序,沒有連接埠):
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
print(client.server_capabilities.model_dump(exclude_none=True))
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```text
|
||||
{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}}
|
||||
```
|
||||
|
||||
那個字典就是伺服器宣告的**能力**,也是每個連線進來的用戶端最先得知的事:
|
||||
|
||||
| 能力 | 用戶端現在可以呼叫 |
|
||||
|-------------|------------------------------------------------------------|
|
||||
| `tools` | `tools/list`, `tools/call` |
|
||||
| `resources` | `resources/list`, `resources/templates/list`, `resources/read` |
|
||||
| `prompts` | `prompts/list`, `prompts/get` |
|
||||
|
||||
`MCPServer` 三種基本元件都提供,所以三者永遠都會宣告。
|
||||
|
||||
注意少了什麼。`completions`(資源範本和提示詞的引數自動完成)需要你寫一個處理函式,這個伺服器沒有,所以這項能力不存在,守規矩的用戶端也不會去問。所有選用的東西都照這條規則:註冊了,能力就出現;**[自動完成](../servers/completions.md)** 會證明這一點。
|
||||
|
||||
!!! info
|
||||
`Client(mcp)` 就是這份文件裡每個範例測試時用的同一個記憶體內用戶端,你也會用它來測試自己的。它有專屬的一整頁:**[測試](testing.md)**。
|
||||
|
||||
## 你沒寫的東西 {#what-you-did-not-write}
|
||||
|
||||
回頭看這一頁。你寫了三個小小的 Python 函式。你**沒有**寫:
|
||||
|
||||
* JSON Schema。`a: int, b: int` **就是** `add` 的 schema。
|
||||
* 請求處理函式。`tools/list`、`resources/read`、`prompts/get`:全都替你處理好了。
|
||||
* 能力宣告。`MCPServer` 替你做了。
|
||||
* 任何一行協定。版本協商、JSON-RPC 訊框、能力交換:全都發生在 `mcp dev` 和 `Client(mcp)` 裡面,你完全沒看到。
|
||||
|
||||
這個比例正是 SDK 的意義所在。
|
||||
|
||||
## 重點回顧 {#recap}
|
||||
|
||||
* **主機**是 LLM 應用程式,**用戶端**是它講 MCP 的那一半,**伺服器**是你打造的東西。
|
||||
* 工具由**模型**控制,資源由**應用程式**控制,提示詞由**使用者**控制。
|
||||
* 每種基本元件一個裝飾器:`@mcp.tool()`、`@mcp.resource(uri)`、`@mcp.prompt()`。名稱、描述和 schema 都來自函式。
|
||||
* 帶 `{param}` 的 URI 會產生資源**範本**,和具體資源分開列出。
|
||||
* 伺服器的**能力**會替你宣告好,而用戶端只會要求伺服器宣告過的東西。
|
||||
* `Client(mcp)` 在記憶體內連上伺服器物件:從第一天起就是你的測試工具。
|
||||
|
||||
接下來是 **[連接真正的主機](real-host.md)**:把這個伺服器真的放進 Claude Desktop 或 IDE 裡。然後是 **[測試](testing.md)**:一頁、一個記憶體內用戶端,從此不用猜它到底能不能動。再之後,每種基本元件各有自己的一頁,從模型主導的那個開始:**[工具](../servers/tools.md)**。
|
||||
53
i18n/zh-hant/pages/get-started/index.md
Normal file
53
i18n/zh-hant/pages/get-started/index.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
translation:
|
||||
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
|
||||
tool: 1
|
||||
---
|
||||
# 開始使用 {#get-started}
|
||||
|
||||
剛接觸 MCP,或剛接觸這個 SDK?從這裡開始。這幾頁會帶你從零開始,做出一個能運作、經過測試的伺服器:[安裝 SDK](installation.md)、建立[第一個伺服器](first-steps.md)、[把它接上真正的 MCP 主機(host)](real-host.md),再用記憶體內用戶端[測試它](testing.md)。
|
||||
|
||||
## 執行程式碼 {#run-the-code}
|
||||
|
||||
所有程式碼區塊都可以直接複製使用:每一個都是完整、可運作的檔案。
|
||||
|
||||
想跟著做的話,把程式碼區塊貼進 `server.py`,然後用 MCP Inspector 開啟:
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
**強烈建議**自己寫下(或複製)程式碼、動手修改,並在本機執行。在自己的編輯器裡用過,才真正看得出重點在哪:要寫的東西有多少、自動完成的體驗,以及型別檢查在執行之前就幫你抓出錯誤。
|
||||
|
||||
## 不需要猜 {#you-will-not-be-guessing}
|
||||
|
||||
這份說明文件裡的每個範例,都是 SDK 自己的儲存庫中 [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) 底下的完整檔案,而且每一個都由 SDK 的測試套件透過**記憶體內用戶端**實際跑過:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from mcp import Client
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_add() -> None:
|
||||
async with Client(mcp) as client:
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
assert result.structured_content == {"result": 3}
|
||||
```
|
||||
|
||||
沒有子處理程序、沒有連接埠、沒有傳輸方式。`Client(mcp)` 直接連到伺服器物件。
|
||||
|
||||
如果 SDK 的某個改動弄壞了這幾頁上的範例,CI 會比頁面先亮紅燈。你在這裡讀到的程式碼,就是實際執行的程式碼。
|
||||
|
||||
在[測試](testing.md)那一頁你會自己用到這個做法;測試自己的伺服器時也是這樣做。
|
||||
|
||||
## 接下來往哪裡走 {#where-to-go-next}
|
||||
|
||||
伺服器跑起來之後,這份說明文件的其他部分是參考資料,不是課程。每一頁都可以獨立閱讀,直接跳到需要的地方即可:
|
||||
|
||||
* 伺服器對外提供什麼(工具、資源、提示詞),請見 **[伺服器](../servers/index.md)**。
|
||||
* 註冊的函式裡有哪些東西可用,請見 **[在處理函式內部](../handlers/index.md)**。
|
||||
* 怎麼把伺服器交到用戶端面前(stdio、HTTP、現有的 FastAPI 應用程式),請見 **[執行伺服器](../run/index.md)**。
|
||||
* 打造另一端,也就是**使用** MCP 伺服器的應用程式,請見 **[用戶端](../client/index.md)**。
|
||||
45
i18n/zh-hant/pages/get-started/installation.md
Normal file
45
i18n/zh-hant/pages/get-started/installation.md
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
translation:
|
||||
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
|
||||
tool: 1
|
||||
---
|
||||
# 安裝 {#installation}
|
||||
|
||||
Python SDK 在 PyPI 上的套件名稱是 [`mcp`](https://pypi.org/project/mcp/),需要 **Python 3.10+**。
|
||||
|
||||
這份文件描述的是 **v2**,也就是目前的穩定版本線:
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add "mcp[cli]"
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install "mcp[cli]"
|
||||
```
|
||||
|
||||
!!! note "從 v1 過來的嗎?"
|
||||
v2 是含有破壞性變更的主要版本,**[遷移指南](../migration.md)**逐一說明了每一項變更。如果你的**套件**依賴 `mcp` 而且還沒準備好遷移,請保留 `<2` 的版本上限(例如 `mcp>=1.28,<2`),這樣沒有鎖定版本的解析結果就會停留在 1.x 版本線。
|
||||
|
||||
## 會安裝哪些東西 {#what-gets-installed}
|
||||
|
||||
使用 SDK 不需要知道這些,但如果你好奇每個相依套件的用途:
|
||||
|
||||
* `mcp-types`:所有協定型別(請求、結果、內容區塊)獨立成一個套件,版本與 SDK 同步。依賴 `mcp` 的程式碼透過 `mcp.types` 這個別名匯入(這份文件裡每一個 `from mcp.types import ...` 都是如此);只有在安裝了 `mcp-types` 但沒有安裝 SDK 的專案裡,才直接匯入 `mcp_types`。
|
||||
* [`anyio`](https://anyio.readthedocs.io/):非同步執行環境。整個 SDK 都是基於 anyio 寫的,所以在 `asyncio` 或 `trio` 上都能執行。
|
||||
* [`pydantic`](https://docs.pydantic.dev/):每個 `mcp.types` 模型的基礎,也負責所有 schema 的產生與驗證。
|
||||
* [`httpx2`](https://pypi.org/project/httpx2/):Streamable HTTP 和 SSE **用戶端**傳輸背後的 HTTP 用戶端,內建 server-sent events 支援。
|
||||
* [`starlette`](https://www.starlette.io/)、[`uvicorn`](https://www.uvicorn.org/)、[`sse-starlette`](https://pypi.org/project/sse-starlette/) 和 [`python-multipart`](https://pypi.org/project/python-multipart/):HTTP **伺服器**傳輸。
|
||||
* [`jsonschema`](https://pypi.org/project/jsonschema/):依照工具宣告的輸出 schema 驗證它的結構化輸出。
|
||||
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/):授權用的 OAuth 權杖處理。
|
||||
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/):只有輕量的 API,所以除非你自己安裝 OpenTelemetry SDK 和匯出器,否則 SDK 的追蹤中介軟體不會帶來任何負擔。
|
||||
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) 和 [`typing-inspection`](https://pypi.org/project/typing-inspection/):讓 Python 3.10 也能使用新的型別功能。
|
||||
* [`pywin32`](https://pypi.org/project/pywin32/):僅限 Windows,用於 `stdio` 子處理程序管理。
|
||||
|
||||
## 選用的 extra {#optional-extras}
|
||||
|
||||
* `mcp[cli]` 會加裝 [`typer`](https://typer.tiangolo.com/) 和 [`python-dotenv`](https://pypi.org/project/python-dotenv/),供 `mcp` 命令列工具使用(`mcp dev`、`mcp run`、`mcp install`)。開發期間會用到;部署後的伺服器可能就不需要了。
|
||||
* `mcp[rich]` 會加裝 [`rich`](https://rich.readthedocs.io/),讓伺服器記錄更好看。
|
||||
168
i18n/zh-hant/pages/get-started/real-host.md
Normal file
168
i18n/zh-hant/pages/get-started/real-host.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
|
||||
tool: 1
|
||||
---
|
||||
# 連接到真正的主機 {#connect-to-a-real-host}
|
||||
|
||||
**主機(host)** 指的是伺服器最後會被放進去的那個應用程式:Claude Desktop、Claude Code、IDE。使用者直接面對、互動的就是主機。在主機內部,MCP **用戶端**會把你的伺服器當成子處理程序啟動,並透過該處理程序的 stdin 和 stdout 與它溝通。
|
||||
|
||||
也就是說,連接到主機只有一個動作:告訴它**啟動伺服器的指令**。這一頁上的所有內容(兩個 CLI 指令、三個 JSON 檔案),都只是放這同一道指令的不同位置。
|
||||
|
||||
## 一個伺服器,所有主機 {#one-server-every-host}
|
||||
|
||||
```python title="server.py" hl_lines="3 33-34"
|
||||
--8<-- "docs_src/real_host/tutorial001.py"
|
||||
```
|
||||
|
||||
兩個工具加一個資源,全在一個檔案裡。這個檔案有三件事對下面每個主機都很重要:
|
||||
|
||||
* `mcp.run()` 不帶引數時會啟動 **stdio** 伺服器:它會阻塞,從 stdin 讀取協定訊息,並把訊息寫到 stdout。這一頁上每個主機說的都是這種傳輸方式。主機把你的檔案當成子處理程序啟動,並掌管這兩條管道,所以連接永遠只是「指令在這裡」。不需要挑連接埠,也沒有任何東西在監聽連接埠。
|
||||
* `run()` 放在 `if __name__ == "__main__":` 底下。下面所有做法都是**匯入**這個檔案而不是執行它,所以沒有這層保護的 `run()` 會在任何東西載入模組的那一刻就啟動伺服器。
|
||||
* 伺服器物件是模組層級的全域變數,名稱是 `mcp`。`mcp run` 找的就是這個名稱(`server` 和 `app` 也可以)。如果取別的名字,就要明確指定:`mcp run server.py:bookshop`。
|
||||
|
||||
這是這一頁最後一行 Python。從這裡往下全都是主機設定。
|
||||
|
||||
## 啟動指令 {#the-launch-command}
|
||||
|
||||
下面每個主機拿到的都是同一道指令:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
所有主機共用一道指令,是因為 `uv run --with` 會當場把 SDK 解析進一個全新的環境:從任何目錄都能執行,不需要專案,也不需要啟用虛擬環境。這一點在這裡比任何地方都重要,因為主機是從**它自己**的工作目錄、帶著幾乎空白的環境來啟動伺服器,而不是從你的 shell。
|
||||
|
||||
這也是 `mcp install` 替你寫進 Claude Desktop 設定檔的指令(見下文),所以手動輸入的和工具產生的會一致,差別只在工具多加了精確的版本鎖定。
|
||||
|
||||
!!! tip "如果主機找不到 `uv`"
|
||||
主機用極簡的 `PATH` 產生你的伺服器處理程序,`uv` 可能不在裡面。把單獨的 `uv` 換成 `which uv`(macOS/Linux)或 `where uv`(Windows)給出的絕對路徑。`mcp install` 寫的正是這個。
|
||||
|
||||
!!! note "這一頁講的是本機情境"
|
||||
這裡的一切都是在主機所在的那台機器上執行伺服器:主機透過 stdio 啟動你的檔案。對個人用或單機工具來說,這完全正確。要把伺服器交給**沒有**你這個檔案的人,給出去的是 **URL** 而不是指令:同一個 `mcp` 物件,改用 Streamable HTTP 提供服務。**[執行伺服器](../run/index.md)** 用一張表講清楚這個抉擇,**[部署與擴展](../run/deploy.md)** 則是從那裡走到真正主機名稱的路。
|
||||
|
||||
而主機不過就是內含 MCP 用戶端的應用程式,所以你自己的 Python 也能扮演主機的角色:**[用戶端傳輸方式](../client/transports.md)** 用 `stdio_client(...)` 把同一個檔案當成子處理程序啟動,**[測試](testing.md)** 則在記憶體內連接它,完全不需要處理程序。
|
||||
|
||||
## Claude Desktop {#claude-desktop}
|
||||
|
||||
SDK 唯一能替你設定的主機:
|
||||
|
||||
```bash
|
||||
uv run mcp install server.py
|
||||
```
|
||||
|
||||
就這樣。`mcp install` 會匯入檔案來讀取伺服器名稱,找到 Claude Desktop 的設定檔,然後把啟動指令寫進去。過程中它會把你的路徑轉成絕對路徑,不用自己動手。
|
||||
|
||||
沒什麼神祕的。它寫進去的項目長這樣:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Bookshop": {
|
||||
"command": "/absolute/path/to/uv",
|
||||
"args": [
|
||||
"run",
|
||||
"--frozen",
|
||||
"--with",
|
||||
"mcp[cli]==2.0.0",
|
||||
"mcp",
|
||||
"run",
|
||||
"/absolute/path/to/server.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
這就是上一節的啟動指令,外加三樣東西:`uv` 的絕對路徑、`--frozen`(讓 `uv` 永遠不會改寫它剛好碰到的 lockfile),以及精確鎖定在你已安裝的 `mcp` 版本。它會寫進 `claude_desktop_config.json`,這個檔案位於:
|
||||
|
||||
* **macOS**:`~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
* **Windows**:`%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
這個檔案可以手寫。`mcp install` 存在的意義,是讓你手寫時不會犯那個經典錯誤(相對路徑)。
|
||||
|
||||
完全結束 Claude Desktop(不只是關掉視窗),再重新開啟。
|
||||
|
||||
!!! warning
|
||||
如果 Claude Desktop 的設定**目錄**還不存在,`mcp install` 會以 `Claude app not found` 失敗。安裝 Claude Desktop 並執行一次:目錄就是這樣建立的。
|
||||
|
||||
!!! tip
|
||||
Claude Desktop 在它自己的處理程序裡啟動你的伺服器,所以 shell 的環境變數不會在那裡。`uv run mcp install server.py -v API_KEY=abc123`(或 `-f .env`)會把它們記錄在項目的 `env` 欄位裡。`--name` 可以覆寫項目名稱;預設為伺服器的 `name`。
|
||||
|
||||
## Claude Code {#claude-code}
|
||||
|
||||
沒有檔案要編輯。用 `claude` CLI 註冊伺服器;`--` 之後的全部都是啟動指令。
|
||||
|
||||
```bash
|
||||
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
在 Claude Code 工作階段裡執行 `/mcp`,確認 `bookshop` 已連線且列出了它的工具。
|
||||
|
||||
## Cursor {#cursor}
|
||||
|
||||
在專案根目錄建立 `.cursor/mcp.json`。
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"bookshop": {
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
同樣的 `command` 加 `args`,放在 Claude Desktop 也在用的同一個 `mcpServers` 鍵底下。伺服器會出現在 Cursor 的 MCP 設定裡,兩個工具都會列出來。
|
||||
|
||||
## VS Code {#vs-code}
|
||||
|
||||
在專案根目錄建立 `.vscode/mcp.json`。
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"bookshop": {
|
||||
"type": "stdio",
|
||||
"command": "uv",
|
||||
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
和 Cursor 的檔案只有兩處不同,就這兩處:外層的鍵是 `servers` 而不是 `mcpServers`,而且每個項目都要宣告 `type`。確認信任提示後,在命令選擇區執行 **MCP: List Servers**,就會看到 `bookshop` 正在執行。
|
||||
|
||||
!!! note
|
||||
需要 VS Code 1.99 以上,並安裝已登入的 **GitHub Copilot** 延伸模組(Copilot Free 就夠了),而且 Copilot Chat 必須在 **Agent** 模式,因為其他模式都不會呼叫工具。
|
||||
|
||||
## 沒有出現 {#it-doesnt-show-up}
|
||||
|
||||
動任何主機設定之前,先自己執行一次啟動指令:
|
||||
|
||||
```bash
|
||||
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
|
||||
```
|
||||
|
||||
什麼都不會印出,也不會結束返回。這種沉默是正確的:stdio 伺服器正在等主機先從 stdin 開口(按 `Ctrl-C` 停止)。出現 traceback 或立刻結束才是真正的 bug,而現在可以直接讀到它,不用隔著主機瞎猜。
|
||||
|
||||
一旦這道指令乖乖停在那裡等,剩下的問題幾乎一定是這三件事之一:
|
||||
|
||||
* **相對路徑。** 主機是從**它自己**的工作目錄啟動伺服器,不是你註冊時所在的目錄。該寫 `/absolute/path/to/server.py` 卻寫成 `server.py`,是最常見的失敗原因。如果主機也找不到 `uv`,那個路徑也得是絕對路徑。
|
||||
* **主機還在用舊的設定。** 主機在啟動時讀取設定。特別是 Claude Desktop,必須**完全結束**(不只是關掉視窗)再重新開啟,對 `claude_desktop_config.json` 的修改才會生效。
|
||||
* **有東西在轉向的時段之外寫到了 stdout。** 在 stdio 上,stdout **就是**協定。SDK 在提供服務期間會把已 flush 的雜散輸出轉到 stderr,但在那之前就 flush 到 stdout 的輸出(包裝腳本的 echo、未緩衝處理程序中匯入階段的 `print()`),或是在直譯器結束時才排出的緩衝 `print()`,都會交給主機一則損壞的訊息,主機就會斷線。用預設的 `logging` 設定來記錄,它的 stderr handler 會逐筆 flush;自訂 handler 也必須避開 stdout。完整說明請見 **[記錄](../handlers/logging.md)**。
|
||||
|
||||
Claude Desktop 會為每個伺服器各留一份記錄:`mcp-server-<NAME>.log` 是伺服器的 stderr,旁邊的 `mcp.log` 記錄連線,macOS 在 `~/Library/Logs/Claude` 底下,Windows 在 `%APPDATA%\Claude\logs`。
|
||||
|
||||
超出這三件事的問題,請見 **[疑難排解](../troubleshooting.md)**。
|
||||
|
||||
## 重點回顧 {#recap}
|
||||
|
||||
* **主機**(Claude Desktop、IDE)執行一個 MCP 用戶端,透過 stdio 把你的伺服器當成子處理程序啟動。連接就是給它一道啟動指令。
|
||||
* 這道指令是 `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`:不用啟用 venv,從任何目錄都能執行。
|
||||
* **Claude Desktop** 是 `mcp install` 唯一能替你設定的主機。它把同一道指令(加上 `uv` 的絕對路徑、`--frozen`,以及精確鎖定你已安裝的版本)寫進 `claude_desktop_config.json`,你永遠不必自己動手。
|
||||
* **Claude Code** 是 `claude mcp add bookshop -- <launch command>`。**Cursor** 是 `.cursor/mcp.json`,放在 `mcpServers` 底下。**VS Code** 是 `.vscode/mcp.json`,放在 `servers` 底下,每個項目都有 `type`。
|
||||
* 到處都用絕對路徑,改完設定後重新啟動主機,而且除了 SDK 之外,絕不讓任何東西寫到 stdout。
|
||||
|
||||
這一頁上每個主機都連到同一個檔案,用的是同一道指令。這個檔案能**公開**什麼,就是這份文件其餘的內容:**[工具](../servers/tools.md)**、**[資源](../servers/resources.md)**,以及 **[執行伺服器](../run/index.md)** 裡 stdio 以外的每一種傳輸方式。
|
||||
96
i18n/zh-hant/pages/get-started/testing.md
Normal file
96
i18n/zh-hant/pages/get-started/testing.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
translation:
|
||||
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
|
||||
tool: 1
|
||||
---
|
||||
# 測試 {#testing}
|
||||
|
||||
Python SDK 附帶一個 `Client` 類別,內建**記憶體內傳輸**:把伺服器物件傳給它,它就會直接連上去。
|
||||
|
||||
沒有子處理程序,沒有連接埠,根本沒有傳輸層。概念和 FastAPI 的 `TestClient` 一樣。
|
||||
|
||||
## 基本用法 {#basic-usage}
|
||||
|
||||
假設有一個簡單的伺服器,只有一個工具:
|
||||
|
||||
```python title="server.py"
|
||||
--8<-- "docs_src/testing/tutorial001.py"
|
||||
```
|
||||
|
||||
要執行下面的測試,需要兩個額外的(開發用)相依套件:
|
||||
|
||||
=== "uv"
|
||||
|
||||
```bash
|
||||
uv add --dev pytest inline-snapshot
|
||||
```
|
||||
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install pytest inline-snapshot
|
||||
```
|
||||
|
||||
!!! info
|
||||
這份說明文件假設你已經會用 [`pytest`](https://docs.pytest.org/en/stable/)。
|
||||
|
||||
下面的測試用 [`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) 來在一行內對整個結果物件做斷言。它會把測試的輸出記錄成你看到的 `snapshot(...)` 字面值。如果不想用它,拿掉 import,像其他測試一樣對你在意的欄位做斷言(`result.content[0].text == "3"`)即可。
|
||||
|
||||
接著是測試:
|
||||
|
||||
```python title="test_server.py"
|
||||
import pytest
|
||||
from inline_snapshot import snapshot
|
||||
from mcp import Client
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
from server import mcp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend(): # (1)!
|
||||
return "asyncio"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(): # (2)!
|
||||
async with Client(mcp, raise_exceptions=True) as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_call_add_tool(client: Client):
|
||||
result = await client.call_tool("add", {"a": 1, "b": 2})
|
||||
# Drop the server identity stamp in `_meta`; it is not what this test is about.
|
||||
result.meta = None
|
||||
assert result == snapshot(
|
||||
CallToolResult(
|
||||
content=[TextContent(type="text", text="3")],
|
||||
structured_content={"result": 3},
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
1. 如果用的是 `trio`,改成回傳 `"trio"`。細節請見 [anyio 說明文件](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on)。
|
||||
2. 這個 fixture 會 yield 一個已連線的用戶端。每個接收 `client` 的測試都會拿到一條連到同一個伺服器的全新記憶體內連線。
|
||||
|
||||
這樣就完成了!現在可以擴充測試,涵蓋更多情境。
|
||||
|
||||
## 為什麼要 `raise_exceptions=True`? {#why-raise_exceptionstrue}
|
||||
|
||||
可能出錯的地方有兩種,而這個旗標只影響其中一種。
|
||||
|
||||
**你的工具**內部引發的例外不算協定失敗。它會變成一個帶有 `is_error=True` 的正常結果,模型會讀到那則訊息。`raise_exceptions` 不會改變這一點:不管有沒有設定,`call_tool` 都回傳同樣的 `is_error=True` 結果。這部分有一整頁的說明:**[處理錯誤](../servers/handling-errors.md)**。
|
||||
|
||||
發生在工具本體**之外**的失敗就不一樣了。在 `Client(mcp)` 給你的連線上,伺服器會先把它淨化成通用的 `"Internal server error"`,用戶端才看得到。意外當掉的細節本來就不該洩漏給遠端呼叫端。但在測試裡,這正是你**不**想要的,也正是 `raise_exceptions=True` 改變的地方:測試會看到真正的訊息,而不是淨化過的版本。
|
||||
|
||||
測試裡就開著它。在正式環境的程式碼裡它沒有任何意義。
|
||||
|
||||
## 預設為處理程序內連線 {#in-process-by-default}
|
||||
|
||||
!!! note
|
||||
`Client(mcp)` 以處理程序內的方式連線,而且預設是**世代中立**的:它會探測伺服器,選出合適的協定路徑。如果測試要驗證舊版特有的語意(取樣(sampling)或徵詢(elicitation)的推送、`message_handler`),就固定用 `mode="legacy"`,並且在那裡拿掉 `raise_exceptions=True`:舊版連線本來就不會淨化,而這個旗標會讓失敗在伺服器任務裡重新引發,而不是在你的測試裡。
|
||||
|
||||
也正是因為這一行,這份說明文件才敢保證範例都能跑:每個範例檔案都由 SDK 自己的測試套件實際執行過,幾乎全部都是透過這個用戶端。你用的工具和 SDK 用在自己身上的是同一個。
|
||||
|
||||
現在有了一個可以運作、也測試過的伺服器。要把它放進真正的應用程式(Claude Desktop、IDE)裡,請見 **[連接真實的主機(host)](real-host.md)**;其他所有提供服務的方式請見 **[執行伺服器](../run/index.md)**。
|
||||
Loading…
Reference in a new issue