跳轉至

處理錯誤

機器翻譯

本頁是從英文說明文件自動翻譯而來,以英文頁面為準。如果哪裡讀起來不對勁,翻譯有說明如何回報。

工具失敗的方式有兩種,而 SDK 對待它們的方式截然不同。

引發一般的例外,看到的是模型。引發 MCPError,看到的是協定

這一頁談的是怎麼選。

模型能修正的錯誤

拿一個查東西的工具來說,讓查詢落空:

server.py
from mcp.server import MCPServer

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.tool()
def get_author(title: str) -> str:
    """Look up the author of a book in the catalog."""
    if title not in CATALOG:
        raise ValueError(f"No book titled {title!r} in the catalog.")
    return CATALOG[title]

這兩行跟 MCP 一點關係也沒有。get_author 引發的是普通的 ValueError,任何 Python 函式都會這麼做。

用一個不在目錄裡的書名呼叫它,看看結果:

result.is_error            # True
result.content             # [TextContent(text="Error executing tool get_author: No book titled 'Nothing' in the catalog.")]
result.structured_content  # None
  • 請求成功了。有結果;呼叫端沒有引發任何東西。
  • is_errorTrue,而例外的訊息(前面加上工具名稱)就在 content 裡,正是模型讀取的地方。
  • structured_contentNone。失敗的呼叫沒有回傳值可以結構化。

這是工具錯誤,也是工具引發任何例外時的預設行為。而且幾乎總是你想要的。

呼叫工具的是模型,引數也是它選的。所以工具錯誤就是對話中的一個回合:模型讀到「No book titled 'Nothing' in the catalog.」,發現自己猜錯了書名,就換個更好的再呼叫一次。只寫了一個 raise,就得到一個會自我修正的 agent。

Tip

永遠不要從工具 return 錯誤訊息。回傳的字串 is_error=False,所以在模型(以及每個用戶端 UI)看來,工具是成功的,那個字串就是答案。要用 raise。那個旗標才是訊號。

模型無法修正的錯誤

現在把 ValueError 換成 MCPError

server.py
from mcp import MCPError
from mcp.server import MCPServer
from mcp.types import INVALID_PARAMS

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.tool()
def get_author(title: str) -> str:
    """Look up the author of a book in the catalog."""
    if title not in CATALOG:
        raise MCPError(code=INVALID_PARAMS, message=f"No book titled {title!r} in the catalog.")
    return CATALOG[title]

MCPError 是 SDK 的協定錯誤。它是工具包裝層唯一不會攔截的例外:它會往外傳播,整個 tools/call 請求以 JSON-RPC 錯誤失敗,而不是回傳結果。

{
  "code": -32602,
  "message": "No book titled 'Nothing' in the catalog."
}
  • 沒有結果。沒有 content,沒有 is_error:模型沒有東西可讀。
  • 錯誤改由主機(host)應用程式收到,跟工具根本不存在時一模一樣。
  • codemessagedata 原封不動地送達。INVALID_PARAMS-32602mcp.types 把它和其他 JSON-RPC 錯誤碼(INVALID_REQUESTINTERNAL_ERROR……)都匯出成常數,所以永遠不用手打魔術數字。

Check

同樣的查詢、同樣落空,但現在呼叫在用戶端引發例外,而不是回傳:

mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.

第一個版本交給模型一句它能回應的話。這個版本什麼都沒給。對 get_author 來說這絕對更糟,而這正是下一節的重點。

該引發哪一個

兩條路徑回答的是兩個不同的問題。

  • 引發任何例外,用於執行上的失敗:工具想做的事沒做成。呼叫是模型選的,所以模型應該看到後果,並有機會補救。拼錯的書名、逾時的上游 API、不存在的資料列:都是工具錯誤。
  • 引發 MCPError,用於請求本身就該被拒絕的情況:用戶端缺少工具所依賴的能力、伺服器處於無法服務任何人的狀態、呼叫端跳過了必要的步驟。模型再怎麼重試也修不好這些,所以把訊息交給它毫無益處。

一個問題就能決定:更聰明的模型能避開這個錯誤嗎?能 -> 一般的例外。不能 -> MCPError

照這個標準,第二版的 get_author 選錯了:換個更好的書名就能解決,所以模型理應看到訊息。放在那裡是為了示範機制,不是建議這麼做。

Info

MCPError 位於 from mcp import MCPError,接受 codemessage 和選用的 data 承載。放進去什麼,用戶端就收到什麼:SDK 會把引發的 MCPError 原封不動地轉送,不會加以清理。

不存在的資源

資源也畫了同一條線,並為常見情況提供了一個具名的例外。

server.py
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError

mcp = MCPServer("Bookshop")

CATALOG = {"Dune": "Frank Herbert", "Neuromancer": "William Gibson"}


@mcp.resource("books://{title}")
def book(title: str) -> str:
    """The catalog entry for one book."""
    if title not in CATALOG:
        raise ResourceNotFoundError(f"No book titled {title!r} in the catalog.")
    return f"{title} by {CATALOG[title]}"

books://{title} 是一個範本。它能比對任何書名,所以「URI 格式正確」和「這本書存在」是兩個不同的問題,而只有你的函式能回答第二個。

回答不了的時候,引發 ResourceNotFoundError。SDK 會把它轉成規格指派給缺漏資源的協定錯誤:-32602,並把請求的 URI 放在 data 裡,讓用戶端知道是哪一次讀取失敗。

{
  "code": -32602,
  "message": "No book titled 'Nothing' in the catalog.",
  "data": {"uri": "books://Nothing"}
}

注意這裡沒有 is_error=True 那種半成品結果。資源讀取要嘛回傳內容,要嘛失敗:資源只有協定這條路。範本以及資源的其他一切都在 資源

永遠不用引發的錯誤

錯誤的引數永遠到不了你的函式。

傳給 get_author 一個不是字串的 title,SDK 會在呼叫你之前就依輸入 schema 拒絕它,同樣是模型能讀懂並修正的那種 is_error=True 工具錯誤。工具Field(le=50) 限制示範了同樣的拒絕。

這表示有一整類 raise 陳述式不用寫:不要重新驗證自己的型別提示。

Info

這一頁的一切都是用戶端看到的東西,而寫測試用的記憶體內 Client 看到的完全一樣。就算是 raise_exceptions=True 也不會把工具錯誤變回 traceback:等到那個旗標能起作用時,你的例外早已是 is_error=True 的結果。對結果做斷言。測試 說明了這個模式。

重點回顧

  • 在工具裡引發任何例外 -> 呼叫回傳 is_error=True,訊息在 content 裡。模型讀到後可以重試。這是預設行為。
  • 引發 MCPError -> 呼叫本身以 JSON-RPC 錯誤失敗。模型什麼都看不到;由主機處理。codemessagedata 完整保留。
  • 決定性的問題:「更聰明的模型能避開這個錯誤嗎?」能 -> 例外。不能 -> MCPError
  • 資源處理函式引發的 ResourceNotFoundError -> 協定的 -32602,URI 在 data 裡。
  • 錯誤的引數會在函式執行前依 schema 被拒絕;這些不用 raise
  • from mcp import MCPError;錯誤碼常數來自 mcp.types

錯誤處理完畢。這就是伺服器公開的全部內容。每個處理函式在執行時能讀到什麼、又能反過來對用戶端做什麼,是下一節的主題:在處理函式內部

最常碰到的 SDK 錯誤的確切文字、各自的意思,以及每一個的一步修正法,都在 疑難排解