サンプリングとルート
ハンドラーは、接続しているクライアントにさらに 2 つのことを要求できます。1 つはクライアント自身のモデルによる補完、つまりサンプリングです。もう 1 つはクライアントのワークスペースフォルダー、つまりルート(roots)です。
どちらも、SDK が話すすべてのプロトコルバージョンで引き続き動作します。ただし、これらを前提に設計する前に、次の警告を読んでください。
2026-07-28 仕様で非推奨
サンプリングとルートは 2026-07-28 で非推奨になりました(SEP-2577)。引き続き完全に機能し、削除の対象になるまで少なくとも 12 か月は仕様に残りますが、新しい実装はこれらを土台にすべきではありません。推奨される移行先は次のとおりです。サンプリングの代わりに LLM プロバイダーの API と直接統合し、ルートの代わりにツールのパラメーター、リソース URI、またはサーバー設定でディレクトリを渡します。SDK 全体の一覧は 非推奨の機能 にあります。
サンプリング:クライアントのモデルを借りる
リゾルバーが Sample(...) を返すと、ツールは補完結果を受け取ります。これは 依存関係 で Elicit を動かしているのと同じ依存関係のしくみを通ります。
from typing import Annotated
from mcp.server import MCPServer
from mcp.server.mcpserver import Resolve, Sample
from mcp.types import CreateMessageResult, SamplingMessage, TextContent
mcp = MCPServer("Bookshop")
def draft_blurb(title: str) -> Sample:
prompt = f"Write a one-sentence blurb for the book {title!r}."
return Sample(
[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))],
max_tokens=60,
)
@mcp.tool()
async def blurb(title: str, draft: Annotated[CreateMessageResult, Resolve(draft_blurb)]) -> str:
"""Draft a blurb for a book."""
return draft.content.text if draft.content.type == "text" else "No blurb."
Sample(messages, max_tokens=...)はsampling/createMessageのパラメーターをそのまま反映しています。注入される値はクライアントのCreateMessageResultです。toolsまたはtool_choiceを渡すと、代わりにCreateMessageResultWithToolsになります。- クライアントは
samplingケイパビリティを宣言している必要があります(toolsまたはtool_choiceを渡す場合はsampling.tools)。宣言していない場合、クライアントが処理できないリクエストを送る代わりに、呼び出しは-32021のプロトコルエラーで失敗します。バックチャネル(back-channel)のない 2026 年より前のセッションでは、送る経路がそもそもないため、いつものバックチャネルなしのエラーで失敗します。 2026-07-28では、リクエストはマルチラウンドトリップ(multi-round-trip)のフローの中で配送されます(マルチラウンドトリップリクエスト)。2025-11-25では、クライアントへの単独のリクエストです。コードはどちらでも同じですが、マルチラウンドトリップのルールに注意してください。リクエストは再試行の各ラウンドでまったく同じ内容にならなければならないため、ツールの引数やその他の安定したデータだけから組み立ててください。include_contextには触れないでください。"none"以外の値はそれ自体が非推奨で(SEP-2596)、ほとんどのクライアントが宣言しないケイパビリティを必要とします。
ルート:これはどこに置くべきか
ルートは、サーバーが操作してよいとクライアントが示すフォルダーです。参考情報としての案内であり、アクセス制御のしくみではありません。リゾルバーは ListRoots() を返します。
from typing import Annotated
from mcp.server import MCPServer
from mcp.server.mcpserver import ListRoots, Resolve
from mcp.types import ListRootsResult
mcp = MCPServer("Bookshop")
def workspace_roots() -> ListRoots:
return ListRoots()
@mcp.tool()
async def catalog_folder(roots: Annotated[ListRootsResult, Resolve(workspace_roots)]) -> str:
"""Pick the folder the catalog export should go to."""
if not roots.roots:
return "No workspace folders shared."
return str(roots.roots[0].uri)
- 注入される
ListRootsResultはRootのリストを持ちます。それぞれがfile://URI と、省略可能な表示名です。 - 条件はサンプリングと同じです。
rootsケイパビリティが宣言されていなければ、リクエストを送る代わりに呼び出しは-32021で失敗します。
通信路の反対側では、クライアントはすでに持っているコールバックで両方のリクエストに応答します。sampling_callback と list_roots_callback で、クライアントのコールバック で説明しています。
2025 年世代の接続では
ctx.session.create_message(...) と ctx.session.list_roots() は、セッションを直接操作するコードのために今も存在します。これらはバックチャネルが存在する場所(2025 年世代の、ステートレスではない接続)でのみ動作し、呼び出すと非推奨の警告が出ます。上で紹介したリゾルバーのマーカーがサポートされる形です。ネゴシエートされたバージョンから配送方法を選び、警告も出しません。
まとめ
- リゾルバーから
Sample(...)またはListRoots()を返します。ツールは、ほかの依存関係と同じようにCreateMessageResultまたはListRootsResultを受け取ります。 - クライアントは対応するケイパビリティを宣言しなければなりません。そうでなければ、リクエストは送られず、呼び出しは
-32021で失敗します。 - どちらの機能も
2026-07-28で非推奨です。当面は完全に機能しますが、新しい設計には向きません。サンプリングよりプロバイダーの API を、ルートより明示的なパラメーターを選んでください。
遅いツールがどこまで進んだかを報告するには、進捗 を参照してください。