Claude Media
MCPのページネーション仕様 — カーソルで一覧を取得する設計

MCPのページネーション仕様 — カーソルで一覧を取得する設計

MCPのtools/list等が返す大量データを、不透明なカーソルで分割取得する仕様を解説します。ページサイズの扱いと実装時の注意点を仕様原文から示します。

MCPはtools/listresources/listのような一覧取得の応答が大きくなりすぎないよう、カーソルベースのページネーションを仕様として定義しています。ページ番号ではなく、サーバーが発行する不透明な(opaque)文字列トークンで「どこまで読んだか」を管理する方式です。MCPサーバーを自作するなら、ツール数やリソース数が増えた将来を見越してこの仕様に沿った実装をしておく必要があります。

MCPのページネーションはなぜページ番号でないのか

一覧系のAPIを設計するとき、ページ番号方式(page=3のような指定)は直感的です。ですがMCPの仕様はこの方式を採らず、カーソル方式を選んでいます。理由は仕様には明記されていませんが、構造から読み取れる利点は3つあります。

1つ目は、サーバー側でページサイズを自由に変えられることです。仕様は「ページサイズはサーバーが決定し、クライアントは固定のページサイズを仮定してはならない(MUST NOT)」と定めています。ページ番号方式だと、途中でページサイズを変更したときにクライアント側の期待とずれが生じますが、カーソル方式ならその心配がありません。

2つ目は、一覧の途中でデータが増減しても破綻しにくいことです。ページ番号は「何件目から何件目まで」という絶対位置に依存しますが、カーソルは「直前の応答のどこまで返したか」という相対位置を表すトークンです。データベースのカーソルベースページネーションと同じ発想であり、MCPが外部のツールやリソースを大量に持つ実際のサーバー(社内のファイルサーバーや大規模なAPIカタログを持つサーバーなど)を想定した設計であることがうかがえます。

3つ目は、2026-07-28リビジョンで進んだMCPのステートレス化と関係していることです。このリビジョンはStreamable HTTPトランスポートからMcp-Session-Idヘッダーとプロトコルレベルのセッション概念そのものを削除しました。tools/listresources/listprompts/listのような一覧系エンドポイントの結果は、もう接続ごとに変わりません。

コネクションをまたいだ状態が必要なら、サーバーは明示的なハンドルを通常のツール引数として渡す設計に切り替える必要があります。カーソルという「不透明な文字列トークンに位置情報を閉じ込める」設計は、このステートレス化の方向性と噛み合っています。カーソル自体がリクエストの一部として運ばれるパラメータであり、コネクションの生存に依存しない値だからです。

対象となる操作は次の4つです。

  • resources/list — 利用可能なリソースの一覧
  • resources/templates/list — リソーステンプレートの一覧
  • prompts/list — 利用可能なプロンプトの一覧
  • tools/list — 利用可能なツールの一覧

Claude Codeに接続するMCPサーバーがツールを数十〜数百件持つケースは珍しくありません。tools/listが対象に含まれている以上、ページネーションはツール数が多いサーバーでは他人事ではない仕様です。

リクエストとレスポンスの仕様

ページネーションは、サーバーが返すレスポンスnextCursorフィールドが含まれることで始まります。

{
  "jsonrpc": "2.0",
  "id": "123",
  "result": {
    "resultType": "complete",
    "resources": [ "..." ],
    "nextCursor": "eyJwYWdlIjogM30=",
    "ttlMs": 300000,
    "cacheScope": "public"
  }
}

レスポンス例に出てくるresultType: "complete"は、ページネーション固有のフィールドではなく、MCP全体の応答フォーマットが共通で持つフィールドです。値がcompleteならリクエストが最終結果まで完了したことを示し、input_requiredならMulti Round-Trip Requestsパターンでさらに入力が必要なことを示します。一覧取得の応答は常にcompleteを返す設計で、resultTypeが欠落している旧リビジョンの実装からの応答はcompleteとみなす後方互換ルールも定められています。

続きを取得するクライアントは、このカーソルをそのまま次のリクエストparams.cursorに載せます。

{
  "jsonrpc": "2.0",
  "id": "124",
  "method": "resources/list",
  "params": {
    "cursor": "eyJwYWdlIjogMn0="
  }
}

このやり取りをnextCursorが返らなくなるまで繰り返すのが基本フローです。仕様が定める要件は次の3点です。

  1. カーソルは不透明な文字列として扱う。形式を推測したり、パースしたり、値そのものから何かを判定してはならない(MUST)
  2. 空文字列も有効なカーソルであり、「一覧の終端」として扱ってはならない(MUST)
  3. nextCursorが存在しなければ、そこが一覧の終端である(SHOULD)

とくに2点目のMUSTは見落としやすく、「カーソルが空文字なら終わり」という直感的だが誤った実装をしてしまうと、最後のページを取りこぼす不具合になります。

不正なカーソルを受け取った場合、サーバーはJSON-RPCのエラーコード-32602(Invalid params)で応答するべき(SHOULD)とされています。このエラーコードは2026-07-28リビジョンで統一されたもので、以前のリビジョンでは「見つからないリソース」に-32002という専用コードが割り当てられていましたが、JSON-RPC仕様との整合を取るために-32602へ変更されました。カーソル絡みのエラーも同じコードに揃えられている形です。2026-07-28の1つ前のプロトコルリビジョンは2025-11-25で、当時からこの仕様を実装しているサーバーはエラーコードの変更点として認識しておく必要があります。

実装イメージ — サーバー側とクライアント側で守ることが違う

仕様はサーバー側とクライアント側それぞれに推奨事項(SHOULD)を分けて定めています。役割ごとの内訳は次のとおりです。

役割求められる挙動
サーバー求められる挙動安定したカーソルを発行する / 無効なカーソルを受け取ってもクラッシュせず適切に処理する
クライアント求められる挙動nextCursorが無ければ終端とみなす / ページネーションありとなしの両方のフローに対応する
クライアント(MUST)求められる挙動カーソルを不透明なトークンとして扱い、形式を推測・改変しない

実装で迷いやすいのは「クライアントは複数ページにまたがる一覧を、途中でデータが変わっても整合性のあるスナップショットとして扱えるか」という点です。仕様はこの点に直接答えていませんが、キャッシュ仕様側に「ページごとに独立してキャッシュされ、ページ間の一貫性は保証しない」という補足があり、一覧の途中でデータが変わりうる前提で設計されていることが読み取れます。厳密な一貫性が必要なクライアントは、カーソルなしで最初から取り直す実装にする必要があります。

もう1点、レスポンス例にttlMscacheScopeという、ページネーション本体とは別の仕様(キャッシュ)のフィールドが同居している点にも注目です。2026-07-28リビジョンでtools/list等の一覧系レスポンスには、この2フィールドが必須(MUST)になりました。この必須化はSEP-2549という提案番号で管理されている変更で、changelog上もページネーション本体の変更とは別のマイナー変更として列挙されています。ページネーションを実装するなら、返す各ページに正しいキャッシュヒントを付与する作業も同時に発生することになります。

実際の取得フローを追うと、クライアント側の実装イメージがつかみやすくなります。まず初回リクエストはcursorを含めずに送ります。サーバーは1ページ目の結果とnextCursorを返し、クライアントはこの値を保持したまま次のリクエストのparams.cursorに載せて再送します。この往復を、レスポンスにnextCursorが含まれなくなるまで繰り返します。

途中でサーバーが「そのカーソルはもう無効」と判断した場合(たとえば内部状態が入れ替わった場合)は、-32602のエラーが返ります。仕様はこのケースへの対処法を明示していませんが、キャッシュ仕様側の記述と合わせると「カーソルが無効になったら、保持していたページのキャッシュを全て破棄し、カーソルなしで最初から取得し直す」のが妥当な回復手順です。

MCPサーバー開発者がページ番号方式の直感を捨てる境目

ページネーションの実装でつまずくのは、たいてい「ページ番号方式のメンタルモデルをそのままカーソル方式に持ち込む」ときです。具体的には、カーソルの中身をBase64デコードして中身を覗き見て、それに依存したロジックを書いてしまうケースがあります。仕様の例に出てくるeyJwYWdlIjogM30=はBase64エンコードされたJSON({"page": 3})に見えますが、これはあくまで一例であり、値の構造に依存してよいという意味ではありません。次のリビジョンでサーバー実装がカーソルの中身を変えた瞬間、クライアントのロジックは壊れます。

もう1つの落とし穴は、tools/listのページネーションを「ツールが少ないから関係ない」と後回しにすることです。MCPサーバーは接続先を増やすたびにツール数が積み上がる性質を持ちます。単体では10個のツールしかないサーバーでも、企業導入で複数のデータソースをラップするようになると数百件規模に膨れることがあります。

ページネーション未対応のクライアント実装は、そうした成長局面で初めて表面化する種類の不具合です。ツール数が閾値を超えた瞬間に「一部のツールがClaudeから見えなくなった」という報告として現れやすく、原因がページネーション対応漏れだと気づくまでに時間がかかりがちな不具合でもあります。MCP Inspectorでツール一覧のレスポンスを確認する際は、nextCursorの有無もあわせてチェックしておくと、後からの手戻りを避けられます。

まとめ

MCPのページネーションは、resources/listresources/templates/listprompts/listtools/listの4操作を対象に、不透明なカーソル文字列で一覧を分割取得する仕組みです。ページ番号ではなくカーソルを採用しているため、クライアントはカーソルの中身を解釈せず、nextCursorの有無だけで終端を判定します。不正なカーソルへのエラーコードは-32602に統一されており、一覧系レスポンスにはキャッシュ仕様のttlMscacheScopeも同居します。この2つの仕様が同じレスポンスに同居している事実自体が、MCPの一覧取得がページ分割と鮮度管理をセットで設計されていることを物語っています。実装するときは、どちらか一方だけを後回しにせず、両方をまとめて対応する前提で進めるのが結局のところ近道です。Claude Code接続前提でMCPサーバーを設計するなら、ツール数が伸びる将来を見越してページネーションとキャッシュヒントの両方を最初から組み込んでおくのが安全です。

この記事を共有:XはてブLinkedIn
MCP をもっと見る →