リソース
リソースとは、アプリケーションが読めるように公開するデータです。
これが分かれ目です。ツールはモデルが呼び出すと決めるものです。リソースはアプリケーションが読み込むと決めるもの(設定ファイル、レコード、ドキュメントなど)で、コンテキストとしてモデルの前に置かれます。
宣言するには、普通の Python 関数に @mcp.resource(uri) を付けます。
最初のリソース
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("config://app")
def get_config() -> str:
"""The active shop configuration."""
return "theme=dark\nlanguage=en"
形はツールと同じで、1 つだけ加わるものがあります。URI です。リソースは名前ではなくアドレスで指定されます。クライアントが要求するのは config://app であって、get_config ではありません。
残りは、やはり SDK が関数から読み取ります。
- 名前は関数名、つまり
get_configです。 - クライアントに見える説明は docstring です。
- 内容は関数が返すものです。
resources/list でクライアントが受け取るのは次のとおりです。
{
"name": "get_config",
"uri": "config://app",
"description": "The active shop configuration.",
"mimeType": "text/plain"
}
そして config://app を読むと関数が実行され、戻り値がテキストとして返ってきます。
result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]
Tip
一覧の取得は軽い処理です。関数は resources/list のときには呼び出されません。呼び出されるのは resources/read のときだけで、それも要求された URI についてだけです。リソースを 1000 個公開しても、コストがかかるのは誰かが開いたものだけです。
試してみる
MCP Inspector でサーバーを起動してください。
uv run mcp dev server.py
表示された URL を開き、Resources タブに移動してください。一覧に config://app が説明付きで並んでいます。クリックすると Inspector がそれを読み込み、2 行の設定が表示されます。
リソーステンプレート
レコードごとに URI を 1 つずつ用意するやり方はスケールしません。URI にプレースホルダーを置き、それに対応するパラメーターを関数に持たせます。
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("config://app")
def get_config() -> str:
"""The active shop configuration."""
return "theme=dark\nlanguage=en"
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
"""A customer's profile."""
return f"User {user_id}: 12 orders since 2021."
URI に {user_id}、関数に user_id: str と書きます。約束事はこれですべてです。
これでリソーステンプレートになり、居場所も変わります。resources/list からは外れ、代わりに resources/templates/list に、アドレスではなくパターンとして現れます。
{
"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 として渡されます。
result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]
結果の uri に注目してください。これはクライアントが要求した具体的な URI であって、テンプレートではありません。
Check
プレースホルダーとパラメーターは一致している必要があります。URI が {user_id} のまま関数のパラメーターを user に改名すると、デコレーターはインポート時に、つまりどのクライアントも触れないうちに拒否します。
ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}
不一致はバグでしかありえないので、SDK は不一致を抱えたままではサーバーを起動できないようにしています。
プレースホルダーの構文は RFC 6570 です。複数セグメントにまたがる値には {+path}、省略可能なクエリパラメーターには {?q,lang} というように、ほかにも書き方があります。また、SDK は取り出した値に対して、デフォルトでパス安全性のチェックを行います。完全なリファレンスは URI テンプレートとパス安全性 を参照してください。
get_user_profile は、Context と注釈を付けたパラメーターを受け取ることもできます。SDK はそれを URI パラメーターとして扱うことなく注入します。それで何が得られるかは Context のページで説明しています。
何を返すか
返せるのは str だけではありません。リソースごとに mime_type を指定し、合うものを返してください。
import base64
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("docs://readme", mime_type="text/markdown")
def readme() -> str:
"""How to use this server."""
return "# Bookshop\n\nSearch the catalog with the `search_books` tool."
@mcp.resource("stats://catalog", mime_type="application/json")
def catalog_stats() -> dict[str, int]:
"""Live counts for the catalog."""
return {"books": 1204, "authors": 391}
@mcp.resource("covers://placeholder", mime_type="image/gif")
def placeholder_cover() -> bytes:
"""A 1x1 transparent GIF, shown when a book has no cover."""
return base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
readmeはstrを返すので、そのまま送られます。これがよくあるケースです。-
catalog_statsはdictを返すので、SDK が 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(...) で登録します。
クライアントはリソースを購読して、変更があったときに通知を受け取ることもできます。これはクライアント側の話なので、クライアント で説明しています。
まとめ
- 関数に
@mcp.resource(uri)を付けるとリソースになります。URI がアドレス、戻り値が内容、docstring が説明です。 - URI に
{placeholder}を入れるとテンプレートになります。resources/templates/listに載り、マッチするすべての URI に 1 つの関数が応答します。 - プレースホルダーの名前は関数のパラメーター名と一致させなければなりません。間違えても、気づくのは本番ではなくインポート時です。
- 関数が実行されるのはリソースが読まれたときで、一覧に載るときではありません。
strはテキストに、bytesは base64 の blob に、それ以外は JSON テキストになります。ラベルを付けるにはmime_type=を使います。- ツールはモデルが行動するためのもので、リソースはアプリケーションが読むためのものです。
3 つ目のプリミティブ、つまり人がメニューから選ぶものが プロンプト です。