コンテンツにスキップ

エラーの処理

機械翻訳

このページは英語版ドキュメントから自動翻訳されたものであり、正式な版は英語版のページです。不自然な箇所があれば、翻訳についてで報告の方法を説明しています。

ツールの失敗には 2 通りあり、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]

この 2 行に MCP らしいところは何もありません。get_author は、どんな Python 関数でもそうするように、ただの ValueError を送出しているだけです。

カタログにないタイトルで呼び出して、結果を見てみましょう。

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 です。失敗した呼び出しには、構造化すべき戻り値がありません。

これがツールエラーで、ツールが送出する「あらゆる」例外のデフォルトの扱いです。そして、ほとんどの場合これこそが望む挙動です。

ツールを呼び出しているのはモデルです。引数を選んだのもモデルです。つまりツールエラーは会話の 1 ターンになります。モデルは「No book titled 'Nothing' in the catalog.」を読み、タイトルを推測し損ねたことに気づき、もっと良いタイトルで呼び直します。raise を 1 つ書いただけで、自己修正するエージェントが手に入りました。

Tip

ツールからエラーメッセージを return しないでください。返された文字列は is_error=False なので、モデルにとっても(そしてあらゆるクライアント UI にとっても)ツールは正常に動作し、その文字列が答えだったように見えます。raise してください。シグナルはこのフラグです。

モデルが直せないエラー

今度は ValueErrorMCPError に置き換えます。

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."
}
  • 結果がありませんcontentis_error もなく、モデルが読めるものは何もありません。
  • 代わりにホストアプリケーションがエラーを受け取ります。ツールがそもそも存在しなかった場合と同じ扱いです。
  • codemessagedata はそのまま届きます。INVALID_PARAMS-32602 です。mcp.types はこれを含む JSON-RPC のエラーコード(INVALID_REQUESTINTERNAL_ERROR など)を定数としてエクスポートしているので、マジックナンバーを手で打つ必要はありません。

Check

同じ検索、同じ空振りですが、今度はクライアント側で呼び出しが結果を返す代わりに「送出」します。

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

最初のバージョンは、モデルが反応できる一文を渡しました。こちらは何も渡しません。get_author にとってこれは明らかに改悪であり、それが次のセクションの要点です。

どちらを送出するか

2 つの経路は、2 つの異なる問いに答えるものです。

  • 「実行」の失敗、つまりツールがやろうとしたことがうまくいかなかった場合は、任意の例外を送出します。呼び出しを選んだのはモデルなので、モデルがその結果を目にし、立て直す機会を得るべきです。綴りの間違ったタイトル、タイムアウトした上流の API、存在しない行。どれもツールエラーです。
  • 「リクエストそのもの」を拒否すべきときは MCPError を送出します。ツールが依存するケイパビリティをクライアントが持っていない、サーバーが誰にも応答できる状態にない、呼び出し側が必要な手順を飛ばした。どれもモデルが再試行しても直らないので、メッセージを渡しても得るものはありません。

決め手になる問いは 1 つです。もっと賢いモデルならこれを避けられたか。はい → 通常の例外。いいえ → MCPError

この基準で見ると、get_author の 2 番目のバージョンは選択を誤っています。より良いタイトルで直るのですから、モデルはメッセージを見るべきでした。あれは仕組みを見せるためのもので、推奨するためのものではありません。

Info

MCPErrorfrom mcp import MCPError でインポートでき、codemessage、省略可能な data ペイロードを受け取ります。そこに入れた内容がそのままクライアントに届きます。SDK は送出された MCPError をサニタイズせず、そのまま転送します。

存在しないリソース

リソースも同じ線引きをします。そして、よくあるケースのために名前付きの例外を 1 つ用意しています。

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 が正しい形式か」と「その本が存在するか」は別の問いであり、2 番目に答えられるのはこの関数だけです。

答えられないときは 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 は関数を呼び出す前に入力スキーマと照合して拒否します。その結果は同じ種類の is_error=True のツールエラーなので、モデルが読んで修正できます。ツール では、Field(le=50) 制約で同じ拒否の様子を示しています。

つまり、書かなくてよい raise 文がまるごと一群あるということです。自分の型ヒントを改めて検証しないでください。

Info

このページの内容はすべてクライアントから見えるものです。テストを書くときに使うインメモリの Client にも、まったく同じものが見えます。raise_exceptions=True でもツールエラーがトレースバックに戻ることはありません。このフラグが作用できる時点では、例外はすでに is_error=True の結果になっています。結果に対してアサートしてください。このパターンは テスト で扱っています。

まとめ

  • ツールの中で任意の例外を送出する → 呼び出しは is_error=True を返し、メッセージが content に入ります。モデルはそれを読み、再試行できます。これがデフォルトです。
  • MCPError を送出する → 呼び出しそのものが JSON-RPC エラーで失敗します。モデルには何も見えず、ホストが対処します。codemessagedata はそのまま残ります。
  • 決め手の問い:「もっと賢いモデルならこれを避けられたか」。はい → 例外。いいえ → MCPError
  • リソースのハンドラーから ResourceNotFoundError を送出する → プロトコルの -32602 になり、URI が data に入ります。
  • 不正な引数は関数が実行される前にスキーマと照合して拒否されます。そのために raise する必要はありません。
  • from mcp import MCPError でインポートします。エラーコードの定数は mcp.types から取得します。

エラーの処理はここまでです。サーバーが「公開する」ものはこれですべてです。すべてのハンドラーが実行中に読み取れるもの、そして実行中にクライアントに対して行えることは、次のセクション ハンドラーの中で で扱います。

遭遇する可能性が最も高い SDK エラーの正確な文面、それぞれの意味、そしてそれぞれを一手で直す方法は トラブルシューティング にあります。