docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
5 changed files with 4821 additions and 2277 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 70b9ece244ca1b0c, cba78e052898c3f6, f06bdb541cb0b469, fb82d526320b7cc3]
|
||||
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 10394c6f16601638, cba78e052898c3f6, f06bdb541cb0b469, 6dc898ccc5a903f9]
|
||||
tool: 1
|
||||
---
|
||||
# 既存のアプリに組み込む {#add-to-an-existing-app}
|
||||
|
|
@ -86,7 +86,7 @@ Starlette の `Host` ルートも同じように動きます。`Mount("/", ...)`
|
|||
--8<-- "docs_src/asgi/tutorial004.py"
|
||||
```
|
||||
|
||||
これでクライアントは `/notes/mcp` ではなく `/notes` に接続します。
|
||||
これでクライアントは `/notes/mcp` ではなく `/notes/` に接続します。
|
||||
|
||||
## ブラウザークライアント向けの CORS {#cors-for-browser-clients}
|
||||
|
||||
|
|
@ -127,4 +127,4 @@ Starlette の `Host` ルートも同じように動きます。`Mount("/", ...)`
|
|||
* ブラウザークライアントには CORS が必要です。`Mcp-*` リクエストヘッダーのための `allow_headers` と、レスポンスのための `expose_headers=["Mcp-Session-Id"]` です。
|
||||
* `@mcp.custom_route()` は、認証なしの素の HTTP エンドポイントを `/mcp` の隣に追加します。
|
||||
|
||||
サーバーに実際の URL で到達できるようになったら、**[クライアント](../client/index.md)** はサーバーオブジェクトの代わりにその URL を使って接続します。
|
||||
サーバーに実際の URL で到達できるようになったら、**[クライアント](../client/index.md)** はその URL を使ってサーバーに接続します。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, d1dc4c54cd00ec9c, 14ad3bc7904036bb, 5225f127bc1b9c77, fe1626fdd5aad1da, 4556cb7ea1a04a31]
|
||||
sections: [d62c13457fc4a534, 80e73abaca6e0652, 128a492d18295f64, 14ad3bc7904036bb, 54a6697833fcc1d9, fe1626fdd5aad1da, 811d083c1da8bcf6]
|
||||
tool: 1
|
||||
---
|
||||
# 認可 {#authorization}
|
||||
|
|
@ -23,12 +23,12 @@ OAuth の用語でいえば、サーバーは**リソースサーバー**です
|
|||
|
||||
有効なトークンがどんな形をしているかについて、SDK は何の前提も持ちません。**`TokenVerifier`** を実装して、こちらから伝えます。
|
||||
|
||||
```python title="server.py" hl_lines="12-14 19-24"
|
||||
```python title="server.py" hl_lines="14-16 21-27"
|
||||
--8<-- "docs_src/authorization/tutorial001.py"
|
||||
```
|
||||
|
||||
* `TokenVerifier` は非同期メソッドを 1 つだけ持つプロトコルです。`verify_token` は `Authorization` ヘッダーから取り出した生のトークンを受け取り、有効なら **`AccessToken`** を、無効なら `None` を返します。実装するものはほかにありません。
|
||||
* この例ではトークンをテーブルから引いています。実際のものは JWT の署名を検証するか、認可サーバーのトークンイントロスペクションエンドポイントを呼び出します。そのコードは自分で書きます。SDK はそれを呼び出すだけです。
|
||||
* この例ではトークンをテーブルから引いています。各エントリには、そのトークンがどのリソース向けに発行されたかが記録されています。実際のものは JWT の署名を検証するか、認可サーバーのトークンイントロスペクションエンドポイントを呼び出し、トークンが誰向けに発行されたか(`aud`)を `AccessToken.resource` で報告します。そのコードは自分で書きます。SDK はそれを呼び出すだけです。
|
||||
* `token_verifier=` と `auth=` は必ずセットで渡します。片方だけ渡すと、`MCPServer(...)` はリクエストを 1 つも処理しないうちに `ValueError` を送出します。
|
||||
|
||||
`AuthSettings` はリソースサーバーの表向きの顔です。
|
||||
|
|
@ -36,6 +36,10 @@ OAuth の用語でいえば、サーバーは**リソースサーバー**です
|
|||
* `issuer_url`:トークンを発行する認可サーバー。
|
||||
* `resource_server_url`:この MCP エンドポイントの公開 URL。トークンが「どの」リソース向けかを示す名前であり、ディスカバリードキュメントが置かれる場所でもあります。
|
||||
* `required_scopes`:すべてのトークンがこれらをすべて持っている必要があります。
|
||||
* `validate_token_resource`:`AccessToken.resource` が `resource_server_url` でないトークンをすべて拒否します。`resource_server_url` を設定したままこれを未設定にすると警告(`MCPDeprecationWarning`)が出て、`False` として動作します。3.0 ではリソースサーバーのデフォルトが `True` になります。
|
||||
* 認可サーバーが、クライアントの要求した `resource` にトークンを結び付ける場合は有効にしてください。MCP クライアントはこれを常に送ります。`resource_server_url` は、クライアントが接続する URL と正確に一致させてください。
|
||||
* 認可サーバーが独自のオーディエンス識別子(Auth0 の API 識別子、Entra のアプリケーション ID)を使う場合は無効のままにし、代わりにベリファイアーの中で `aud` を確認して、このサーバー向けでないトークンには `None` を返してください。
|
||||
* `aud` がリストの場合は、`resource_server_url` と等しいエントリを `resource` に入れてください。
|
||||
|
||||
!!! tip
|
||||
SDK リポジトリの `examples/servers/simple-auth/` には、実際の認可サーバーの [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) エンドポイントを呼び出す `IntrospectionTokenVerifier` があります。本番用のベリファイアーの多くはこの形になります。
|
||||
|
|
@ -85,7 +89,7 @@ OAuth の用語でいえば、サーバーは**リソースサーバー**です
|
|||
|
||||
どのハンドラーの中でも、**`get_access_token()`** は現在のリクエストに対してベリファイアーが返した `AccessToken` です。
|
||||
|
||||
```python title="server.py" hl_lines="4 32-35"
|
||||
```python title="server.py" hl_lines="4 35-38"
|
||||
--8<-- "docs_src/authorization/tutorial002.py"
|
||||
```
|
||||
|
||||
|
|
@ -117,6 +121,6 @@ SDK が提供するのはリソースサーバーの半分、つまり検証、
|
|||
* `token_verifier=` と `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` は必ずセットで渡します。
|
||||
* SDK は [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata を `/.well-known/oauth-protected-resource/...` で公開し、未認証のリクエストには、そこを指す `WWW-Authenticate` ヘッダー付きの 401 で応答します。ディスカバリーの仕組みはこれだけです。
|
||||
* どのハンドラーでも、`get_access_token()` を呼べば誰が呼び出しているかがわかります。
|
||||
* 認可は HTTP の関心事です。`stdio` とインメモリクライアントがそれを目にすることはありません。
|
||||
* 認可は HTTP の関心事です。`stdio` とインメモリのテストクライアントがそれを目にすることはありません。
|
||||
|
||||
クライアント側の半分(認可サーバーを見つけてトークンを取得してくれる部分)については、**[OAuth クライアント](../client/oauth-clients.md)**を参照してください。そして、ユーザーに尋ねる代わりに ID を「アサート」するクライアントについては、**[ID アサーション](../client/identity-assertion.md)**を参照してください。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, e758745df6fb7b0a]
|
||||
sections: [28221886b198784f, f88ea1f1614f3a1d, 2e76f5cb9df15042, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, 697b01d95080880d]
|
||||
tool: 1
|
||||
---
|
||||
# デプロイとスケール {#deploy-scale}
|
||||
|
|
@ -42,6 +42,22 @@ translation:
|
|||
|
||||
この文言はクライアント側では見つかりません。`421` は JSON-RPC エラーではなくプレーンテキストの HTTP レスポンスなので、MCP クライアントは汎用的なトランスポートエラーを送出します。気に入らなかったホスト名は**サーバー**のログに、警告として 1 行出るだけです。デプロイしたばかりのサーバーがすべての接続を拒否するなら、そうでないと証明されるまでは Host の許可リストが原因です。**[トラブルシューティング](../troubleshooting.md)** もここから始まります。
|
||||
|
||||
## TLS 終端プロキシの背後で {#behind-a-tls-terminating-proxy}
|
||||
|
||||
TLS がプロキシ(イングレス、ロードバランサー、Caddy、nginx)で終端し、その背後で uvicorn が平文の HTTP を配信する場合は、プロキシの `X-Forwarded-*` ヘッダーを信頼するよう uvicorn に指示します。
|
||||
|
||||
```console
|
||||
uvicorn server:app --proxy-headers --forwarded-allow-ips='<proxy address>'
|
||||
```
|
||||
|
||||
これがないと、アプリは自分が `http://` で配信されていると思い込み、発行するリダイレクト(よくあるのは `/mcp` → `/mcp/`)はすべて `http://…` を指します。Python クライアントは、HTTPS エンドポイントから平文の HTTP へのリダイレクトには従わず、その旨を伝えます。
|
||||
|
||||
```text
|
||||
MCPError: Redirect to http://mcp.example.com/mcp/ not followed: it would downgrade this HTTPS endpoint to plain HTTP.
|
||||
```
|
||||
|
||||
クライアント側の応急処置は、サーバーが配信する正確な URL(`https://mcp.example.com/mcp/`、末尾のスラッシュ込み)を設定して、リダイレクトが起きないようにすることです。根本的な解決策は上のフラグです。`FORWARDED_ALLOW_IPS` はその環境変数版の書き方です。`*` はすべてのホップを信頼しますが、それが正しいのはプロキシ以外から uvicorn に到達できない場合だけです。
|
||||
|
||||
## ワーカーと、スティッキーにする必要があるのは誰か {#workers-and-who-has-to-be-sticky}
|
||||
|
||||
ホスト名が応答するようになったら、その背後に複数のワーカーを置きます。そのための SDK の設定項目はありません。Starlette アプリは、どんな ASGI アプリとも同じ方法でスケールします。fork の仕方を知っているものにオブジェクトを渡すだけです。
|
||||
|
|
@ -154,6 +170,7 @@ python -c "import secrets; print(secrets.token_hex(32))"
|
|||
## まとめ {#recap}
|
||||
|
||||
* デフォルトでは、このアプリは localhost 宛てのリクエストにだけ応答します。`transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])` が公開時の関門です。これを渡すまでは、本物のホスト名の背後ではすべてのリクエストが `421` になり、理由はサーバーのログにしか出ません。
|
||||
* TLS 終端プロキシの背後では、uvicorn を `--proxy-headers --forwarded-allow-ips=...` 付きで実行してください。そうしないとリダイレクトが `http://` を指し、クライアントに拒否されます。
|
||||
* 2026-07-28 ではセッションはなく、ロードバランサーがスティッキーにすべき対象もありません。`stateless_http=True` がレガシー専用の設定項目なのは、モダンなリクエストはこのフラグが読まれる前にルーティングされ、応答されるからです。
|
||||
* デフォルトの `requestState` の鍵は、プロセスごとに生成される `os.urandom(32)` です。別のワーカーに届いたマルチラウンドトリップのリトライは、`-32602` *"Invalid or expired requestState"* で失敗します。
|
||||
* 解決策は `RequestStateSecurity(keys=[...])` **と**、すべてのインスタンスで同じサーバー名にすることです。名前はトークンのデフォルトの audience クレームです。鍵も同じ、名前も同じ。
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 9fd2357154a5b7e7, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
|
||||
tool: 1
|
||||
---
|
||||
# サーバーの実行 {#running-your-server}
|
||||
|
|
@ -72,6 +72,8 @@ Inspector は本物のホストとまったく同じことをします。`server
|
|||
* `json_response=True`:各 POST に SSE ストリームではなく単一の JSON ボディで応答します。このボディにはレスポンスしか入る余地がありません。そのため、リクエストの途中でクライアントを呼び返すツール(`ctx.elicit()` やサンプリング)は、この区間で `NoBackChannelError` を送出します。進行中の呼び出しに紐づく通知(`ctx.report_progress()` による進捗や呼び出しごとのログメッセージ)は破棄されますが、独立した `GET` ストリームは無関係な通知を引き続き運びます。
|
||||
* `stateless_http=True`:リクエストごとに新しいトランスポートを作り、セッションを追跡しません。
|
||||
* `max_request_body_size`:受け付けるリクエストボディの最大サイズ(バイト単位)です。デフォルトは 4 MiB で、これより大きいリクエストはパースやセッション作成の前に HTTP 413 を受け取ります。正当な MCP メッセージがこのサイズを超える場合にだけ引き上げてください。
|
||||
* `session_idle_timeout`:レガシーセッションが処理中のものを何も持たないまま待機できる秒数で、これを過ぎるとサーバーがセッションを閉じます。デフォルトは 1800 です。`None` で無効になります。[セッションの存続期間と上限](legacy-clients.md#session-lifetime-and-limits) を参照してください。
|
||||
* `max_sessions`:1 つのプロセスが同時に保持するレガシーセッションの数です。デフォルトは 10 000 です。`None` で上限がなくなります。同じセクションで扱っています。
|
||||
* `event_store`、`retry_interval`、`transport_security`:再開可能性と DNS リバインディング保護です。localhost 以外の場所にデプロイするまでは後回しでかまいません。`transport_security` については **[デプロイとスケール](deploy.md)** で扱います。
|
||||
|
||||
!!! warning
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [3d1663c18edc824c, d4fd37009a13f03d, af9f398a5a8b679a, 470c2dd144294d69, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
|
||||
sections: [3d1663c18edc824c, 90956965ae6a1ca1, af9f398a5a8b679a, 5ce83b1f9d88da62, 0d9b5d13fffc94e5, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
|
||||
tool: 1
|
||||
---
|
||||
# レガシークライアントへの対応 {#serving-legacy-clients}
|
||||
|
|
@ -18,15 +18,25 @@ SDK はすべてのリクエストを `MCP-Protocol-Version` ヘッダーで振
|
|||
|
||||
## 1 つのハンドラーで両方の世代 {#one-handler-both-eras}
|
||||
|
||||
ユーザーに何かを尋ねる必要があるツールと、それを呼び出す両方の世代のクライアントを示します。
|
||||
ユーザーに何かを尋ねる必要があるツールを示します。
|
||||
|
||||
```python title="server.py" hl_lines="24 37-38"
|
||||
```python title="server.py" hl_lines="21"
|
||||
--8<-- "docs_src/legacy_clients/tutorial001.py"
|
||||
```
|
||||
|
||||
`reserve` には、モデルが渡してこなかったものが 1 つ必要です。何冊予約するかです。ツールはそれを `Annotated[..., Resolve(ask_quantity)]` で宣言します(詳しくは **[依存関係](../handlers/dependencies.md)** を参照してください)。`reserve` の中には、バージョンを指定する箇所も、ケイパビリティを確認する箇所も、分岐する箇所もありません。
|
||||
|
||||
2 つのクライアントは同じ `mcp` オブジェクトに対して**同時に**開かれています。`mode="legacy"` は `initialize` ハンドシェイクを実行します。2026 年より前のクライアントが開くのとまったく同じ接続です。もう一方はデフォルトのままで、`2026-07-28` になります。
|
||||
これを HTTP で公開します。続いて、両方の世代のクライアントがそれを呼び出す様子です。
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
```python title="client.py" hl_lines="14-15"
|
||||
--8<-- "docs_src/legacy_clients/tutorial001_client.py"
|
||||
```
|
||||
|
||||
2 つのクライアントは、動作中の同じサーバーに対して**同時に**開かれています。`mode="legacy"` は `initialize` ハンドシェイクを実行します。2026 年より前のクライアントが開くのとまったく同じ接続です。もう一方はデフォルトのままで、`2026-07-28` になります。別のターミナルから `python client.py` を実行してください。
|
||||
|
||||
```text
|
||||
2025-11-25 {'result': "Reserved 2 of 'Dune'."}
|
||||
|
|
@ -53,6 +63,31 @@ SDK はすべてのリクエストを `MCP-Protocol-Version` ヘッダーで振
|
|||
!!! warning
|
||||
`event_store=` は解決策に見えますが、そうではありません。これは**再開可能性**(「同じ」セッションに再接続するクライアントに、取りこぼした SSE イベントを再送する機能)であって、セッションストアではありません。別のプロセスからセッションに到達できるようにはしません。
|
||||
|
||||
## セッションの有効期間と上限 {#session-lifetime-and-limits}
|
||||
|
||||
レガシーセッションは永遠には生き続けませんし、1 つのプロセスが無制限にセッションを抱えることもありません。これを制御する設定が 2 つあります。どちらも `run()`、`streamable_http_app()`、`Server.streamable_http_app()` のキーワード引数です。モダンな(`2026-07-28` の)接続と `stateless_http=True` にはセッションがないため、どちらの設定も適用されません。
|
||||
|
||||
| 設定 | デフォルト | 動作 | クライアントから見えるもの | 無効にするには |
|
||||
|---|---|---|---|---|
|
||||
| `session_idle_timeout` | `1800`(30 分) | 処理中のものが何もない状態がその時間続いたセッションを閉じます。 | `404 Session not found`。もう一度 `initialize` する必要があります。 | `None` |
|
||||
| `max_sessions` | `10_000` | その数を超えて新しいセッションを開くことを拒否します。既存のセッションには手を付けず、何も追い出しません。 | `503 Too many open sessions`、JSON-RPC コードは `-32603` です。 | `None` |
|
||||
|
||||
「処理中」と見なされるものは次のとおりです。
|
||||
|
||||
* 開いている `GET` ストリーム。SDK のクライアントは 1 本を開いたままにするため、接続中のクライアントのセッションが期限切れになることはありません。
|
||||
* まだ応答中のリクエスト。タイムアウトより長く動くツール呼び出しが中断されることはなく、カウントダウンはその呼び出しが終わってから始まります。
|
||||
* それ以外にはありません。リクエストとリクエストの間は時計が進みます。セッション上のどんなリクエストでも時計はリセットされ、`ping` も例外ではありません。一度期限切れになったセッションを復活させる手段はありません。
|
||||
|
||||
`DELETE` でセッションを終了したクライアントは、そのセッションを即座に解放します。最初のリクエストが拒否されたクライアントも同様です。
|
||||
|
||||
```python
|
||||
mcp.run(transport="streamable-http", session_idle_timeout=None, max_sessions=50_000)
|
||||
```
|
||||
|
||||
どちらの出来事もサーバーのログに残ります。期限切れは `INFO` レベルの `Session <id> idle timeout` です。セッションを開くのを拒否した場合は `WARNING` レベルの `Refusing to open a new session: <n> sessions are already open` です。
|
||||
|
||||
これらの上限はプロセスごとです。ワーカーが 4 つなら上限は `max_sessions` の 4 倍で、各ワーカーは自分のセッションだけを期限切れにします。
|
||||
|
||||
## 唯一のスイッチ:`stateless_http` {#the-one-knob-stateless_http}
|
||||
|
||||
スティッキー性というコストを払いたくないなら、変更できるものがちょうど 1 つだけあります。
|
||||
|
|
@ -73,7 +108,7 @@ SDK はすべてのリクエストを `MCP-Protocol-Version` ヘッダーで振
|
|||
`json_response=True` はそのスイッチではありませんが、「すべての」レガシーセッションで同じコストの半分を負います。1 つの JSON ボディで応答される `POST` にはリクエストスコープのチャネル用のストリームがないため、リクエスト途中の `ctx.elicit()` は同じ `NoBackChannelError` を送出し、そのリクエストに結び付いた通知は捨てられます。セッションのスタンドアロンストリームには影響しません。無関係な通知は引き続き届きます。
|
||||
|
||||
!!! check
|
||||
あえて間違ったことをしてみましょう。`reserve` は、先ほど両方のクライアントに応答したそのツールです。これを `stateless_http=True` でデプロイし、同じ 2 つのクライアントを HTTP で接続して、それぞれから呼び出してください。
|
||||
あえて間違ったことをしてみましょう。`reserve` は、先ほど両方のクライアントに応答したそのツールです。これを `stateless_http=True` でデプロイし、同じ 2 つのクライアントを接続して、それぞれから呼び出してください。
|
||||
|
||||
モダンなクライアントには引き続き `Reserved 2 of 'Dune'.` が返ります。モダンな経路は変わっていません。
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue