コンテンツにスキップ

リソース

機械翻訳

このページは英語版ドキュメントから自動翻訳されたものであり、正式な版は英語版のページです。不自然な箇所があれば、翻訳についてで報告の方法を説明しています。

リソースとは、アプリケーションが読めるように公開するデータです。

これが分かれ目です。ツールはモデルが呼び出すと決めるものです。リソースはアプリケーションが読み込むと決めるもの(設定ファイル、レコード、ドキュメントなど)で、コンテキストとしてモデルの前に置かれます。

宣言するには、普通の Python 関数に @mcp.resource(uri) を付けます。

最初のリソース

server.py
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 にプレースホルダーを置き、それに対応するパラメーターを関数に持たせます。

server.py
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/profileusers://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 を指定し、合うものを返してください。

server.py
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")
  • readmestr を返すので、そのまま送られます。これがよくあるケースです。
  • catalog_statsdict を返すので、SDK が JSON テキストにシリアライズしてくれます。

    {
      "books": 1204,
      "authors": 391
    }
    
  • placeholder_coverbytes を返すので、クライアントは 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 クラス(TextResourceBinaryResourceFileResourceHttpResourceDirectoryResource)が用意されており、mcp.add_resource(...) で登録します。

クライアントはリソースを購読して、変更があったときに通知を受け取ることもできます。これはクライアント側の話なので、クライアント で説明しています。

まとめ

  • 関数に @mcp.resource(uri) を付けるとリソースになります。URI がアドレス、戻り値が内容、docstring が説明です。
  • URI に {placeholder} を入れるとテンプレートになります。resources/templates/list に載り、マッチするすべての URI に 1 つの関数が応答します。
  • プレースホルダーの名前は関数のパラメーター名と一致させなければなりません。間違えても、気づくのは本番ではなくインポート時です。
  • 関数が実行されるのはリソースが読まれたときで、一覧に載るときではありません。
  • str はテキストに、bytes は base64 の blob に、それ以外は JSON テキストになります。ラベルを付けるには mime_type= を使います。
  • ツールはモデルが行動するためのもので、リソースはアプリケーションが読むためのものです。

3 つ目のプリミティブ、つまり人がメニューから選ぶものが プロンプト です。