社内システムをMCPサーバーとして公開すると、リソースやツールの数は想定より早く増えます。最初は数件だった resources/list が数百件になり、クライアントが応答を待ち続ける、あるいは先頭の一部しか取得できていないことに気づかない、という事故につながります。MCPには一覧取得のためのページネーション仕様があり、守るべきルールは多くありません。
MCPのページネーションはどんな仕組みか
MCPの一覧取得は、番号ページではなく不透明なcursor文字列で位置を表す方式で、1ページの件数はサーバーが決めます。
仕様上、cursor は結果セット内の位置を表す不透明な文字列トークンです。ページ番号やオフセットではありません。サーバーは応答に現在ページの結果と、続きがある場合のみ nextCursor を含めます。
``json``
{
"jsonrpc": "2.0",
"id": "123",
"result": {
"resources": [],
"nextCursor": "eyJwYWdlIjogM30="
}
}
続きが必要なクライアントは、受け取った値を params.cursor に入れて同じメソッドを再度呼びます。nextCursor が無い応答が最終ページです。1ページの件数は固定ではなく、クライアントは件数を仮定してはなりません(MUST NOT)。
どの操作がページネーション対象か
ページネーションに対応するのは、resources/list・resources/templates/list・prompts/list・tools/list の4操作です。
仕様が挙げる対象は次の4つです。
resources/list: リソース一覧resources/templates/list: リソーステンプレート一覧prompts/list: プロンプト一覧tools/list: ツール一覧
tools/call の結果にはこの仕組みは適用されません。大きな出力を返すツールは、件数指定や続きの取得をツール自身の引数と結果スキーマで設計する必要があります。
また、ページネーションは一覧の転送量を分割するだけです。取得した定義がモデルのコンテキストに載るコストは減りません。ツール数が多い場合は、取得そのものを分割する設計に加えて、ツールの数を絞る設計も別に必要です。ツール設計の考え方はMCPサーバーのツール設計を参照してください。
サーバー実装では何を守るべきか
サーバーは安定したcursorを返し、不正なcursorには-32602(Invalid params)のエラーで応答するのが仕様の推奨です。
サーバー側に求められるのは、仕様上 SHOULD の2点です。
- 安定したcursorを提供する: 同じ cursor を渡せば同じ位置から再開できるようにします。
- 不正なcursorを適切に扱う: 不正な cursor には
-32602(Invalid params)を返します。
実装方針として、cursor は位置情報を Base64 などで符号化した文字列にする方法が仕様の例にも見られます。ただしクライアントには中身を解釈させないため、サーバー側で改ざん検知を付けるかどうかは自社で判断する範囲です。cursor に認可情報や個人情報を埋め込まないでください。
並び順が更新で変わる一覧では、cursor が指す位置が意味を失う場合があります。更新日時順ではなく作成順や ID 順にすると、取りこぼしと重複を減らせます。
クライアント実装で守るべきルールは何か
クライアントはnextCursorが無くなるまで取得を繰り返し、cursorの中身を解釈・加工せず、空文字列も有効な値として扱います。
クライアントの規則は仕様で次のように定められています。
nextCursorが無い応答を結果の終端とみなす(SHOULD)- ページネーションあり・なしの両方のサーバーに対応する(SHOULD)
- cursor を不透明トークンとして扱い、形式を仮定・解析・変更しない(MUST)
2025-11-25版では、cursor をセッションをまたいで保存しないことも MUST とされています。一方、draft 版の記述では、空文字列も有効な cursor であり終端と誤認してはならない、という趣旨の規則に置き換わっています。クライアントを実装するときは、準拠するプロトコルバージョンの本文を確認してください。
取得ループの骨格は次のとおりです。
``ts``
const tools = [];
let cursor: string | undefined;
do {
const page = await client.listTools({ cursor });
tools.push(...page.tools);
cursor = page.nextCursor;
} while (cursor !== undefined);
終了条件を !cursor と書くと、空文字列の cursor を終端と誤認します。undefined との比較にしておく点が重要です。
規模が小さいうちに何をしておくか
件数が少ないうちから、ループ取得と不正cursor時のエラー処理を実装しておくと、規模拡大時に作り直す必要がありません。
中小企業の MCP サーバーは、最初は一覧が1ページに収まります。しかしクライアントの取得ループを後から入れると、サーバー側の変更が連鎖します。次の3点だけは最初から入れておくことを勧めます。
- クライアントは常に
nextCursorのループで取得する - サーバーは不正な cursor に
-32602を返す - 一覧の並び順は更新で変わりにくいキーにする
大規模な組織でのカタログ管理は、エンタープライズ向けのMCPレジストリ設計が詳しいです。MCPサーバーの導入から運用定着までは、Kuuの運用管理サービスで伴走しています。
なお、MCP仕様の変更は速く、機能によっては非推奨とされるものがあります。たとえばロギング機能は 2026-07-28 版で非推奨となりました。利用中の機能の状態はMCPの機能ライフサイクルと非推奨の棚卸しで確認できます。
参考
まとめ
MCPの一覧取得は、不透明な cursor と nextCursor で続きを渡す方式です。クライアントは cursor を解析せず、nextCursor が無くなるまで取得を繰り返します。サーバーは安定した cursor を返し、不正な cursor には -32602 で応答します。件数が少ないうちにこの2点を実装しておけば、規模が増えても作り直しが要りません。MCPサーバーの設計や運用でお困りの際は、Kuuの運用管理サービスにご相談ください。
