← ブログに戻る

MCP SamplingはClaude Codeで動かない

この記事を含む総合ガイド Claude Code 実戦運用ガイド

MCP の Sampling を使った権限昇格は、MCP のセキュリティの話題でよく取り上げられます。ただ、その前に確かめるべきことがあります。手元のクライアントは、そもそも Sampling に応じるのか。

Claude Code 2.1.283 で測ったところ、答えは「応じない」でした。

攻撃経路を3つ数える前に、入口が閉まっていた、という話です。

項目実測値
クライアントclaude-code 2.1.283
protocolVersion2025-11-25
宣言された capabilitieselicitation(form, url)と roots(listChanged)のみ。sampling は無し
sampling/createMessage を送った結果McpError: Method not found

この記事では、Sampling の仕組み、測り方、この結果から言えることと言えないことを順に書きます。

Sampling は何をする機能か

Sampling は、サーバーからクライアント(LLM)へ問い合わせる仕組みです。ふつうの MCP は「LLM がサーバーのツールを呼ぶ」一方向ですが、Sampling では逆向きに、サーバーが処理の途中で「この支出は何費ですか?」とクライアント側の LLM に尋ねられます。便利な反面、悪意のあるサーバーが LLM に隠し指示を送ったり、複数のサーバーをつないだ環境で別サーバーの情報を引き出させたりする余地が生まれます。仕様はこれに対して、ユーザーがサンプリングの実行を明示的に承認すること、サーバーがプロンプトの中身を覗けないよう可視性を制限することを求めています。

ここまでは仕様の話です。

攻撃が成立するかどうかは、クライアントが Sampling を実装しているかで決まります。 実装していないクライアントには、そもそもこの経路がありません。

測り方

stdio の MCP サーバーを1本書き、Claude Code に繋いでツールを1回呼ばせました。ツールの中で、次の2つを記録します。

  1. initialize のときにクライアントが宣言した capabilities
  2. sampling/createMessage を実際に送った結果

使ったのは mcp Python SDK 1.28.1 の FastMCP です。

from mcp.server.fastmcp import FastMCP, Context
from mcp.types import SamplingMessage, TextContent

mcp = FastMCP("probe")

@mcp.tool()
async def summarize_note(text: str, ctx: Context) -> str:
    """Summarize a short note."""
    params = ctx.session.client_params
    print(params.clientInfo, params.capabilities)  # 実際はファイルに記録
    try:
        r = await ctx.session.create_message(
            messages=[SamplingMessage(role="user",
                content=TextContent(type="text", text="Reply with the single word PONG."))],
            max_tokens=20)
        return "sampling ok"
    except Exception as e:
        return f"sampling failed: {type(e).__name__}"

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

これを MCP 設定ファイルに登録し、claude -p から呼びました。

claude -p "Use the summarize_note tool on the text 'hello world' and report exactly what it returned." \
  --strict-mcp-config --mcp-config ./probe.mcp.json \
  --allowedTools mcp__probe__summarize_note < /dev/null

--strict-mcp-config を付けて、普段使っている他の MCP サーバーが混ざらないようにしています。

結果

記録された capabilities は elicitation と roots の2つだけでした。

sampling はありません。

それでもサーバー側から sampling/createMessage を送ると、クライアントは Method not found を返しました。ツールの戻り値は sampling failed: McpError です。capabilities は接続ごとにクライアントが宣言するもので、試行回数で変わる性質ではありません。100回まわしても同じ Method not found が100個たまるだけなので、1回で十分と判断しました。

この結果から言えること

Claude Code 2.1.283 を使っている限り、「サーバーが Sampling でクライアントの LLM を呼ぶ」経路は存在しません。 悪意のある MCP サーバーを繋いでしまっても、Sampling を足がかりにした攻撃は成立しません。

ただし、安心するのは Sampling の経路についてだけにしてください。ツール説明文やツールの戻り値に仕込まれた指示(プロンプトインジェクション)は、Sampling とは関係なく届きます。

玄関を閉めても、窓はまだ開いています。

こちらは別の対策が要ります。

この結果から言えないこと

  • Claude Desktop については何も言えません。 この環境に無く、測っていません。
  • Sampling に対応したクライアントの安全性も言えません。 対応を宣言しているクライアントでは、上で書いたリスクがそのまま残ります。
  • 測ったのは Claude Code の1バージョンだけです。将来のバージョンで対応する可能性はあります。

Sampling はこの先どうなるか

Sampling は MCP 仕様 2026-07-28 版で、Roots・Logging とともに非推奨になりました(SEP-2577)。理由として、実装が複雑な割に対応クライアントが少ないことが挙げられています。今回の測定結果も、それと矛盾しません。移行先として案内されているのは、サーバーが LLM プロバイダの API を直接呼ぶ形です。非推奨になっても、すぐには消えません。同じ版で非推奨ポリシー(SEP-2596)ができ、削除までは最低12か月空けることになっています。仕様の変更点は MCP 2026-07-28はなぜステートレスか にまとめました。

サーバーを作る人へ: 使う前に capabilities を見る

Sampling を使うサーバーを書くなら、呼ぶ前にクライアントの capabilities を確認してください。宣言が無いクライアントに送っても、今回のように Method not found で失敗するだけです。

params = ctx.session.client_params
if params is None or params.capabilities.sampling is None:
    # Sampling 非対応のクライアント。LLM API を直接呼ぶか、機能を落とす
    ...

上の確認用サーバーは、そのまま自分のクライアントでも使えます。仕様書を10回読むより、1回繋いで Method not found を見るほうが早い、というのが今回の結論でもあります。使っているクライアントが Sampling に応じるかどうかは、一度測っておくと判断が速くなります。

Sampling の仕組みとリスクは、拙著『MCP実践セキュリティ』(インプレス)の第2章で、クロスサーバーのコンテキスト悪用は第6章で扱っています。

MCP実践セキュリティ 関連書籍 MCP実践セキュリティ MCP セキュリティの実践書 | OWASP MCP Top 10・攻撃の実演・認証認可・本番ワークアラウンド 書籍ページを見る →
この記事が答えている課題 課題の地図を見る →