python-sdk/i18n/ja/pages/servers/handling-errors.md

131 lines
10 KiB
Markdown

---
translation:
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
tool: 1
---
# エラーの処理 {#handling-errors}
ツールの失敗には 2 通りあり、SDK はそれぞれをまったく違う形で扱います。
通常の例外を送出すると、**モデル**がそれを目にします。`MCPError` を送出すると、**プロトコル**がそれを目にします。
このページは、そのどちらを選ぶかについてです。
## モデルが直せるエラー {#an-error-the-model-can-fix}
何かを検索するツールを用意し、その検索を空振りさせてみます。
```python title="server.py" hl_lines="11-12"
--8<-- "docs_src/handling_errors/tutorial001.py"
```
この 2 行に MCP らしいところは何もありません。`get_author` は、どんな Python 関数でもそうするように、ただの `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_error` は `True` で、例外のメッセージ(ツール名が前に付きます)が `content` に入っています。まさにモデルが読む場所です。
* `structured_content` は `None` です。失敗した呼び出しには、構造化すべき戻り値がありません。
これが**ツールエラー**で、ツールが送出する「あらゆる」例外のデフォルトの扱いです。そして、ほとんどの場合これこそが望む挙動です。
ツールを呼び出しているのはモデルです。引数を選んだのもモデルです。つまりツールエラーは会話の 1 ターンになります。モデルは「No book titled 'Nothing' in the catalog.」を読み、タイトルを推測し損ねたことに気づき、もっと良いタイトルで呼び直します。`raise` を 1 つ書いただけで、自己修正するエージェントが手に入りました。
!!! tip
ツールからエラーメッセージを `return` しないでください。返された文字列は `is_error=False` なので、モデルにとっても(そしてあらゆるクライアント UI にとっても)ツールは正常に動作し、その文字列が答えだったように見えます。`raise` してください。シグナルはこのフラグです。
## モデルが直せないエラー {#an-error-the-model-cannot-fix}
今度は `ValueError` を `MCPError` に置き換えます。
```python title="server.py" hl_lines="1 3 14"
--8<-- "docs_src/handling_errors/tutorial002.py"
```
`MCPError` は SDK の**プロトコルエラー**です。ツールのラッパーが捕捉**しない**唯一の例外で、そのまま伝播し、`tools/call` リクエスト全体が結果ではなく JSON-RPC エラーで失敗します。
```json
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog."
}
```
* **結果がありません**。`content` も `is_error` もなく、モデルが読めるものは何もありません。
* 代わりに**ホスト**アプリケーションがエラーを受け取ります。ツールがそもそも存在しなかった場合と同じ扱いです。
* `code`、`message`、`data` はそのまま届きます。`INVALID_PARAMS` は `-32602` です。`mcp.types` はこれを含む JSON-RPC のエラーコード(`INVALID_REQUEST`、`INTERNAL_ERROR` など)を定数としてエクスポートしているので、マジックナンバーを手で打つ必要はありません。
!!! check
同じ検索、同じ空振りですが、今度はクライアント側で呼び出しが結果を返す代わりに「送出」します。
```text
mcp.shared.exceptions.MCPError: No book titled 'Nothing' in the catalog.
```
最初のバージョンは、モデルが反応できる一文を渡しました。こちらは何も渡しません。`get_author` にとってこれは明らかに改悪であり、それが次のセクションの要点です。
## どちらを送出するか {#which-one-to-raise}
2 つの経路は、2 つの異なる問いに答えるものです。
* 「実行」の失敗、つまりツールがやろうとしたことがうまくいかなかった場合は、**任意の例外を送出**します。呼び出しを選んだのはモデルなので、モデルがその結果を目にし、立て直す機会を得るべきです。綴りの間違ったタイトル、タイムアウトした上流の API、存在しない行。どれもツールエラーです。
* 「リクエストそのもの」を拒否すべきときは **`MCPError` を送出**します。ツールが依存するケイパビリティをクライアントが持っていない、サーバーが誰にも応答できる状態にない、呼び出し側が必要な手順を飛ばした。どれもモデルが再試行しても直らないので、メッセージを渡しても得るものはありません。
決め手になる問いは 1 つです。**もっと賢いモデルならこれを避けられたか**。はい → 通常の例外。いいえ → `MCPError`。
この基準で見ると、`get_author` の 2 番目のバージョンは選択を誤っています。より良いタイトルで直るのですから、モデルはメッセージを見るべきでした。あれは仕組みを見せるためのもので、推奨するためのものではありません。
!!! info
`MCPError` は `from mcp import MCPError` でインポートでき、`code`、`message`、省略可能な `data` ペイロードを受け取ります。そこに入れた内容がそのままクライアントに届きます。SDK は送出された `MCPError` をサニタイズせず、そのまま転送します。
## 存在しないリソース {#a-resource-that-doesnt-exist}
リソースも同じ線引きをします。そして、よくあるケースのために名前付きの例外を 1 つ用意しています。
```python title="server.py" hl_lines="2 13"
--8<-- "docs_src/handling_errors/tutorial003.py"
```
`books://{title}` は**テンプレート**です。「あらゆる」タイトルにマッチするので、「URI が正しい形式か」と「その本が存在するか」は別の問いであり、2 番目に答えられるのはこの関数だけです。
答えられないときは `ResourceNotFoundError` を送出してください。SDK はこれを、仕様が存在しないリソースに割り当てているプロトコルエラーに変換します。`-32602` で、リクエストされた URI が `data` に入るので、クライアントは「どの」読み取りが失敗したのかがわかります。
```json
{
"code": -32602,
"message": "No book titled 'Nothing' in the catalog.",
"data": {"uri": "books://Nothing"}
}
```
ここには `is_error=True` のような中間的な結果がないことに注目してください。リソースの読み取りは、内容を返すか失敗するかのどちらかです。リソースにはプロトコルの経路しかありません。テンプレートをはじめ、リソースに関するその他すべては **[リソース](resources.md)** にあります。
## 送出する必要のないエラー {#errors-you-never-raise}
不正な引数が関数に届くことはありません。
`get_author` に文字列ではない `title` を送ると、SDK は関数を呼び出す**前に**入力スキーマと照合して拒否します。その結果は同じ種類の `is_error=True` のツールエラーなので、モデルが読んで修正できます。**[ツール](tools.md)** では、`Field(le=50)` 制約で同じ拒否の様子を示しています。
つまり、書かなくてよい `raise` 文がまるごと一群あるということです。自分の型ヒントを改めて検証しないでください。
!!! info
このページの内容はすべて**クライアント**から見えるものです。テストを書くときに使うインメモリの `Client` にも、まったく同じものが見えます。`raise_exceptions=True` でもツールエラーがトレースバックに戻ることはありません。このフラグが作用できる時点では、例外はすでに `is_error=True` の結果になっています。結果に対してアサートしてください。このパターンは **[テスト](../get-started/testing.md)** で扱っています。
## まとめ {#recap}
* ツールの中で**任意の例外**を送出する → 呼び出しは `is_error=True` を返し、メッセージが `content` に入ります。モデルはそれを読み、再試行できます。これがデフォルトです。
* **`MCPError`** を送出する → 呼び出しそのものが JSON-RPC エラーで失敗します。モデルには何も見えず、ホストが対処します。`code`、`message`、`data` はそのまま残ります。
* 決め手の問い:「もっと賢いモデルならこれを避けられたか」。はい → 例外。いいえ → `MCPError`。
* リソースのハンドラーから `ResourceNotFoundError` を送出する → プロトコルの `-32602` になり、URI が `data` に入ります。
* 不正な引数は関数が実行される前にスキーマと照合して拒否されます。そのために `raise` する必要はありません。
* `from mcp import MCPError` でインポートします。エラーコードの定数は `mcp.types` から取得します。
エラーの処理はここまでです。サーバーが「公開する」ものはこれですべてです。すべてのハンドラーが実行中に読み取れるもの、そして実行中にクライアントに対して行えることは、次のセクション **[ハンドラーの中で](../handlers/index.md)** で扱います。
遭遇する可能性が最も高い SDK エラーの正確な文面、それぞれの意味、そしてそれぞれを一手で直す方法は **[トラブルシューティング](../troubleshooting.md)** にあります。