MCPのsubscriptions/listenとは — 購読ストリームの新方式
MCPのsubscriptions/listenは、resources/subscribeとHTTP GETエンドポイントを置き換える新しい購読方式です。仕組みと確認応答の流れを解説します。
なぜresources/subscribeが置き換えられたのか
MCPの2026-07-28リビジョンは、サーバーからクライアントへ変更を通知する仕組みを1本化しました(MCPの全体像はMCPとはを参照してください)。それまであった resources/subscribe / resources/unsubscribe というRPCと、Streamable HTTPのGETエンドポイントの両方を廃止し、代わりに subscriptions/listen という単一のメソッドに統合しています。
背景にあるのは、プロトコル全体をステートレスにする方針です。同じリビジョンで initialize ハンドシェイクとセッションIDが廃止され、リクエストは1本ずつ独立して扱われるようになりました。通知の受け口だけが古いGETエンドポイント方式のまま残っていると、この設計と整合しません。subscriptions/listen は、長時間開いたままにする1本のストリームという形で、通知の受け口を明示的なリクエスト・レスポンスの枠組みに乗せ直したものです。
以前の resources/subscribe は、更新を知りたいリソースごとに個別のRPCを呼ぶ設計でした。ツール一覧の変更やプロンプト一覧の変更は、それとは別にHTTPのGETで待ち受ける形になっており、通知の種類ごとに窓口が分かれていました。subscriptions/listen はこの窓口を1つにまとめ、どの種類の通知を受け取りたいかをリクエストのパラメータとして渡す形に整理しています。窓口が1本になったことで、購読の開始・確認・終了という一連のライフサイクルを、1つのリクエストIDを軸にして追跡できるようになりました。
subscriptions/listenとは何か
subscriptions/listen は、サーバーからクライアントへの長時間ストリームを開くリクエストです。1回限りのリクエストと違い、このストリームはクライアントが自分で終了させるまで開いたままになり、その間ずっと通知を届け続けます。
クライアントは、受け取りたい通知の種類を notifications フィルタで指定してリクエストを送ります。
| フィールド | 型 | 内容 |
|---|---|---|
toolsListChanged | 型boolean | 内容ツール一覧が変わったときの通知を受け取る |
promptsListChanged | 型boolean | 内容プロンプト一覧が変わったときの通知を受け取る |
resourcesListChanged | 型boolean | 内容リソース一覧が変わったときの通知を受け取る |
resourceSubscriptions | 型string[] | 内容指定したリソースURIの更新通知を受け取る |
すべてのフィールドは任意です。指定しなければ、その種類の通知は購読しないという扱いになります。サーバーは、クライアントが明示的に要求していない種類の通知を送ってはいけません。
確認応答は最初の1通で届く
サーバーはストリームを開いた最初のメッセージとして、必ず notifications/subscriptions/acknowledged を送ります。これより前に、そのストリーム上で他の通知を送ることは禁止されています。
確認応答には、サーバーがどのフィルタを受け入れたかが notifications フィールドとして返ります。サーバーが対応していない通知の種類は、ここから省かれます。クライアントは、自分がリクエストした内容と確認応答を突き合わせ、対応していない種類があれば適切に扱う必要があります。
stdioでは1つの通信路をすべてのメッセージが共有するため、順序の扱いが独特です。確認応答が「そのストリーム上で最初」という順序保証は、購読IDごとに個別に定義されています。つまり、別の購読に属するメッセージは、この確認応答より先に届いても構いません。すべての通知には io.modelcontextprotocol/subscriptionId が _meta に載るため、クライアントはこの値でどの購読に属するメッセージかを見分けます。
この設計は、HTTPのように購読ごとに別々のコネクションを張れる環境と、stdioのようにプロセス全体で1本の通信路しか持てない環境の両方を、同じメソッドでカバーするための妥協点です。HTTPでは通常、購読ごとに独立したストリームが立つため順序の混線はほぼ起きません。stdioを実装するときだけ、この subscriptionId によるデマルチプレクスを意識する必要があります。
複数の購読を同時に開ける
クライアントは、ツール一覧の変更を監視する購読と、特定リソースの更新を監視する購読を、同時に複数持つことができます。それぞれの購読は、それを開いた subscriptions/listen リクエストのJSON-RPC IDで識別されます。
stdioのようにすべてのメッセージが1本の通信路を流れる環境では、この識別が特に重要です。通知に載る subscriptionId を見ずに処理すると、別の購読向けの通知を誤って処理してしまう実装ミスにつながります。
購読はどう終わるか
購読が終わる経路は3つあります。クライアントが能動的に終了する場合、サーバーが自分の都合(シャットダウンなど)で終了する場合、そしてトランスポートそのものが切断される場合です。
クライアントが終了するときは、HTTPならSSEストリームを閉じるだけで済みます。stdioでは、対象の subscriptions/listen リクエストIDを指定した notifications/cancelled 通知を送ります。購読の終了に、別の仕様であるCancellationの語彙をそのまま流用している形です。
サーバー側から終了するときは、突然ストリームを切るのではなく、元の subscriptions/listen リクエストに対する完了レスポンスを先に返してから閉じることが望ましいとされています。この完了レスポンスには通常の結果フィールド以外の中身はありませんが、_meta に購読IDが載ることで、どの購読が正常終了したかが分かります。レスポンスなしにトランスポートが切れた場合は、予期しない切断とみなし、クライアントは再接続を試みてよいことになっています。
stdioでは、接続が切れて再確立された場合、サーバー側は購読の状態を一切保持していません。クライアントは自分から subscriptions/listen を送り直して、購読を再構築する必要があります。
Claude Codeはこの購読ストリームを使っている
Claude CodeはMCP TypeScript SDK 2.0を使う新しい接続方式(v2ランタイム)で、2026-07-28のプロトコルリビジョンに対応しています。Claude Code v2.1.232以降が既定でこのv2ランタイムを使い、HTTPサーバーとclaude.ai経由のコネクターに対しては、新しいリビジョンに対応しているかを自動で確認します(接続設定の詳細はClaude Code MCP設定ガイドを参照してください)。
v2ランタイムでこの新しいリビジョンのサーバーに接続すると、Claude Codeは list_changed 通知を、開いたままのストリーム経由で受け取ります。2026-07-28リビジョンでこのストリームを開く手段は subscriptions/listen なので、実質この仕組みが使われています。ツール一覧が変わったときに、サーバーとの接続を張り直さなくても最新の状態を反映できるのは、この購読ストリームのおかげです。
ストリームが切れたときの挙動も文書化されています。10秒以内に再び切れることが続くと最大3回まで再接続を試み、そこで諦めます。10秒より長く開いていたストリームが切れた場合は、1時間に5回再接続したあと、次の再接続までおよそ6時間待ちます。サーバーレスホスティングのように短命な接続を繰り返すサーバーを想定した挙動です。ストリームが再開するまでの間、Claude Codeはそのサーバーから最後に取得したツール・プロンプト・リソースの情報を保持し続けます。再接続がうまくいかないときの切り分け方はMCPサーバーに接続できないときの切り分け手順にまとめています。
このv2ランタイムは既定で常に有効になるわけではありません。Amazon BedrockやGoogle Cloudのエージェントプラットフォーム経由で動かす場合や、Claude Apps Gateway経由でサインインしている場合は、引き続きMCP TypeScript SDK 1.xベースのv1ランタイムが使われます。stdioサーバーに対しては、環境変数 MCP_PROTOCOL_NEGOTIATION を auto に設定しない限り、Claude Codeは新しいリビジョンを確認しに行きません。自作のstdioサーバーで subscriptions/listen に対応していても、この設定をしていなければClaude Codeからは旧方式のまま見えることになります。
# 自作のstdio MCPサーバーに新リビジョンを確認させる
MCP_PROTOCOL_NEGOTIATION=auto claude実装で踏みやすい落とし穴
実装してテストを書いて初めて表面化しやすい不具合を挙げます(デバッグ時にメッセージのやり取りを直接確認したい場合はMCP Inspectorの使い方が役立ちます)。
- 確認応答より先に通知を送らない: サーバーはストリームの最初のメッセージとして必ず
acknowledgedを送る必要があります。実装の都合で通知を先に流すと仕様違反になります - フィルタを超えた通知を送らない: クライアントが購読していない種類の通知を送ってはいけません。サーバー側の内部イベントをそのまま垂れ流す実装は避けます
subscriptionIdを無視した通知処理: 複数の購読を扱うクライアントで、この識別子を見ずに通知を処理すると、別の購読向けの更新を取り違える不具合になります- stdio再接続時の状態復元忘れ: サーバーは購読の状態を保持しないため、クライアント側が再接続後に購読を再送信する処理を持っていないと、通知が届かないまま気づかない状態になります
- 完了レスポンスなしの強制切断: サーバー側の都合で購読を終える実装が、完了レスポンスを返さずにいきなりストリームを閉じると、クライアントは異常切断と判断して不要な再接続を試みます。シャットダウン処理には完了レスポンスを組み込みます
まとめ
subscriptions/listen は、resources/subscribe とHTTP GETエンドポイントという2つの古い仕組みを1本のストリームに統合した、2026-07-28リビジョンの目玉のひとつです。確認応答から始まり、複数の購読を subscriptionId で識別し、3つの経路のいずれかで終了する、という一連の流れを押さえておけば実装で迷うことは少なくなります。
Claude CodeはすでにこのストリームをMCPサーバーの list_changed 通知の受け口として使っています。自作のMCPサーバーがツールやリソースを動的に更新する設計なら、この仕組みを正しく実装しておく価値があります。実装の各論はClaude Code MCP設定ガイドで補ってください。