0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCP サーバーに Resources を実装して Claude Code から読ませる手順 — URI テンプレートが一覧に出ない等3つのハマりどころ【2026】

0
Posted at

自作 MCP サーバーに「Tools」だけ生やして満足していたが、Claude Code 側から @ でファイルのようにデータを参照させたくなり Resources を実装した。ドキュメント通りに書いたはずが「一覧に出ない」「中身が文字化けする」で数時間溶かしたので、動かし方とハマりどころを残す。

対象と前提

  • 想定読者: MCP サーバーを1つ以上自作したことがあり、Tools は動いている人
  • 前提知識: Python の基本、stdio 経由の MCP サーバー接続
  • 環境: Python 3.13 / MCP Python SDK 1.x / Claude Code v2 系(2026年8月時点)

TL;DR

  • Resources は「LLM が読むための読み取り専用データ」。副作用のある処理は Tool、参照させたいデータは Resource と割り切る
  • @mcp.resource("scheme://path") で生やせるが、引数付き(URI テンプレート)は一覧 API が別なので「出てこない」と勘違いしやすい
  • 返り値の mimeType とテキスト/バイナリの対応を間違えると静かに壊れる
  • 更新通知(notifications/resources/updated)は購読されていないと届かない。ポーリング前提で設計する

最小実装

FastMCP を使うと数行で生える。

# server.py
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("inventory")

@mcp.resource("config://runtime", mime_type="application/json")
def runtime_config() -> str:
    """現在のランタイム設定を返す(静的リソース)"""
    return json.dumps({"region": "ap-northeast-1", "max_workers": 4}, ensure_ascii=False)

@mcp.resource("report://daily/{date}", mime_type="text/markdown")
def daily_report(date: str) -> str:
    """日付を指定して日次レポートを返す(URI テンプレート)"""
    # date は "2026-08-18" 形式で渡ってくる
    return f"# {date} のレポート\n\n- 処理件数: 128\n- 失敗: 0\n"

if __name__ == "__main__":
    mcp.run()

登録と動作確認:

claude mcp add inventory -- python3 /path/to/server.py
claude mcp list          # ✓ Connected を確認

Claude Code のセッション内で /mcp を開き、対象サーバーの「Resources」を選ぶと一覧が見える。プロンプト内では @inventory:config://runtime のように参照できる。

claude を起動せずに素の JSON-RPC で叩くなら:

echo '{"jsonrpc":"2.0","id":1,"method":"resources/list","params":{}}' | python3 server.py

resources 配列に config://runtime が入っていれば成功。

ハマりどころ1: URI テンプレートが resources/list に出ない

report://daily/{date} を実装したのに一覧に現れず、実装ミスを疑って1時間潰した。原因は仕様どおりの挙動で、引数を持つリソースは別メソッドで返る

resources/list            → 静的リソースのみ
resources/templates/list  → {param} を含む URI テンプレート

クライアント側が resources/templates/list を叩いていなければ、当然どこにも出ない。回避策は2つ。

  • よく使う値は静的リソースとしても登録しておく(例: report://daily/latest)
  • 「日付を指定して取りたい」ような動的取得は、素直に Tool として公開する

Resource は「一覧から選んで読ませる」もの、Tool は「引数を組み立てて呼ばせる」もの、と役割で切ったほうが結局ハマらない。

ハマりどころ2: mimeType とテキスト/バイナリの不一致

MCP のリソース内容は textblob(base64)のどちらかで返る。ここで JSON を返すつもりで dict をそのまま返すと、SDK が str 化してシングルクォート混じりの Python 表現が送られ、受け側のパースが静かに失敗する。

# NG: dict をそのまま返す → "{'region': 'ap-northeast-1'}" になりうる
@mcp.resource("config://ng")
def ng() -> dict:
    return {"region": "ap-northeast-1"}

# OK: 明示的に JSON 文字列へ
@mcp.resource("config://ok", mime_type="application/json")
def ok() -> str:
    return json.dumps({"region": "ap-northeast-1"}, ensure_ascii=False)

画像や PDF を返す場合は bytes を返し mime_type="image/png" などを指定する。SDK が base64 化して blob に載せてくれる。text に base64 文字列を自前で詰めないこと(二重エンコードになる)。

確認は resources/read のレスポンスを見るのが早い。

echo '{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"config://ok"}}' \
  | python3 server.py | python3 -m json.tool

contents[0]text が入っているか blob が入っているかで判別できる。

ハマりどころ3: 更新通知は「届かない前提」で書く

リソースが変わったことを知らせる notifications/resources/updated は、クライアントが resources/subscribe を送っていて、かつ通知に対応している場合のみ意味を持つ。未対応のクライアントに通知を投げても何も起きず、サーバー側だけが「更新は伝えた」つもりになる。

実運用では次のどちらかに寄せた。

  • 内容にタイムスタンプを埋め、読んだ側が鮮度を判断できるようにする
  • 「最新を取り直す」動作を Tool として用意し、必要なときに明示的に呼ばせる
@mcp.resource("status://current", mime_type="application/json")
def status() -> str:
    return json.dumps({
        "generated_at": "2026-08-18T08:00:00+09:00",  # 実際は現在時刻を入れる
        "healthy": True,
    }, ensure_ascii=False)

読み手(LLM)は「いつ時点のデータか」が本文に書いてあれば古さを自分で判断できる。通知機構に頼るより堅い。

背景: なぜ Tool ではなく Resource なのか

Tool は「モデルが自分で呼ぶ関数」、Resource は「人間かアプリが選んで文脈に載せるデータ」という設計思想の違いがある。Resource にしておくと毎ターン勝手に呼ばれてトークンを食うことがなく、参照したいときだけ @ で差し込める。設定ファイル・スキーマ定義・仕様書のような「参照はするが更新はしない」データとは相性がいい。

まとめ

  • Resources は読み取り専用データの提供口。@mcp.resource() で数行
  • URI テンプレートは resources/templates/list 側。一覧に出なくてもバグではない
  • JSON は必ず json.dumps して mime_type を明示。バイナリは bytes を返して SDK に任せる
  • 更新通知は届かない前提。本文にタイムスタンプを埋めるほうが確実
  • 迷ったら「一覧から選ばせたい → Resource / モデルに判断させたい → Tool」で切り分ける
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?