Context
ツールの引数はモデルから渡されます。それ以外のすべて(処理中のリクエスト、ツールが属するサーバー、クライアントに話しかける手段)は、1 つのオブジェクトから得られます。それが Context です。
自分で組み立てる必要も、設定する必要もありません。要求するだけです。
要求する
任意のツールに、Context で注釈したパラメーターを追加してください。
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bookshop")
@mcp.tool()
def search_books(query: str, ctx: Context) -> str:
"""Search the catalog by title or author."""
return f"[request {ctx.request_id}] Found 3 books matching {query!r}."
- SDK はリクエストごとに新しい
Contextを組み立てて渡します。 - パラメーターの名前は関係ありません。
ctx、context、cのどれでもよく、SDK は注釈を見て見つけます。 - リソースやプロンプトでも、同じように宣言できます。
ctx.request_idは、関数がいま処理しているリクエストの id です。
Info
FastAPI を使ったことがあれば、この仕組みには見覚えがあるはずです。フレームワーク自身の型(あちらでは Request、こちらでは Context)でパラメーターを宣言すると、フレームワークがそれを供給します。登録するものも設定するものもありません。型注釈がこの仕組みのすべてです。
モデルからは見えない
ここはしっかり身につけておきたい部分です。tools/list が search_books について報告する入力スキーマは次のとおりです。
{
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"}
},
"required": ["query"],
"title": "search_booksArguments"
}
プロパティは 1 つです。ctx は引数ではありません。スキーマには決して現れず、モデルに知らされることもなく、どのクライアントも値を入れられません。これは作成者と SDK の間の取り決めであり、通信上には現れません。
試してみる
MCP Inspector でサーバーを実行してください。
uv run mcp dev server.py
search_books のフォームには query フィールドが 1 つだけあります。dune を指定して呼び出してください。
[request 3] Found 3 books matching 'dune'.
この数字は、たまたまそのときのリクエストの番号です。もう一度ツールを呼び出すと変わります。リクエストごとに専用の Context が作られるからです。
何が得られるか
注入されるオブジェクトは小さなものです。request_id のほかに次のものがあります。
await ctx.read_resource(uri):ツールの中からサーバー自身のリソースを 1 つ読みます。次のセクションで扱います。await ctx.report_progress(progress, total, message):長い呼び出しの最中に、進捗を呼び出し側へ逐次送ります。詳しくは 進捗 を参照してください。await ctx.elicit(message, schema)とawait ctx.elicit_url(...):ツールを一時停止してユーザーに質問します。これが エリシテーション(elicitation) です。ctx.session:このクライアントとの会話のサーバー側です。クライアントに送る通知はここにあり、最後のセクションで使います。ctx.headers:トランスポートが運んだリクエストヘッダー、stdio ではNoneです。カスタムヘッダーは(ctx.headers or {}).get("x-...")で読めます。ヘッダーはクライアントが与える入力です。ロケールや機能フラグには使えますが、身元の確認には決して使わないでください。ctx.request_context:リクエストごとの生のレコードです。実際に手を伸ばすフィールドはlifespan_context、つまり起動コードが yield したオブジェクトです(ライフスパン を参照)。
ロギングは意図的にこの一覧に入れていません。サーバーは、ほかの Python プログラムと同じく Python の logging モジュールでログを記録します。その理由は短いページ ロギング にまとめてあります。
Tip
注入が行われるのは登録した関数だけです。ツールが呼び出すヘルパーに専用の Context は渡されないので、ctx を通常の引数として渡してください。どこか別の場所から取り出せる暗黙の「現在のコンテキスト」はありません。
自分のリソースを読む
サーバーのリソースはクライアントだけのものではありません。ツールからも読めます。
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bookshop")
@mcp.resource("catalog://genres")
def genres() -> str:
"""The genres the catalog is organised into."""
return "fiction, non-fiction, poetry"
@mcp.tool()
async def describe_catalog(ctx: Context) -> str:
"""Describe how the catalog is organised."""
[contents] = await ctx.read_resource("catalog://genres")
return f"The catalog is organised into: {contents.content}"
ctx.read_resource は resources/read を処理するのと同じレジストリを通して URI を解決するので、ツールはクライアントが受け取るのと同じものを得ます。コンテンツブロックごとに 1 つの ReadResourceContents を持つイテラブルです。この URI の場合は 1 つです。
contents.content # 'fiction, non-fiction, poetry'
contents.mime_type # 'text/plain'
contentはgenres()が返したものそのままです。情報源は 1 つです。クライアントはリソースを閲覧し、ツールはそれを消費し、誰も文字列をコピーしません。describe_catalogの唯一のパラメーターはContextなので、その入力スキーマにはプロパティが 1 つもありません。モデルは{}で呼び出します。
一覧が変わったことをクライアントに伝える
サーバーが提供するものは、インポート時に固定されるわけではありません。実行時にツールを登録し、それをクライアントに伝えます。
from mcp.server import MCPServer
from mcp.server.mcpserver import Context
mcp = MCPServer("Bookshop")
def recommend_book(genre: str) -> str:
"""Recommend a book in the given genre."""
return f"In {genre}, try 'Dune'."
@mcp.tool()
async def enable_recommendations(ctx: Context) -> str:
"""Switch on the recommendation tool."""
mcp.add_tool(recommend_book)
await ctx.session.send_tool_list_changed()
return "Recommendations are now available."
mcp.add_tool(recommend_book)は普通の関数をツールとして登録します。名前、説明、スキーマは@mcp.tool()を使った場合とまったく同じように導出されます。await ctx.session.send_tool_list_changed()はnotifications/tools/list_changedを送ります。これを受け取ったクライアントはtools/listを再度呼び出し、recommend_bookを目にします。
同種のメソッドには send_resource_list_changed()、send_prompt_list_changed()、そして特定の 1 つのリソースの変更を知らせる send_resource_updated(uri) があります。
2026-07-28 の接続では、クライアントは自分が開いた subscriptions/listen ストリーム上でしか変更通知を受け取らないため、上記の send_* メソッドはそれらのストリームに届きません。Context の公開メソッドは、購読中のすべてのストリームに一度に配信します。await ctx.notify_tools_changed()、await ctx.notify_prompts_changed()、await ctx.notify_resources_changed()、await ctx.notify_resource_updated(uri) です。レプリカをまたいだスケールアウトも含め、詳しくは サブスクリプション を参照してください。
Check
誰かが enable_recommendations を実行するまで、約束しているツールは存在しません。それでも呼び出すと、結果はモデルが読めるエラーです。
Unknown tool: recommend_book
enable_recommendations を実行すると、まったく同じ呼び出しが成功します。ツールの一覧は本当に動的です。tools/list は「いま」登録されているものをそのまま反映します。
まとめ
- パラメーターに
Contextを注釈すると(ツールでも、リソースでも、プロンプトでも)、SDK がそれを注入します。名前は自由です。 - モデルからは見えません。入力スキーマに含まれるのは、常に本物の引数だけです。
ctx.request_idはリクエストを識別し、ctx.request_context.lifespan_contextは起動時に yield したものです。await ctx.read_resource(uri)を使うと、ツールからサーバー自身のリソースを読めます。ctx.sessionはクライアントへ戻るチャネルです。send_tool_list_changed()とその同種のメソッドは、変更した一覧を取得し直すようクライアントに伝えます。- 進捗の報告とエリシテーションも
Contextが出発点です。それぞれに専用のページがあります。
モデルが目にすることのない、自分の関数で埋めるパラメーターが 依存関係 です。