docs: publish translated docs in twelve languages and the tool that maintains them (#3280)
This commit is contained in:
parent
14aa889992
commit
52ad0a8876
9 changed files with 89737 additions and 54 deletions
122
i18n/ja/pages/servers/completions.md
Normal file
122
i18n/ja/pages/servers/completions.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
translation:
|
||||
sections: [72f9c964769076dd, 9a2c14e10935b515, 235299eb78ab12d7, 8aee1e78c8237fb8, 9bd86acd4112138f, 55343cb7f250dc7b]
|
||||
tool: 1
|
||||
---
|
||||
# 補完 {#completions}
|
||||
|
||||
サーバーの上に UI を構築するクライアントは、ユーザーの入力に合わせて引数の値を自動補完したいと考えます。言語名、リポジトリ名、ファイルパスなどです。
|
||||
|
||||
**補完(completions)**は、サーバーがそうした候補を提供するための仕組みです。
|
||||
|
||||
## 補完する対象を用意する {#something-worth-completing}
|
||||
|
||||
補完が適用されるのはちょうど 2 つだけです。**プロンプト**の引数と、**リソーステンプレート**のパラメーターです。そこで、まずはその両方を 1 つずつ持つサーバーから始めます。
|
||||
|
||||
```python title="server.py" hl_lines="6 12"
|
||||
--8<-- "docs_src/completions/tutorial001.py"
|
||||
```
|
||||
|
||||
ここにはまだ補完に関するものは何もありません。
|
||||
|
||||
* `review_code` は `language` を受け取ります。どの綴りが受け付けられるかをユーザーに推測させるべきではありません。
|
||||
* `github_repo` は `owner` と `repo` を受け取ります。両方とも自由入力のテキストボックスでは、使いにくいフォームになります。
|
||||
|
||||
## 補完ハンドラー {#the-completion-handler}
|
||||
|
||||
`@mcp.completion()` でデコレートした関数を **1 つ**追加します。
|
||||
|
||||
```python title="server.py" hl_lines="21-29"
|
||||
--8<-- "docs_src/completions/tutorial002.py"
|
||||
```
|
||||
|
||||
* ハンドラーはサーバーごとに 1 つです。補完リクエストはすべてここに届くので、何が補完されているかに応じて分岐します。
|
||||
* `async def` でなければなりません。SDK がこれを await します。
|
||||
* 3 つの引数を受け取ります。
|
||||
* `ref`:「どの」プロンプトまたはリソーステンプレートかを表し、`PromptReference` か `ResourceTemplateReference` のどちらかです。見分けるには `isinstance` を使います。
|
||||
* `argument`:`argument.name` は補完対象の引数、`argument.value` はユーザーがこれまでに入力した文字列です。
|
||||
* `context`:すでに解決済みの引数です。今は無視してかまいません。
|
||||
* 戻り値は `Completion(values=[...])`、または提示するものがないときは `None` です。
|
||||
|
||||
!!! tip
|
||||
`argument.value` はユーザーが入力したプレフィックスです。SDK はフィルタリングを**しません**。`values` に入れたものがそのまま UI に表示されます。`startswith` は自分で書きます。
|
||||
|
||||
### 試してみる {#try-it}
|
||||
|
||||
**[テスト](../get-started/testing.md)**で紹介したインメモリの `Client` で動かします。`ref=PromptReference(name="review_code")` と `argument={"name": "language", "value": "py"}` を指定して `client.complete()` を呼び出します。
|
||||
|
||||
```python
|
||||
result.completion.values # ['python']
|
||||
```
|
||||
|
||||
* `ref` はハンドラーが受け取るのと同じ参照型です。
|
||||
* `argument` は `name` と `value` のちょうど 2 つのキーを持つ、普通の dict です。
|
||||
|
||||
空の `value` を送ると、リスト全体が返ってきます。`lang.startswith("")` はどの言語に対しても真だからです。
|
||||
|
||||
```python
|
||||
result.completion.values # ['go', 'javascript', 'python', 'rust', 'typescript']
|
||||
```
|
||||
|
||||
`code`(ハンドラーが認識しない引数)について尋ねると `None` が返り、SDK はそれを空のリストに変換します。
|
||||
|
||||
```python
|
||||
result.completion.values # []
|
||||
```
|
||||
|
||||
`None` は「候補なし」という意味であり、決してエラーではありません。UI は普通のテキストボックスにフォールバックします。
|
||||
|
||||
## 宣言した覚えのないケイパビリティ {#a-capability-you-never-declared}
|
||||
|
||||
ハンドラーを登録すること自体が宣言です。クライアントを接続して確認してみてください。
|
||||
|
||||
```python
|
||||
client.server_capabilities.completions # CompletionsCapability()
|
||||
```
|
||||
|
||||
`completions` をどこにも列挙していません。SDK がハンドラーを見つけて、代わりにケイパビリティを宣言したのです。「オプション」のケイパビリティはすべてこの仕組みで動きます。ハンドラーが宣言そのものです。(3 つのプリミティブはオプションではありません。`MCPServer` はハンドラーの有無にかかわらず常にそれらを宣言します。)
|
||||
|
||||
!!! check
|
||||
最初の `server.py`(ハンドラーのないほう)に戻り、それでも問い合わせてみてください。呼び出しは JSON-RPC エラーで失敗します。
|
||||
|
||||
```text
|
||||
Method not found
|
||||
```
|
||||
|
||||
そして `client.server_capabilities.completions` は `None` です。これこそがケイパビリティの存在意義です。行儀のよいクライアントはこれを確認し、応答できないリクエストは最初から送りません。
|
||||
|
||||
## 依存する引数 {#dependent-arguments}
|
||||
|
||||
`github://repos/{owner}/{repo}` にはパラメーターが 2 つあり、`repo` として意味のある値は、先にどの `owner` が選ばれたかによって変わります。
|
||||
|
||||
そのためにあるのが `context` です。ユーザーが**すでに解決した**引数を運びます。
|
||||
|
||||
```python title="server.py" hl_lines="8-11 34-38"
|
||||
--8<-- "docs_src/completions/tutorial003.py"
|
||||
```
|
||||
|
||||
* 新しい分岐は、テンプレートの `repo` パラメーターに対して実行されます。
|
||||
* `context.arguments` は、これまでに選ばれた値(ここでは `owner`)を持つ `dict[str, str] | None` です。
|
||||
* `owner` がまだなければ意味のある候補も出せないので、ハンドラーは `None` を返します。
|
||||
|
||||
クライアントは、解決済みの値を `context_arguments=` で送ります。今回の `ref` は `ResourceTemplateReference(uri="github://repos/{owner}/{repo}")` です。空の `value` で `repo` を要求し、`context_arguments={"owner": "modelcontextprotocol"}` を渡します。
|
||||
|
||||
```python
|
||||
result.completion.values # ['python-sdk', 'typescript-sdk', 'inspector']
|
||||
```
|
||||
|
||||
`context_arguments=` を外すと、同じ呼び出しが `[]` を返します。ハンドラーは、オーナーがわかるまでどのリポジトリを提示すべきか知りようがありません。
|
||||
|
||||
!!! info
|
||||
`Completion` は `total=` と `has_more=` も受け取ります。`values` がより長いリストの一部であるときに設定すると、UI が「ほか 200 件」のように表示できます。ほとんどのハンドラーには必要ありません。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* 補完は、**プロンプトの引数**と**リソーステンプレートのパラメーター**に対する候補です。それ以外にはありません。
|
||||
* `@mcp.completion()` で唯一のハンドラーを登録します。シグネチャは `async def (ref, argument, context) -> Completion | None` です。
|
||||
* `isinstance(ref, ...)` と `argument.name` で分岐します。`argument.value` によるフィルタリングは自分で行います。
|
||||
* `None` は空のリストになります。決してエラーではありません。
|
||||
* `context.arguments` は解決済みの値を保持し、クライアントはそれを `context_arguments=` として渡します。
|
||||
* `completions` ケイパビリティは、ハンドラーを登録した瞬間に現れます。ハンドラーがなければ、リクエストは `Method not found` になります。
|
||||
|
||||
候補が役立つのは、ユーザーがまだプロンプトやテンプレートを「入力している」あいだです。ツール呼び出しの「途中」でユーザーに質問したいなら、必要なのは**[エリシテーション(elicitation)](../handlers/elicitation.md)**です。ツールがテキスト以外に返せるものはすべて**[画像、音声、アイコン](media.md)**にまとめてあります。
|
||||
131
i18n/ja/pages/servers/handling-errors.md
Normal file
131
i18n/ja/pages/servers/handling-errors.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
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)** にあります。
|
||||
22
i18n/ja/pages/servers/index.md
Normal file
22
i18n/ja/pages/servers/index.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09defc170a0da89d]
|
||||
tool: 1
|
||||
---
|
||||
# サーバー {#servers}
|
||||
|
||||
`MCPServer` は、接続したクライアントに 3 つのプリミティブを公開します。それぞれの違いは、誰がそれを使うと決めるかにあります。
|
||||
|
||||
* **[ツール](tools.md)**は、モデルが選んで呼び出すアクションです。ほとんどの人がまず読みたいのはこのページです。その対になるリファレンスが**[構造化出力](structured-output.md)**で、ツールが返すものの形に関することはすべてそこにまとまっています。
|
||||
* **[リソース](resources.md)**は、アプリケーションが選んで読む、読み取り専用のデータです。その対になるリファレンスが**[URI テンプレート](uri-templates.md)**で、アドレス指定の構文のすべてとパスの安全性に関するルールを扱います。
|
||||
* **[プロンプト](prompts.md)**は、人がメニューやスラッシュコマンドから名前で呼び出すメッセージテンプレートです。
|
||||
|
||||
この 3 つのプリミティブの周辺に、サーバーが宣言するそのほかの要素があります。
|
||||
|
||||
* **[補完](completions.md)**は、プロンプトやリソーステンプレートの引数に対する、サーバー側のオートコンプリートです。
|
||||
* **[画像、音声、アイコン](media.md)**では、ツールがテキスト以外に返せるものすべてと、クライアントがサーバーの横に表示するアイコンを扱います。
|
||||
* **[エラーの処理](handling-errors.md)**では、モデルにとって回復可能なエラーと、モデルに決して見せてはならないエラーの違いを説明します。
|
||||
|
||||
ここにあるページはどれも単独で読めます。必要なページに直接進んでください。まだサーバーを作ったことがなければ、代わりに**[最初のステップ](../get-started/first-steps.md)**から始めてください。
|
||||
|
||||
登録した関数の内側で起きること(`Context`、依存性の注入、呼び出しの途中でユーザーに追加の入力を求めること)を扱うのが、次のセクション「**[ハンドラーの中で](../handlers/index.md)**」です。
|
||||
117
i18n/ja/pages/servers/media.md
Normal file
117
i18n/ja/pages/servers/media.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
---
|
||||
translation:
|
||||
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
|
||||
tool: 1
|
||||
---
|
||||
# メディア {#media}
|
||||
|
||||
ツールが返せるのはテキストだけではありません。
|
||||
|
||||
SDK には、バイナリの結果を扱うヘルパーが 2 つ(**`Image`** と **`Audio`**)と、サーバー、ツール、リソース、プロンプトにクライアントの UI 上での「顔」を与える **`Icon`** 型が用意されています。
|
||||
|
||||
## 画像を返す {#returning-an-image}
|
||||
|
||||
戻り値の型を `Image` と注釈し、ファイルを指定して返します。
|
||||
|
||||
```python title="server.py" hl_lines="8 12 14"
|
||||
--8<-- "docs_src/media/tutorial001.py"
|
||||
```
|
||||
|
||||
* `Image` は `path`(読み込むファイル)か `data`(生のバイト列)のどちらか一方だけを取ります。
|
||||
* クライアントに見える MIME タイプは拡張子から推測されます。`logo.png` は `image/png` として通知されます。
|
||||
* ロゴだからといって特別なことは何もありません。`server.py` の隣にある PNG なら何でも使えます。コードが描画したグラフでも、図でも、写真でもかまいません。
|
||||
|
||||
`Image` は SDK の便利機能であって、プロトコルの型ではありません。実際に送受信されるときには、戻り値は **`ImageContent`** ブロック(ファイルのバイト列を base64 エンコードしたものと MIME タイプ)になります。
|
||||
|
||||
```python
|
||||
result.content # [ImageContent(type="image", data="iVBORw0KGgoAAAANSUhEUg...", mime_type="image/png")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
注目すべき点が 2 つあります。
|
||||
|
||||
* `data` は base64 です。バイト列には一切触れていません。ファイルを読み込んでエンコードしたのは SDK です。
|
||||
* `structured_content` は `None` です。`Image` はモデルが見るためのコンテンツであり、アプリケーションが解析するためのデータではありません。出力スキーマはありません。(戻り値の注釈そのものがスキーマになる **[構造化出力](structured-output.md)** と比べてみてください。)
|
||||
|
||||
!!! info
|
||||
`ImageContent` と `AudioContent` は `mcp.types` にあり、単純な `str` の結果が変換される `TextContent` のすぐ隣に並んでいます(**[ツール](tools.md)**)。ツールの結果はコンテンツブロックのリストです。`Image` と `Audio` は、2 種類のバイナリブロックを作る最短の方法です。
|
||||
|
||||
### 試してみる {#try-it}
|
||||
|
||||
任意の PNG を `server.py` の隣に置いて `logo.png` という名前にし、次を実行してください。
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
**Tools** タブを開いて `logo` を呼び出します。結果は文字列ではありません。`image` コンテンツブロックであり、Inspector が画像を描画します。ディスク上のファイルから画面上のピクセルまでの間は、すべて SDK が処理しました。
|
||||
|
||||
## 音声を返す {#returning-audio}
|
||||
|
||||
`Audio` も同じ形です。`logo.png` はそのままにして、任意の WAV を `chime.wav` として隣に置いてください。
|
||||
|
||||
```python title="server.py" hl_lines="18-21"
|
||||
--8<-- "docs_src/media/tutorial002.py"
|
||||
```
|
||||
|
||||
結果は **`AudioContent`** ブロックです。
|
||||
|
||||
```python
|
||||
result.content # [AudioContent(type="audio", data="UklGR...", mime_type="audio/wav")]
|
||||
result.structured_content # None
|
||||
```
|
||||
|
||||
仕組みは同じです。ディスク上のファイルが入力で、base64 と MIME タイプが出力、出力スキーマはありません。
|
||||
|
||||
## バイト列かファイルか {#bytes-or-a-file}
|
||||
|
||||
どちらのヘルパーも `path=` の代わりに `data=`(生のバイト列)を受け付けます。これは、そもそもファイルとして存在したことのないバイト列のためのモードです。データベースのカラム、HTTP のレスポンス、Pillow が描いたばかりの画像などです。
|
||||
|
||||
```python title="server.py" hl_lines="14 15"
|
||||
--8<-- "docs_src/media/tutorial003.py"
|
||||
```
|
||||
|
||||
`path=` なら宣言するものは何もありません。ファイルは結果を組み立てるときに読み込まれ、MIME タイプは拡張子から推測されます。
|
||||
|
||||
* `Image`:`.png`、`.jpg`、`.jpeg`、`.gif`、`.webp`。
|
||||
* `Audio`:`.wav`、`.mp3`、`.ogg`、`.flac`、`.aac`、`.m4a`。
|
||||
|
||||
認識できない拡張子は `application/octet-stream` にフォールバックします。
|
||||
|
||||
!!! check
|
||||
`data=` の場合はファイル名がないので、推測する材料がありません。`format=` を忘れると、SDK はデフォルトにフォールバックします。画像なら `image/png`、音声なら `audio/wav` です。この方法で MP3 のバイト列から `Audio` を作ると、クライアントには `mime_type="audio/wav"` と伝えられ、それを忠実に信じてデコードに失敗します。`data=` を渡すときは `format=` も渡してください。
|
||||
|
||||
## アイコン {#icons}
|
||||
|
||||
`Icon` はメタデータであって、コンテンツではありません。画像そのものは運ばず、URI で画像を指し示します。クライアントはそれを取得して、サーバーの名前やツール、リソース、プロンプトの横に表示することがあります。
|
||||
|
||||
```python title="server.py" hl_lines="4-5 7 10 16"
|
||||
--8<-- "docs_src/media/tutorial004.py"
|
||||
```
|
||||
|
||||
* `src` はクライアントが解決できる URI です。`https:` か、追加の取得なしでアイコンを埋め込みたければ `data:` URI です。
|
||||
* `mime_type` と `sizes`(`"48x48"`、スケーラブルな形式なら `"any"`)を指定すると、複数のアイコンを提供したときにクライアントが適切なものを選べます。
|
||||
* `theme="light"` または `theme="dark"` で、アイコンを一方の配色向けとして印を付けます。
|
||||
|
||||
同じ `icons=[...]` キーワードは `MCPServer(...)`、`@mcp.tool()`、`@mcp.resource()`、`@mcp.prompt()` のいずれでも受け付けられます。
|
||||
|
||||
### クライアントからはどこに見えるか {#where-a-client-sees-them}
|
||||
|
||||
アイコンは、それが飾る対象と一緒に送られます。サーバーのアイコンはクライアントの接続時に `client.server_info` に届きます(2026 年世代の接続では省略可能なので、まず絞り込んでください)。
|
||||
|
||||
```python
|
||||
assert client.server_info is not None # python-sdk servers identify themselves by default
|
||||
client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_type="image/png", sizes=["48x48"])]
|
||||
```
|
||||
|
||||
ツールのアイコンは `tools/list` の `Tool` オブジェクトに、リソースのアイコンは `resources/list` の `Resource` に、プロンプトのアイコンは `prompts/list` の `Prompt` にあります。フィールド名は常に `icons` です。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* ツールから `Image` または `Audio` を返すと、クライアントは `ImageContent` / `AudioContent` ブロックを受け取ります。バイト列が base64 エンコードされ、MIME タイプが付きます。
|
||||
* `path=` から作って拡張子に MIME タイプを決めさせるか、メモリ上の `data=` に明示的な `format=` を添えて作ります。
|
||||
* メディアの結果には `structured_content` も出力スキーマもありません。
|
||||
* `Icon` はポインターです。`src` URI に、省略可能な `mime_type`、`sizes`、`theme` を加えたものです。
|
||||
* `icons=[...]` はサーバー、ツール、リソース、プロンプトのどれにも使え、クライアントは対応するオブジェクト上でそれらを見つけます。
|
||||
|
||||
ツールが結果に「入れられる」ものはこれですべてです。ツールが「失敗した」ときに何が起こるか(そして誰がそれを知るべきか)は **[エラーの処理](handling-errors.md)** で扱います。
|
||||
151
i18n/ja/pages/servers/prompts.md
Normal file
151
i18n/ja/pages/servers/prompts.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
|
||||
tool: 1
|
||||
---
|
||||
# プロンプト {#prompts}
|
||||
|
||||
**プロンプト**は、ユーザーが選ぶメッセージテンプレートです。
|
||||
|
||||
ツールはモデルのためのものです。プロンプトはその逆です。ユーザーがクライアントのメニュー(スラッシュコマンドやボタン)から 1 つを選んで引数を入力すると、レンダリングされたメッセージが、ユーザー自身が入力したかのように会話に入ります。
|
||||
|
||||
プロンプトを宣言するには、テキストを返す関数に `@mcp.prompt()` を付けます。
|
||||
|
||||
## 最初のプロンプト {#your-first-prompt}
|
||||
|
||||
```python title="server.py" hl_lines="6-9"
|
||||
--8<-- "docs_src/prompts/tutorial001.py"
|
||||
```
|
||||
|
||||
SDK が読み取るのは、ツールの場合と同じ 3 つです。
|
||||
|
||||
* **名前**は関数名、つまり `review_code` です。
|
||||
* クライアントが表示する**説明**は docstring、つまり `Review a piece of code.` です。
|
||||
* **引数**はパラメーターから決まります。`code` にはデフォルト値がないので必須です。
|
||||
|
||||
クライアントが `prompts/list` で受け取るのは次のとおりです。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "review_code",
|
||||
"description": "Review a piece of code.",
|
||||
"arguments": [
|
||||
{"name": "code", "required": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
ここには JSON Schema がありません。プロンプトの引数は、**名前付きの文字列値**が並んだフラットなリストです。モデルが組み立てるペイロードではなく、人が記入するフォームです。
|
||||
|
||||
### レンダリングする {#rendering-it}
|
||||
|
||||
クライアントは `prompts/get` に引数を渡してテンプレートをレンダリングします。関数が実行され、返した `str` が **1 つのユーザーメッセージ**になります。
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Review a piece of code.",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": "Please review this code:\n\ndef add(a, b): return a + b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"resultType": "complete"
|
||||
}
|
||||
```
|
||||
|
||||
プロンプトの一生はこれがすべてです。名前で一覧に載り、必要なときにレンダリングされ、チャットに差し込まれます。
|
||||
|
||||
!!! check
|
||||
`required` のチェックは関数が実行される前に行われます。`code` なしで `review_code` をレンダリングすると、リクエスト自体が JSON-RPC エラー(コード `-32603`)で失敗します。
|
||||
|
||||
```text
|
||||
mcp.shared.exceptions.MCPError: Internal server error
|
||||
```
|
||||
|
||||
モデルに返すためのツール形式のエラー結果はありません。そもそもモデルが関与していないからです。呼び出しは例外を送出します。理由(`Missing required arguments: {'code'}`)はサーバーのログに記録されます。
|
||||
|
||||
### 試してみる {#try-it}
|
||||
|
||||
MCP Inspector でサーバーを実行してください。
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
**Prompts** タブを開いて `review_code` を選択してください。Inspector は、必須の `code` フィールドが 1 つあるフォームを表示します。入力してレンダリングすると、上のユーザーメッセージがそのまま返ってきます。
|
||||
|
||||
## 複数のメッセージ {#more-than-one-message}
|
||||
|
||||
コードレビューは 1 つのメッセージです。デバッグセッションは会話であり、プロンプトはその会話全体の出発点を用意できます。
|
||||
|
||||
`str` の代わりに、メッセージのリストを返します。
|
||||
|
||||
```python title="server.py" hl_lines="2 13-20"
|
||||
--8<-- "docs_src/prompts/tutorial002.py"
|
||||
```
|
||||
|
||||
* `UserMessage` と `AssistantMessage` は `mcp.server.mcpserver.prompts.base` にあります。`str` を渡すと、`TextContent` にラップしてくれます。ロールはクラス名で決まります。
|
||||
* `Message` は両者に共通の基底クラスです。戻り値のアノテーションにはこれを使ってください。
|
||||
|
||||
`debug_error` をレンダリングすると、3 つのメッセージがこの順番で生成されるようになります。
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "Start a debugging conversation.",
|
||||
"messages": [
|
||||
{"role": "user", "content": {"type": "text", "text": "I'm seeing this error:"}},
|
||||
{"role": "user", "content": {"type": "text", "text": "TypeError: 'int' object is not iterable"}},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": {"type": "text", "text": "I'll help debug that. What have you tried so far?"}
|
||||
}
|
||||
],
|
||||
"resultType": "complete"
|
||||
}
|
||||
```
|
||||
|
||||
最後のメッセージに注目してください。`assistant` のターンをあらかじめ埋めておくのは、誘導の文言をユーザー自身に入力させることなく、モデルの「次の」返答を方向づけるための方法です。
|
||||
|
||||
## タイトルと引数の説明 {#titles-and-argument-descriptions}
|
||||
|
||||
`review_code` は関数名であって、ラベルではありません。ボタンに載せるのにもっとふさわしいものをクライアントに渡し、フォームを見ただけで意味がわかるように各引数に説明を付けます。
|
||||
|
||||
```python title="server.py" hl_lines="10-13"
|
||||
--8<-- "docs_src/prompts/tutorial003.py"
|
||||
```
|
||||
|
||||
* `title="Code review"` は人が読むための名前で、ツールの `title` とまったく同じです。
|
||||
* `Annotated[str, Field(description=...)]` は、**[ツール](tools.md)** でツールのパラメーターを説明するのに使うのと同じパターンです。ここでは、説明はスキーマの中ではなく引数に付きます。
|
||||
* `language` にはデフォルト値があるので、必須ではなくなります。
|
||||
|
||||
これで `prompts/list` のエントリには、クライアントがよいフォームを描くのに必要なものがすべてそろいます。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "review_code",
|
||||
"title": "Code review",
|
||||
"description": "Review a piece of code.",
|
||||
"arguments": [
|
||||
{"name": "code", "description": "The code to review.", "required": true},
|
||||
{"name": "language", "description": "The language the code is written in.", "required": false}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
**[ツール](tools.md)** を読んでいれば、このページの内容はもうすべて知っています。同じデコレーター、同じく docstring が説明になる仕組み、同じ `Annotated`/`Field` です。変わるのは、誰が起動するか(ユーザー)と、結果がどこへ行くか(会話の中)だけです。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* 関数に `@mcp.prompt()` を付けるとプロンプトになります。名前は関数から、説明は docstring から取られます。
|
||||
* プロンプトは**ユーザーが制御する**ものです。クライアントが一覧を出し、ユーザーが 1 つ選んで引数を入力します。
|
||||
* 引数は名前付き文字列のフラットなリストです(スキーマなし)。デフォルト値のあるパラメーターは省略可能です。
|
||||
* `str` を返すと 1 つのユーザーメッセージになります。`UserMessage` / `AssistantMessage` のリストを返すと、複数ターンの会話の出発点を用意できます。
|
||||
* `title=` と `Field(description=...)` は、クライアントが UI に表示するものです。
|
||||
* 必須の引数が欠けていると、リクエスト全体が失敗します。プロンプト単位のエラー結果はありません。
|
||||
|
||||
プロンプト(やリソーステンプレート)の引数をサーバー側でオートコンプリートする機能については、**[補完](completions.md)** を参照してください。
|
||||
138
i18n/ja/pages/servers/resources.md
Normal file
138
i18n/ja/pages/servers/resources.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
---
|
||||
translation:
|
||||
sections: [09df998c2a799f78, 0cf131146d16d4f9, 4e6b91e3f8025346, 8fe4eef576db17ed, 0d0d1ed43e3d0a53]
|
||||
tool: 1
|
||||
---
|
||||
# リソース {#resources}
|
||||
|
||||
**リソース**とは、アプリケーションが読めるように公開するデータです。
|
||||
|
||||
これが分かれ目です。ツールは**モデル**が呼び出すと決めるものです。リソースは**アプリケーション**が読み込むと決めるもの(設定ファイル、レコード、ドキュメントなど)で、コンテキストとしてモデルの前に置かれます。
|
||||
|
||||
宣言するには、普通の Python 関数に `@mcp.resource(uri)` を付けます。
|
||||
|
||||
## 最初のリソース {#your-first-resource}
|
||||
|
||||
```python title="server.py" hl_lines="6-8"
|
||||
--8<-- "docs_src/resources/tutorial001.py"
|
||||
```
|
||||
|
||||
形はツールと同じで、1 つだけ加わるものがあります。**URI** です。リソースは名前ではなくアドレスで指定されます。クライアントが要求するのは `config://app` であって、`get_config` ではありません。
|
||||
|
||||
残りは、やはり SDK が関数から読み取ります。
|
||||
|
||||
* **名前**は関数名、つまり `get_config` です。
|
||||
* クライアントに見える**説明**は docstring です。
|
||||
* **内容**は関数が返すものです。
|
||||
|
||||
`resources/list` でクライアントが受け取るのは次のとおりです。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "get_config",
|
||||
"uri": "config://app",
|
||||
"description": "The active shop configuration.",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
```
|
||||
|
||||
そして `config://app` を読むと関数が実行され、戻り値がテキストとして返ってきます。
|
||||
|
||||
```python
|
||||
result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]
|
||||
```
|
||||
|
||||
!!! tip
|
||||
一覧の取得は軽い処理です。関数は `resources/list` のときには**呼び出されません**。呼び出されるのは `resources/read` のときだけで、それも要求された URI についてだけです。リソースを 1000 個公開しても、コストがかかるのは誰かが開いたものだけです。
|
||||
|
||||
### 試してみる {#try-it}
|
||||
|
||||
MCP Inspector でサーバーを起動してください。
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
表示された URL を開き、**Resources** タブに移動してください。一覧に `config://app` が説明付きで並んでいます。クリックすると Inspector がそれを読み込み、2 行の設定が表示されます。
|
||||
|
||||
## リソーステンプレート {#resource-templates}
|
||||
|
||||
レコードごとに URI を 1 つずつ用意するやり方はスケールしません。URI に**プレースホルダー**を置き、それに対応するパラメーターを関数に持たせます。
|
||||
|
||||
```python title="server.py" hl_lines="12-13"
|
||||
--8<-- "docs_src/resources/tutorial002.py"
|
||||
```
|
||||
|
||||
URI に `{user_id}`、関数に `user_id: str` と書きます。約束事はこれですべてです。
|
||||
|
||||
これで**リソーステンプレート**になり、居場所も変わります。`resources/list` からは外れ、代わりに `resources/templates/list` に、アドレスではなくパターンとして現れます。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "get_user_profile",
|
||||
"uriTemplate": "users://{user_id}/profile",
|
||||
"description": "A customer's profile.",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
```
|
||||
|
||||
クライアントはプレースホルダーを埋め、`users://42/profile` や `users://ada/profile` のような具体的な URI を読みます。そのすべてに 1 つの関数が応答し、マッチした値が `user_id` として渡されます。
|
||||
|
||||
```python
|
||||
result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]
|
||||
```
|
||||
|
||||
結果の `uri` に注目してください。これはクライアントが要求した**具体的な** URI であって、テンプレートではありません。
|
||||
|
||||
!!! check
|
||||
プレースホルダーとパラメーターは一致している必要があります。URI が `{user_id}` のまま関数のパラメーターを `user` に改名すると、デコレーターは**インポート時に**、つまりどのクライアントも触れないうちに拒否します。
|
||||
|
||||
```text
|
||||
ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}
|
||||
```
|
||||
|
||||
不一致はバグでしかありえないので、SDK は不一致を抱えたままではサーバーを起動できないようにしています。
|
||||
|
||||
プレースホルダーの構文は [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) です。複数セグメントにまたがる値には `{+path}`、省略可能なクエリパラメーターには `{?q,lang}` というように、ほかにも書き方があります。また、SDK は取り出した値に対して、デフォルトでパス安全性のチェックを行います。完全なリファレンスは **[URI テンプレートとパス安全性](uri-templates.md)** を参照してください。
|
||||
|
||||
`get_user_profile` は、`Context` と注釈を付けたパラメーターを受け取ることもできます。SDK はそれを URI パラメーターとして扱うことなく注入します。それで何が得られるかは **[Context](../handlers/context.md)** のページで説明しています。
|
||||
|
||||
## 何を返すか {#what-you-return}
|
||||
|
||||
返せるのは `str` だけではありません。リソースごとに `mime_type` を指定し、合うものを返してください。
|
||||
|
||||
```python title="server.py" hl_lines="8-9 14-15 20-21"
|
||||
--8<-- "docs_src/resources/tutorial003.py"
|
||||
```
|
||||
|
||||
* `readme` は `str` を返すので、そのまま送られます。これがよくあるケースです。
|
||||
* `catalog_stats` は `dict` を返すので、SDK が **JSON テキスト**にシリアライズしてくれます。
|
||||
|
||||
```json
|
||||
{
|
||||
"books": 1204,
|
||||
"authors": 391
|
||||
}
|
||||
```
|
||||
|
||||
* `placeholder_cover` は `bytes` を返すので、クライアントは `TextResourceContents` ではなく `BlobResourceContents` を受け取ります。その `blob` フィールドに、バイト列が base64 エンコードされて入っています。
|
||||
|
||||
JSON にシリアライズできるほかのもの、つまりリスト、Pydantic モデル、dataclass にも同じルールが当てはまります。`str` でも `bytes` でもなければ、JSON になります。
|
||||
|
||||
`mime_type` は自分で宣言するもので、デフォルトは `text/plain` です。SDK が戻り値の中身を調べて推測することはありません。そのため、ラベルを付けていない `dict` のリソースは、相変わらずプレーンテキストとして案内されます。
|
||||
|
||||
!!! tip
|
||||
関数から導き出したくないときは、`@mcp.resource()` に `name=`、`title=`、`description=` も渡せます。また、書くべき関数がそもそもないときのために、`mcp.server.mcpserver.resources` には既製の `Resource` クラス(`TextResource`、`BinaryResource`、`FileResource`、`HttpResource`、`DirectoryResource`)が用意されており、`mcp.add_resource(...)` で登録します。
|
||||
|
||||
クライアントはリソースを**購読**して、変更があったときに通知を受け取ることもできます。これはクライアント側の話なので、**[クライアント](../client/index.md)** で説明しています。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* 関数に `@mcp.resource(uri)` を付けるとリソースになります。URI がアドレス、戻り値が内容、docstring が説明です。
|
||||
* URI に `{placeholder}` を入れると**テンプレート**になります。`resources/templates/list` に載り、マッチするすべての URI に 1 つの関数が応答します。
|
||||
* プレースホルダーの名前は関数のパラメーター名と一致させなければなりません。間違えても、気づくのは本番ではなくインポート時です。
|
||||
* 関数が実行されるのはリソースが**読まれた**ときで、一覧に載るときではありません。
|
||||
* `str` はテキストに、`bytes` は base64 の blob に、それ以外は JSON テキストになります。ラベルを付けるには `mime_type=` を使います。
|
||||
* ツールはモデルが行動するためのもので、リソースはアプリケーションが読むためのものです。
|
||||
|
||||
3 つ目のプリミティブ、つまり人がメニューから選ぶものが **[プロンプト](prompts.md)** です。
|
||||
242
i18n/ja/pages/servers/structured-output.md
Normal file
242
i18n/ja/pages/servers/structured-output.md
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
---
|
||||
translation:
|
||||
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
|
||||
tool: 1
|
||||
---
|
||||
# 構造化出力 {#structured-output}
|
||||
|
||||
単なる `str` を返すツールは、結果を 2 回生み出します。`content` にはテキストとして、`structured_content` には `{"result": "..."}` として入ります。
|
||||
|
||||
このページで扱うのは、その 2 つ目のチャネルです。それがどこから来るのか、どんな形を取りうるのか、そして SDK がその正しさをどう担保しているのかを見ていきます。
|
||||
|
||||
ひとことで言えば、**戻り値の型アノテーションが出力スキーマです**。もう書いてあります。
|
||||
|
||||
## 出力スキーマ {#the-output-schema}
|
||||
|
||||
```python title="server.py" hl_lines="9"
|
||||
--8<-- "docs_src/structured_output/tutorial001.py"
|
||||
```
|
||||
|
||||
重要なのはシグネチャの行、`-> int` です。
|
||||
|
||||
この行があるおかげで、SDK が `tools/list` で送るツールには、パラメーターから組み立てる入力スキーマ(こちらは **[ツール](tools.md)** で扱っています)の隣に `output_schema` が付きます。
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"result": {"title": "Result", "type": "integer"}
|
||||
},
|
||||
"required": ["result"],
|
||||
"title": "get_temperatureOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
`int` 単体は JSON オブジェクトではないため、SDK はそれを `{"result": ...}` で**ラップ**します。ツールを呼び出すと、両方のチャネルが埋まります。
|
||||
|
||||
```python
|
||||
result.content # [TextContent(text="17")]
|
||||
result.structured_content # {"result": 17}
|
||||
```
|
||||
|
||||
スカラーはどれも同じラッパーに包まれます。`str`、`int`、`float`、`bool`、`bytes`、`None` のすべてが対象です。
|
||||
|
||||
## 2 つのチャネル {#two-channels}
|
||||
|
||||
なぜ同じ値を 2 回送るのでしょうか。
|
||||
|
||||
* `content` は**モデル**のためのものです。言語モデルが読むのはテキストであり、結果のうちモデルの目に入るのはこの部分だけです。
|
||||
* `structured_content` は、モデルがその中で動いている**アプリケーション**のためのものです。つまり「17」を含んだ文章ではなく、`17` そのものが欲しいコードです。
|
||||
* `output_schema` は両者をつなぐ契約で、ツールが一度でも呼ばれる前に公開されます。
|
||||
|
||||
返すのは Python の値 1 つです。3 つすべてを埋めるのは SDK です。
|
||||
|
||||
## モデルを返す {#return-a-model}
|
||||
|
||||
形を Pydantic の `BaseModel` として宣言し、そのインスタンスを返します。
|
||||
|
||||
```python title="server.py" hl_lines="8-11 15"
|
||||
--8<-- "docs_src/structured_output/tutorial002.py"
|
||||
```
|
||||
|
||||
今度は `WeatherData` **そのものが**スキーマです。ラッパーも `result` キーもありません。
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"temperature": {"description": "Degrees Celsius.", "title": "Temperature", "type": "number"},
|
||||
"humidity": {"description": "Relative humidity, 0 to 1.", "title": "Humidity", "type": "number"},
|
||||
"conditions": {"title": "Conditions", "type": "string"}
|
||||
},
|
||||
"required": ["temperature", "humidity", "conditions"],
|
||||
"title": "WeatherData",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
`structured_content` は、そのオブジェクトをフィールドごとにそのまま写したものです。
|
||||
|
||||
```python
|
||||
result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions": "Overcast"}
|
||||
```
|
||||
|
||||
モデルも置き去りにはしません。SDK は同じオブジェクトを JSON テキストにシリアライズして `content` に入れます。
|
||||
|
||||
```json
|
||||
{
|
||||
"temperature": 16.2,
|
||||
"humidity": 0.83,
|
||||
"conditions": "Overcast"
|
||||
}
|
||||
```
|
||||
|
||||
`temperature` と `humidity` に付けた `Field(description=...)` がスキーマに入っている点に注目してください。**入力**を説明したのと同じ `Field` が、出力も説明します。
|
||||
|
||||
!!! info
|
||||
FastAPI の `response_model` を使ったことがあれば、これはすでにおなじみのはずです。宣言したレスポンスとして Pydantic モデルを置けば、シリアライズもドキュメント化も任せられる、というものです。唯一の違いは、ここでは戻り値のアノテーションだけで宣言が完結する点です。
|
||||
|
||||
## `TypedDict` {#a-typeddict}
|
||||
|
||||
どんな形にもクラスがふさわしいわけではありません。`TypedDict` でも同じスキーマになります。
|
||||
|
||||
```python title="server.py" hl_lines="8"
|
||||
--8<-- "docs_src/structured_output/tutorial003.py"
|
||||
```
|
||||
|
||||
`TypedDict` は実行時にはただの `dict` なので、組み立てて返すのもそれです。スキーマもバリデーションも `structured_content` も、`BaseModel` 版と同一です(説明だけは付きません。`TypedDict` には説明を書く場所がないからです)。
|
||||
|
||||
## データクラス {#a-dataclass}
|
||||
|
||||
データクラスも使えますし、属性に型ヒントの付いた普通のクラスならどれでも使えます。SDK が裏側で、アノテーションから Pydantic モデルを組み立てます。
|
||||
|
||||
```python title="server.py" hl_lines="8-9"
|
||||
--8<-- "docs_src/structured_output/tutorial004.py"
|
||||
```
|
||||
|
||||
書き方は 3 通り、スキーマは 1 つです。コードベースにすでにあるものを使ってください。
|
||||
|
||||
## リスト {#lists}
|
||||
|
||||
`list[...]` も JSON オブジェクトではないので、`{"result": ...}` ラッパーに包まれます。要素の型は、その中で `$defs` への参照になります。
|
||||
|
||||
```python title="server.py" hl_lines="15"
|
||||
--8<-- "docs_src/structured_output/tutorial005.py"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"$defs": {
|
||||
"WeatherData": {
|
||||
"properties": {
|
||||
"temperature": {"title": "Temperature", "type": "number"},
|
||||
"humidity": {"title": "Humidity", "type": "number"},
|
||||
"conditions": {"title": "Conditions", "type": "string"}
|
||||
},
|
||||
"required": ["temperature", "humidity", "conditions"],
|
||||
"title": "WeatherData",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"result": {"items": {"$ref": "#/$defs/WeatherData"}, "title": "Result", "type": "array"}
|
||||
},
|
||||
"required": ["result"],
|
||||
"title": "get_forecastOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
2 日分の予報を要求すると、`structured_content` は `{"result": [{...}, {...}]}` になります。`content` のほうは、要素ごとに 1 つずつ、**2 つ**の `TextContent` ブロックになります。リストは 1 本の文字列として丸ごと出力されるのではなく、モデル向けに平坦化されます。
|
||||
|
||||
`tuple[...]`、ユニオン、`Optional[...]` も同じようにラップされます。
|
||||
|
||||
## 辞書 {#dictionaries}
|
||||
|
||||
`dict[str, ...]` は、それ自体がすでに JSON オブジェクトである唯一のジェネリック型なので、ラップされません。
|
||||
|
||||
```python title="server.py" hl_lines="9"
|
||||
--8<-- "docs_src/structured_output/tutorial006.py"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"additionalProperties": {"type": "number"},
|
||||
"title": "get_temperaturesDictOutput",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
result.structured_content # {"London": 16.2, "Reykjavik": 4.4}
|
||||
```
|
||||
|
||||
キーは `str` でなければなりません。`dict[int, float]` は JSON オブジェクトになれないため、`{"result": ...}` ラッパーにフォールバックします。
|
||||
|
||||
## バリデーション {#validation}
|
||||
|
||||
`output_schema` は単なるドキュメントではありません。関数が返すものは何であれ、サーバーを出る前に**このスキーマに照らして検証されます**。
|
||||
|
||||
値を手で組み立てているうちは、このことに気づきません。`WeatherData` が本当に `WeatherData` であることは、Pydantic がすでに保証しているからです。気づくのは、自分では制御できない場所からデータが来るようになった日です。
|
||||
|
||||
```python title="server.py" hl_lines="9 21"
|
||||
--8<-- "docs_src/structured_output/tutorial007.py"
|
||||
```
|
||||
|
||||
アノテーションは `WeatherData` を約束しています。ところが、上流のレスポンスが `humidity` を送ってこなくなりました。
|
||||
|
||||
!!! check
|
||||
`get_weather` を呼び出しても、中身が半分欠けたオブジェクトがこっそりクライアントに渡ることはありません。呼び出しは失敗し、エラーの冒頭の数行にそのフィールド名が示されます。
|
||||
|
||||
```text
|
||||
Error executing tool get_weather: 1 validation error for WeatherData
|
||||
humidity
|
||||
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
|
||||
```
|
||||
|
||||
このテキストは `is_error=True` の付いたツール結果として返ってくるので、モデルは、ありもしない天気を自信満々に読み上げる代わりに、呼び出しが失敗したと分かります。
|
||||
|
||||
ちなみに、`-> WeatherData` のツールから単なる `dict` を返してもかまいません。`json.loads` が返したのはまさにそれです。バリデーションの対象は Python の型ではなく、値です。
|
||||
|
||||
## オプトアウト {#opting-out}
|
||||
|
||||
戻り値のアノテーションが、プロトコルのためではなく型チェッカーのためにある場合もあります。`structured_output=False` を渡せば、ツールはテキストのみになります。
|
||||
|
||||
```python title="server.py" hl_lines="6"
|
||||
--8<-- "docs_src/structured_output/tutorial008.py"
|
||||
```
|
||||
|
||||
`output_schema` も、ラップも、バリデーションもありません。`structured_content` は `None` になり、`content` は返した文字列そのものです。
|
||||
|
||||
その逆の `structured_output=True` は、自動検出を必須要件に変えます。戻り値の型からスキーマを作れないツールは、テキストにフォールバックするのではなく、インポート時に例外を送出します。
|
||||
|
||||
## 型ヒントのないクラス {#a-class-without-type-hints}
|
||||
|
||||
頼んでもいないのに非構造化になってしまう道が 1 つだけあります。**本体にアノテーションが 1 つもない**クラスを返すことです。
|
||||
|
||||
```python title="server.py" hl_lines="6-9"
|
||||
--8<-- "docs_src/structured_output/tutorial009.py"
|
||||
```
|
||||
|
||||
`Station` は `__init__` の中で `name` と `online` を設定していますが、クラス自体は何も宣言していません。SDK はクラスのアノテーションを読みにいき、何も見つからず、諦めます。
|
||||
|
||||
!!! warning
|
||||
しかも**黙って**諦めます。`output_schema` は `None`、`structured_content` も `None` で、モデルが読むテキストはオブジェクトの `repr` です。
|
||||
|
||||
```text
|
||||
"<server.Station object at 0x7f539d75b230>"
|
||||
```
|
||||
|
||||
エラーも警告もなく、役に立たないツールができあがります。アノテーションをクラス本体へ移すか、`structured_output=True` を渡してください。後者なら、モジュールをインポートした瞬間に `Function get_station: return type <class 'server.Station'> is not serializable for structured output` というハードエラーに変わります。
|
||||
|
||||
!!! tip
|
||||
完全な制御が必要な場合(`CallToolResult` を自分で組み立てたい、あるいはアプリケーションからは見えてモデルからは見えない `_meta` を付けたいなど)は、**[低レベル Server](../advanced/low-level-server.md)** を参照してください。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* **戻り値の型アノテーション**が出力スキーマです。`tools/list` で `output_schema` として公開されます。
|
||||
* スカラー、リスト、タプル、ユニオンは `{"result": ...}` でラップされます。モデル、`TypedDict`、データクラス、アノテーション付きクラス、`dict[str, ...]` はもともとオブジェクトなので、そのままです。
|
||||
* どの結果も `content`(モデル向けのテキスト)**と** `structured_content`(アプリケーション向けのデータ)の両方を持ちます。
|
||||
* 返したものはスキーマに照らして検証されます。食い違いは壊れた結果ではなく、ツールエラーになります。
|
||||
* `structured_output=False` を渡すと、そのツールはオプトアウトします。型ヒントのないクラスは黙ってオプトアウトするので、気をつけてください。
|
||||
|
||||
これで、ツールが返せるものはすべて押さえました。次は 2 つ目のプリミティブ、**[リソース](resources.md)** です。
|
||||
170
i18n/ja/pages/servers/tools.md
Normal file
170
i18n/ja/pages/servers/tools.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
|
||||
tool: 1
|
||||
---
|
||||
# ツール {#tools}
|
||||
|
||||
**ツール**とは、モデルが呼び出せる関数のことです。
|
||||
|
||||
普通の Python 関数に `@mcp.tool()` を付ければ宣言できます。API はこれだけです。
|
||||
|
||||
## 最初のツール {#your-first-tool}
|
||||
|
||||
```python title="server.py" hl_lines="6-8"
|
||||
--8<-- "docs_src/tools/tutorial001.py"
|
||||
```
|
||||
|
||||
書いたコードを見てください。スキーマも JSON もプロトコルもなく、あるのは関数だけです。SDK はこの関数から 3 つのことを読み取ります。
|
||||
|
||||
* ツールの**名前**は関数名、つまり `search_books` です。
|
||||
* モデルが目にする**説明**は docstring、つまり `Search the catalog by title or author.` です。
|
||||
* モデルが渡すことを許される**引数**は型ヒント、つまり `query: str` と `limit: int` から決まります。
|
||||
|
||||
### 入力スキーマ {#the-input-schema}
|
||||
|
||||
SDK はこれらの型ヒントから JSON Schema を生成し、`tools/list` のときにクライアントへ送ります。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"title": "Query", "type": "string"},
|
||||
"limit": {"title": "Limit", "type": "integer"}
|
||||
},
|
||||
"required": ["query", "limit"],
|
||||
"title": "search_booksArguments"
|
||||
}
|
||||
```
|
||||
|
||||
どちらの引数にもデフォルト値がないため、両方とも `required` に入っています。これはすぐ後で直します。(`title` キーは Pydantic が生成した付随物です。契約にあたるのは、プロパティとその型、そして `required` です。)
|
||||
|
||||
!!! tip
|
||||
ここでの型ヒントはドキュメントではありません。**契約そのもの**です。クライアントが `"limit": "ten"` を送ってきても、関数が実行される前に SDK が拒否します。
|
||||
|
||||
### モデルが受け取るもの {#what-the-model-gets-back}
|
||||
|
||||
`{"query": "dune", "limit": 5}` でツールを呼び出すと、結果は 2 つの部分からなります。
|
||||
|
||||
```python
|
||||
result.content # [TextContent(text="Found 3 books matching 'dune' (showing up to 5).")]
|
||||
result.structured_content # {'result': "Found 3 books matching 'dune' (showing up to 5)."}
|
||||
```
|
||||
|
||||
`content` は**モデル**が読むテキストです。`structured_content` は**クライアントアプリケーション**向けの型付きデータです。これが含まれているのは、戻り値の型を `-> str` と宣言したからです。
|
||||
|
||||
`structured_content` については、まだ気にしなくてかまいません。ツールから本物の Python オブジェクトを返せば、適切に処理されます。詳しくは **[構造化出力](structured-output.md)** のページを参照してください。
|
||||
|
||||
### 試してみる {#try-it}
|
||||
|
||||
MCP Inspector でサーバーを実行してください。
|
||||
|
||||
```console
|
||||
uv run mcp dev server.py
|
||||
```
|
||||
|
||||
表示される URL を開き、**Tools** タブに移動して `search_books` を呼び出してください。
|
||||
|
||||
Inspector は、必須の `query` テキストフィールドと必須の `limit` 数値フィールドを持つフォームを描画します。このフォームは型ヒントから組み立てられたものです。ほかの MCP クライアントもすべて同じようにします。
|
||||
|
||||
## 省略可能な引数 {#optional-arguments}
|
||||
|
||||
パラメーターにデフォルト値を与えると、必須ではなくなります。これだけです。ただの Python です。
|
||||
|
||||
```python title="server.py" hl_lines="7"
|
||||
--8<-- "docs_src/tools/tutorial002.py"
|
||||
```
|
||||
|
||||
スキーマは次のようになります。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"title": "Query", "type": "string"},
|
||||
"limit": {"default": 10, "title": "Limit", "type": "integer"}
|
||||
},
|
||||
"required": ["query"],
|
||||
"title": "search_booksArguments"
|
||||
}
|
||||
```
|
||||
|
||||
`limit` は `required` から外れ、`"default": 10` が付きました。省略したクライアントには `10` が渡ります。Python とまったく同じです。
|
||||
|
||||
## `Field` を使った詳細なスキーマ {#richer-schemas-with-field}
|
||||
|
||||
型ヒントだけでもかなりのことができますが、引数に「説明」を付けたり、制約を課したりしたい場合もあります。
|
||||
|
||||
型を `Annotated` で包み、Pydantic の `Field` を加えます。
|
||||
|
||||
```python title="server.py" hl_lines="12-14"
|
||||
--8<-- "docs_src/tools/tutorial003.py"
|
||||
```
|
||||
|
||||
新しい点が 3 つあり、どれもパラメーターに付いています。
|
||||
|
||||
* `Field(description=...)`:引数ごとの説明です。モデルは docstring と合わせてこれを読みます。
|
||||
* `Field(ge=1, le=50)`:数値の範囲です。スキーマには `"minimum": 1, "maximum": 50` として入ります。
|
||||
* `Literal["fiction", "non-fiction", "poetry"]`:列挙型です。モデルはこの中の 1 つしか選べません。
|
||||
|
||||
!!! check
|
||||
制約は飾りではありません。`limit=999` でツールを呼び出すと、**関数が実行される前に** SDK がツールエラーを返します。
|
||||
|
||||
```text
|
||||
Input should be less than or equal to 50
|
||||
```
|
||||
|
||||
このエラーはツールの結果としてモデルに返され、モデルはそれを読んで有効な値でやり直します。`le=50` と一度書いただけで、自己修正するエージェントがただで手に入ったことになります。
|
||||
|
||||
!!! info
|
||||
FastAPI や Pydantic を使ったことがあれば、これはすべて既知の内容です。同じ `Field`、同じ `Annotated`、同じバリデーションです。MCP 固有の学ぶべきことはここにはありません。
|
||||
|
||||
## パラメーターとしてのモデル {#a-model-as-a-parameter}
|
||||
|
||||
ツールが取る引数が 2、3 個を超えるときは、Pydantic モデルにまとめます。
|
||||
|
||||
```python title="server.py" hl_lines="8-11 15"
|
||||
--8<-- "docs_src/tools/tutorial004.py"
|
||||
```
|
||||
|
||||
`Book` のスキーマはツールの入力スキーマの中に(`$defs` の参照として)ネストされます。モデルはこれを JSON オブジェクトとして埋め、関数はバリデーション済みの**本物の `Book` インスタンス**を受け取ります。`.title`、`.author`、`.year` の各属性が使えます。
|
||||
|
||||
自由に組み合わせられます。普通のパラメーターとモデルのパラメーターを並べても、モデルをネストしても、モデルのリストにしてもかまいません。どこまで行っても Pydantic です。
|
||||
|
||||
## `async def` {#async-def}
|
||||
|
||||
ツールが I/O を行う場合(API を呼ぶ、ファイルを読む、データベースに問い合わせるなど)は、`async def` で宣言し、その中で `await` してください。SDK 側がそれを await します。
|
||||
|
||||
普通の `def` のツールも使えます。SDK がスレッド内で実行するので、サーバーをブロックすることはありません。
|
||||
|
||||
ほかに設定することはありません。
|
||||
|
||||
## 名前、タイトル、アノテーション {#names-titles-and-annotations}
|
||||
|
||||
SDK が推論するものはすべて、デコレーターで上書きできます。
|
||||
|
||||
```python title="server.py" hl_lines="7-10"
|
||||
--8<-- "docs_src/tools/tutorial005.py"
|
||||
```
|
||||
|
||||
* `title` は UI 向けの、人が読むための名前です。クライアントは `search_books` の代わりに *"Search the catalog"* を表示します。
|
||||
* `annotations` はクライアントに対する振る舞いの**ヒント**です。
|
||||
* `read_only_hint=True`:このツールは何も変更しません。
|
||||
* `open_world_hint=False`:開かれた Web ではなく、閉じた対象の集合(このカタログ)に対して働きます。
|
||||
* 残りの 2 つ、`destructive_hint` と `idempotent_hint` は「書き込む」ツールを説明するものです。何かを削除する可能性があるか、そして 2 回呼び出すのは 1 回呼び出すのと同じか、を表します。仕様はどちらも読み取り専用でないツールに対してだけ定義しているので、`search_books` に付けても何も伝わりません。
|
||||
|
||||
行儀のよいクライアントは、これらを使って「これを実行する前にユーザーに確認する必要があるか」といったことを判断します。これらはヒントであって、セキュリティではありません。クライアントがこれらを守ることを決して当てにしないでください。
|
||||
|
||||
!!! tip
|
||||
関数名と docstring から導きたくない場合は、`@mcp.tool()` に `name=` と `description=` を渡すこともできます。たいていは導くほうで十分です。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* 関数に `@mcp.tool()` を付けるとツールになります。名前は関数から、説明は docstring から取られます。
|
||||
* 型ヒントが**そのまま**入力スキーマです。デフォルト値を付けると引数は省略可能になります。
|
||||
* `Annotated[..., Field(...)]` で説明と制約を、`Literal` で列挙型を加えられます。
|
||||
* 構造化された「ボディ」を受け取るには、Pydantic モデルのパラメーターを使います。
|
||||
* 不正な引数は自動的に拒否され、モデルが読んで立て直せるエラーが返ります。
|
||||
* I/O には `async def` を、それ以外には普通の `def` を使います。
|
||||
|
||||
`return` した値がその後どうなるかは、**[構造化出力](structured-output.md)** で扱います。
|
||||
167
i18n/ja/pages/servers/uri-templates.md
Normal file
167
i18n/ja/pages/servers/uri-templates.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
---
|
||||
translation:
|
||||
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
|
||||
tool: 1
|
||||
---
|
||||
# URI テンプレートとパスの安全性 {#uri-templates-and-path-safety}
|
||||
|
||||
このページは、[`@mcp.resource`](resources.md) が受け付ける URI テンプレート構文と、抽出した値に SDK が適用するパス安全性ポリシーのリファレンスです。リソースとは何か、いつ使うのかについては、まず **[リソース](resources.md)** を参照してください。このページでは、リソースの宣言にはすでに慣れていて、演算子の全セットやセキュリティの設定項目、低レベルでの組み込み方を知りたい、という読者を想定しています。
|
||||
|
||||
テンプレート構文は [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) です。SDK がサポートするのは、受信する `resources/read` の URI のマッチング向けに選んだそのサブセットです。加えて、提供するつもりのディレクトリの外へ解決されてしまう値を拒否するセキュリティレイヤーを備えています。プロトコルレベルの詳細(メッセージ形式、ライフサイクル、ページネーション)については、[MCP のリソース仕様](https://modelcontextprotocol.io/specification/latest/server/resources) を参照してください。
|
||||
|
||||
## 演算子の全セット {#the-full-operator-set}
|
||||
|
||||
単純なプレースホルダー `{user_id}` は、**[リソース](resources.md)** で紹介したものです。演算子の形式はほかに 4 つあります。並べて見比べられるように、1 つのサーバーにまとめました。
|
||||
|
||||
```python title="server.py" hl_lines="16-17 22-23 28-29 34-35 40-41"
|
||||
--8<-- "docs_src/uri_templates/tutorial001.py"
|
||||
```
|
||||
|
||||
ハイライトされたデコレーターは、それぞれ異なる方法で URI を切り分けています。以下のセクションで上から順に説明します。
|
||||
|
||||
### 単純な展開:`{name}` {#simple-expansion-name}
|
||||
|
||||
`books://{isbn}` は、日常的に使う単純な形式です。プレースホルダーは `isbn` パラメーターに対応するので、クライアントが `books://978-0441172719` を読むと `get_book("978-0441172719")` が呼び出されます。
|
||||
|
||||
単純な `{name}` は最初の `/` で止まります。`books://978/extra` はマッチしません。`978` の後ろのスラッシュでキャプチャが終わり、`/extra` が余ってしまうからです。
|
||||
|
||||
### 型変換 {#type-conversion}
|
||||
|
||||
抽出した値は文字列として届きますが、より具体的な型を宣言すれば SDK が変換します。`orders://{order_id}` の値は、パラメーターが `order_id: int` の関数に渡るので、`orders://12345` を読むと `get_order("12345")` ではなく `get_order(12345)` が呼び出されます。ハンドラーはキャストなしで、そのまま算術演算(`order_id + 1`)を行えます。
|
||||
|
||||
### 複数セグメントのパス:`{+name}` {#multi-segment-paths-name}
|
||||
|
||||
スラッシュを含む値をキャプチャするには `{+name}` を使います。`manuals://{+path}` の場合は次のようになります。
|
||||
|
||||
* `manuals://returns.md` なら `path = "returns.md"`
|
||||
* `manuals://printing/setup.md` なら `path = "printing/setup.md"`
|
||||
|
||||
値が階層構造を持つときは、いつでも `{+name}` を使ってください。ファイルシステムのパス、ネストしたオブジェクトのキー、プロキシする URL のパスなどです。
|
||||
|
||||
### クエリパラメーター:`{?a,b,c}` {#query-parameters-abc}
|
||||
|
||||
`reviews://{isbn}{?limit,sort}` は、`limit` と `sort` を `?` の後ろに置きます。パスは「どの」本かを特定し、クエリは「どのように」読むかを調整します。
|
||||
|
||||
クエリパラメーターのマッチングは緩やかです。順序は問わず、余分なものは無視され、省略されたパラメーターには関数のデフォルト値が使われます。つまり `reviews://978-0441172719` では `limit=10, sort="newest"` が使われ、`reviews://978-0441172719?sort=top` では `sort` だけが上書きされます。
|
||||
|
||||
### リストとしてのパスセグメント:`{/name*}` {#path-segments-as-a-list-name}
|
||||
|
||||
スラッシュ入りの 1 つの文字列ではなく、パスの各セグメントを別々のリスト要素として受け取りたい場合は `{/name*}` を使います。`shelves://browse{/path*}` なら、クライアントが `shelves://browse/fiction/sci-fi` を読むと `browse_shelf(["fiction", "sci-fi"])` が呼び出されます。
|
||||
|
||||
### テンプレート早見表 {#template-reference}
|
||||
|
||||
よく使うパターンは次のとおりです。
|
||||
|
||||
| パターン | 入力例 | 得られる値 |
|
||||
|--------------|-----------------------|-------------------------|
|
||||
| `{name}` | `alice` | `"alice"` |
|
||||
| `{name}` | `docs/intro.md` | マッチしない(`/` で止まる) |
|
||||
| `{+path}` | `docs/intro.md` | `"docs/intro.md"` |
|
||||
| `{.ext}` | `.json` | `"json"` |
|
||||
| `{/segment}` | `/v2` | `"v2"` |
|
||||
| `{?key}` | `?key=value` | `"value"` |
|
||||
| `{?a,b}` | `?a=1&b=2` | `"1"`, `"2"` |
|
||||
| `{/path*}` | `/a/b/c` | `["a", "b", "c"]` |
|
||||
|
||||
### パーサーが拒否するもの {#what-the-parser-rejects}
|
||||
|
||||
テンプレートの形によっては、最初のリクエストで失敗するのを待たず、事前に検出されるものがあります。`@mcp.resource` はデコレーターの実行時にテンプレートを解析するので、これらが稼働中のサーバーに到達することはありません。
|
||||
|
||||
`UriTemplate.parse()` は、次の場合に `InvalidUriTemplate` を送出します。
|
||||
|
||||
* **間に何もない 2 つの変数。** `manuals://{+path}{ext}` は拒否されます。マッチングでは、`path` がどこで終わり `ext` がどこで始まるのか判断できないからです。間にリテラルを挟む(`manuals://{+path}/{ext}`)か、区切り文字を自前で持つ演算子を使ってください。`manuals://{+path}{.ext}` は、`{.ext}` 自体が `.` を提供するので受け付けられます。
|
||||
* **複数セグメントの変数が 2 つ以上ある場合。** `{+var}`、`{#var}`、explode 修飾子付きの変数(`{/var*}`、`{.var*}`、`{;var*}`)は、1 つのテンプレートにつき多くても 1 つです。2 つあると本質的にあいまいになります。余分なセグメントをどちらが吸収するのか、筋の通った決め方がないからです。
|
||||
* **よくある構文エラー**:閉じていない波括弧、2 回使われている変数名、あるいは SDK がサポートしていない RFC 6570 の機能です。たとえばプレフィックス修飾子の `{var:3}` や、クエリの explode である `{?vars*}` などが該当します。
|
||||
|
||||
これに加えて `@mcp.resource` は、ハンドラーのパラメーターがテンプレート末尾に連なる `{?...}`/`{&...}` のクエリ変数に束縛されているのに Python のデフォルト値を持たない場合、`ValueError` を送出します。これらの変数は緩やかにマッチングされる(クライアントはどれを省略してもかまいません)ので、デフォルト値のないパラメーターは、それを省略した最初のリクエストで、わかりにくい内部エラーとして表面化するだけになってしまいます。上のサーバーの `reviews://{isbn}{?limit,sort}` は正しく書かれた例で、`limit` と `sort` はどちらもデフォルト値を持っています。
|
||||
|
||||
## セキュリティ {#security}
|
||||
|
||||
テンプレートのパラメーターはクライアントから届きます。チェックしないままファイルシステムやデータベースの操作に流し込むと、`../../etc/passwd` のような値が、提供するつもりだったディレクトリの外に解決されてしまうことがあります。
|
||||
|
||||
### SDK がデフォルトでチェックする内容 {#what-the-sdk-checks-by-default}
|
||||
|
||||
SDK はハンドラーの実行前に、次のいずれかに当てはまるパラメーターを拒否します。
|
||||
|
||||
* `..` の構成要素を使って開始ディレクトリの外に出てしまうもの。
|
||||
* 絶対パス(`/etc/passwd`、`C:\Windows`)や Windows のドライブ相対パス(`C:foo`)のように見えるもの。ドライブ相対の値と `x:y` のような名前空間付きの識別子は、文字列としては区別できません。そのため、1 文字の後にコロンが続く形の値は、デフォルトではすべて拒否されます。そのような値を正当に受け取るパラメーターは、チェックの対象から除外してください。
|
||||
* ヌルバイト(`\x00`)を含むもの。
|
||||
|
||||
`..` のチェックは部分文字列の走査ではなく、パスの構成要素単位で行われます。`v1.0..v2.0` や `HEAD~3..HEAD` のような値は通ります。そこでの `..` は独立したパスセグメントではないからです。
|
||||
|
||||
これらのチェックはデコード後の値に適用されるので、URI でどのようにエンコードされていてもトラバーサルを検出します(`../etc`、`..%2Fetc`、`%2E%2E/etc`、`..%5Cetc`、`%00` はすべて検出されます)。
|
||||
|
||||
!!! check
|
||||
上のサーバーから `manuals://../etc/passwd` を読むと、リクエストは即座に拒否されます。テンプレートのマッチングは最初の失敗で止まるので、後続の(より緩いかもしれない)テンプレートがフォールバックとして試されることはありません。クライアントには、どのテンプレートにもマッチしない URI の場合と同じ `-32602` の「Unknown resource」エラーが返り、`read_manual` は実行されません。
|
||||
|
||||
### ファイルシステムのハンドラー:safe_join を使う {#filesystem-handlers-use-safe_join}
|
||||
|
||||
組み込みのチェックはよくあるケースを止めますが、サンドボックスの境界までは知りようがありません。ファイルシステムにアクセスする場合は、`safe_join` を使ってパスを解決し、ベースディレクトリの内側に収まっていることを検証してください。
|
||||
|
||||
```python title="server.py" hl_lines="4 14"
|
||||
--8<-- "docs_src/uri_templates/tutorial002.py"
|
||||
```
|
||||
|
||||
`safe_join` は、単純な文字列チェックでは見逃してしまうシンボリックリンクによる脱出、`..` の並び、絶対パスを使ったトリックを検出します。解決されたパスが `DOCS_ROOT` の外に出ると `PathEscapeError` を送出し、クライアントには `ResourceError` として伝わります。
|
||||
|
||||
### デフォルトが妨げになるとき {#when-the-defaults-get-in-the-way}
|
||||
|
||||
チェックが正当な値をブロックしてしまうこともあります。カタログのインポートツールが意図的に絶対パスを受け取る場合や、パラメーターが `../sibling` のような相対参照で、ハンドラーがファイルシステムに触れずに安全に解釈する場合などです。そのパラメーターをチェックの対象から除外するか、サーバー全体のポリシーを緩めてください。
|
||||
|
||||
```python title="server.py" hl_lines="9 16-19"
|
||||
--8<-- "docs_src/uri_templates/tutorial003.py"
|
||||
```
|
||||
|
||||
* デコレーターに付けた `security=ResourceSecurity(exempt_params={"source"})` は、その 1 つのリソースのその 1 つのパラメーターに限ってチェックをスキップします。サーバーのほかの部分はデフォルトのポリシーのままです。
|
||||
* `MCPServer` のコンストラクターに渡す `resource_security=` は、すべてのリソースのデフォルトを設定します。ここでは `relaxed` によって `..` のチェックが完全に無効になります。
|
||||
|
||||
設定できるチェックは次のとおりです。
|
||||
|
||||
| 設定 | デフォルト | 動作 |
|
||||
|-------------------------|---------|-------------------------------------|
|
||||
| `reject_path_traversal` | `True` | 開始ディレクトリの外に出る `..` の並びを拒否する |
|
||||
| `reject_absolute_paths` | `True` | `/foo`、`C:\foo`、UNC パス、ドライブ相対の `C:foo` を拒否する(`x:y` も対象になる) |
|
||||
| `reject_null_bytes` | `True` | `\x00` を含む値を拒否する |
|
||||
| `exempt_params` | 空 | チェックをスキップするパラメーター名 |
|
||||
|
||||
これらのチェックはヒューリスティックな事前フィルターです。ファイルシステムへのアクセスでは、依然として `safe_join` が封じ込めの境界です。
|
||||
|
||||
!!! tip
|
||||
ハンドラーがリクエストに応えられない場合(ファイルが存在しない、ID が不明など)は、例外を送出してください。SDK がそれをエラーレスポンスに変換します。プロトコルエラーとツールエラーの違いについては、**[エラーの処理](handling-errors.md)** を参照してください。
|
||||
|
||||
## 低レベル Server でのリソース {#resources-on-the-low-level-server}
|
||||
|
||||
低レベルの `Server` の上に構築している場合(**[低レベル Server](../advanced/low-level-server.md)** を参照)は、プロトコルメソッド `resources/list` と `resources/read` のハンドラーを直接登録します。デコレーターはなく、プロトコルの型は自分で返します。
|
||||
|
||||
### 静的リソース {#static-resources}
|
||||
|
||||
固定の URI については、レジストリを持っておき、完全一致でディスパッチします。
|
||||
|
||||
```python title="server.py" hl_lines="17 21 27"
|
||||
--8<-- "docs_src/uri_templates/tutorial004.py"
|
||||
```
|
||||
|
||||
一覧ハンドラーは利用できるものをクライアントに伝え、読み取りハンドラーはコンテンツを返します。まずレジストリを調べ、テンプレートがあればそちら(後述)に回し、それ以外はすべて例外を送出してください。
|
||||
|
||||
### テンプレート {#templates}
|
||||
|
||||
`MCPServer` が使っているテンプレートエンジンは `mcp.shared.uri_template` にあり、単独でも動作します。解析とマッチングは同じものが手に入ります。ルーティングとセキュリティポリシーの組み立ては自分で行います。
|
||||
|
||||
```python title="server.py" hl_lines="13-16 22-25 29 33 45"
|
||||
--8<-- "docs_src/uri_templates/tutorial005.py"
|
||||
```
|
||||
|
||||
ハイライトされた行では 3 つのことが行われています。
|
||||
|
||||
* **解析は一度、マッチングはリクエストごと。** `UriTemplate.parse()` がテンプレートを組み立て、`template.match(uri)` が抽出した変数を `dict` として返します。URI が合わなければ `None` です。URL のデコードは `match()` の内部で行われ、デコード後の値はパス安全性の検証なしにそのまま返されます。値は文字列として出てくるので、自分で変換してください(`int(matched["id"])`、`Path(matched["path"])`)。
|
||||
* **安全性チェックは自分で適用する。** `MCPServer` がデフォルトで実行する `..` と絶対パスのチェックは `mcp.shared.path_security` にあります。`read_manual_safely` は `MANUALS` に触れる前にそれらを呼び出します。パラメーターがファイルシステムのパスでない場合(ISBN や検索クエリなど)は、その値のチェックをスキップしてください。ポリシーは設定オブジェクト経由ではなく、ハンドラーごとに制御します。
|
||||
* **テンプレートの一覧も同じ情報源から。** クライアントは `resources/templates/list` を通じてテンプレートを見つけます。`str(template)` は元のテンプレート文字列を返すので、一覧とマッチャーは同じ 1 つの情報源を共有します。
|
||||
|
||||
## まとめ {#recap}
|
||||
|
||||
* `{name}` は 1 つのセグメントにマッチし、`{+name}` はスラッシュを保持し、`{?a,b}` はクエリ文字列から値を取り出し、`{/name*}` はセグメントをリストに分割します。
|
||||
* 間に何もない 2 つの変数や、2 つ目の複数セグメント変数は、解析時に拒否されます。末尾の `{?...}`/`{&...}` のクエリ変数に束縛されるパラメーターは、Python のデフォルト値を宣言しなければなりません。
|
||||
* パラメーターに型注釈を付ければ(`order_id: int`)、SDK が変換します。
|
||||
* デフォルトのセキュリティポリシーは、ハンドラーの実行前に `..`、絶対パス、ヌルバイトを拒否します。リソースごとに上書きするには `security=ResourceSecurity(...)` を、サーバー全体で上書きするには `resource_security=` を使います。
|
||||
* ファイルシステムへのアクセスでは、`safe_join` が封じ込めの境界です。
|
||||
* 低レベルの `Server` では、`UriTemplate.parse()` で解析し、`.match()` でマッチングし、`mcp.shared.path_security` を自分で適用します。
|
||||
Loading…
Reference in a new issue