跳到主内容

MCP 分页怎么做:大结果集的游标设计与返回体裁剪

100%
MCP 分页怎么做:大结果集的游标设计与返回体裁剪

MCP 的分页用的是不透明游标而不是页码:服务端在响应里返回 nextCursor,客户端原样回传,页大小完全由服务端决定,客户端绝不能假设一个固定值。落地结论是:服务端把游标做成”自包含、可校验、可失效”的字符串,在四个列表接口上统一实现;客户端把游标当黑盒,只以 nextCursor 缺失作为结束信号,并且必须同时兼容不分页的服务端。

协议规定:只有四个接口支持分页

按 MCP 规范(2026-07-28 修订版),支持分页的操作只有这四个:

  • resources/list —— 列出可用资源
  • resources/templates/list —— 列出资源模板
  • prompts/list —— 列出可用提示模板
  • tools/list —— 列出可用工具
tools/call不在分页覆盖范围内。如果你的工具一次返回一万行数据,协议不会替你分页,必须在工具自己的入参里设计 limit / offset(或游标)参数,由你自己在工具内部裁剪。这是最常见的误解。

请求与响应的最小形态

服务端响应带 nextCursor 即表示还有下一页:

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

客户端把游标原样放进下一次请求:

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

三个必须记住的细节:

  1. 没有 nextCursor 就是最后一页,不要再发一次请求确认。
  2. 空字符串是合法游标,"" 不能被当成”结束”。只能用”字段是否存在”判断,不能用”值是否为真”。
  3. 不要解析、不要修改、不要跨会话持久化游标。它的格式是服务端的实现细节,随时可能变。

服务端:三种游标设计怎么选

方案做法优点缺点适用
偏移游标base64 编码 {"offset":30}实现最简单,可跳页数据变动时会重复或漏项静态列表,如工具清单
键集游标编码最后一项的排序键 {"last_id":"…"}数据插入不影响,稳定性好需要稳定唯一排序键数据库、文件列表
签名游标上述任一 + HMAC 签名 + 过期时间防篡改、可主动失效多一层密钥管理多租户、带权限过滤的列表

推荐起点是键集游标加签名。因为列表结果通常与权限相关,服务端需要能确认”这个游标是我签发的、没被改过、还没过期”。

import base64, hmac, hashlib, json, time, os

SECRET = os.environ["CURSOR_SECRET"].encode()

def make_cursor(last_id: str, ttl: int = 600) -> str:
    payload = {"k": last_id, "e": int(time.time()) + ttl}
    raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode()
    sig = hmac.new(SECRET, raw.encode(), hashlib.sha256).hexdigest()[:16]
    return f"{raw}.{sig}"

def read_cursor(cursor: str) -> str:
    raw, _, sig = cursor.partition(".")
    if not hmac.compare_digest(
        hmac.new(SECRET, raw.encode(), hashlib.sha256).hexdigest()[:16], sig
    ):
        raise InvalidParams("bad cursor")          # → -32602
    payload = json.loads(base64.urlsafe_b64decode(raw))
    if payload["e"] < time.time():
        raise InvalidParams("cursor expired")      # → -32602
    return payload["k"]
游标过期或不合法时,按规范应返回 JSON-RPC 错误码 -32602(Invalid params)。不要静默返回空页——那会让客户端误以为已经取完,造成数据缺失。

客户端:一个正确的翻页循环长什么样

async function listAllTools(client) {
  const tools = [];
  let cursor;                    // undefined = 首页
  const seen = new Set();
  while (true) {
    const page = await client.request(
      { method: "tools/list", params: cursor ? { cursor } : {} },
      ListToolsResultSchema
    );
    tools.push(...page.tools);
    if (!page.nextCursor) break;             // 关键:只在缺失时结束
    if (seen.has(page.nextCursor)) break;    // 防御重复游标导致的死循环
    seen.add(page.nextCursor);
    cursor = page.nextCursor;
  }
  return tools;
}

三个易错点:用 if (cursor) 判断结束(错,空串是合法游标)、假设每页 50 条(错,页大小由服务端定)、不设重复游标保护(服务端出 bug 时会无限循环)。

顺带可以带上的缓存提示

列表结果可以携带缓存提示:ttlMs 表示结果保鲜时长(0 = 不要缓存),cacheScope 取 public 或 private。对工具清单这类很少变动的列表,给一个 5–10 分钟的 ttlMs 能显著减少 tools/list 的重复请求;而含用户权限过滤的资源列表必须标 private,避免被共享缓存误发给其他用户。

分页、进度通知、工具内裁剪的分工

这三件事经常被混为一谈,实际职责完全不同:

三者职责对照(点击展开)
  • 分页(nextCursor):解决”列表太长,一次返回不完”。只作用于四个 list 接口。
  • 进度与取消(progressToken / notifications/cancelled):解决”单次调用耗时长,需要进度反馈和中断”。属于调用过程,与列表无关,可参考MCP 长任务的进度与取消。
  • 工具内裁剪(limit / offset 入参):解决”工具返回的结果体太大”。由工具自己实现,协议不管。

如果你的 MCP 服务器是连数据库的,那么分页游标最好直接映射成数据库的键集查询条件,避免把全量结果读进内存再切片。这方面的连接池与只读约束,可以参考MCP 服务器连数据库的做法。另外,2026-07-28 之后协议已不再使用旧的握手流程,服务端能力通过 _meta 声明,细节见MCP 能力协商与版本兼容。


客户端可以自己指定每页返回多少条吗?
不可以。规范明确页大小由服务端决定,客户端不得假设固定页大小。如果你的业务确实需要控制批量大小,应在工具自己的入参里加 limit,而不是去改 tools/list。

nextCursor 是空字符串时还有下一页吗?
有。空字符串是合法游标,规范特别强调它不得被当作结束信号。判断是否结束的唯一依据是 nextCursor 字段是否缺失。

服务端一定要实现分页吗?
不是强制。不返回 nextCursor 即为不分页,一次返回全部结果。对工具数量少、资源固定的服务器完全可以不做,但客户端必须同时兼容两种流程。

游标过期了应该怎么处理?
返回 JSON-RPC 错误 -32602,客户端捕获后应从头重新拉取,而不是重试同一个游标。同时客户端要限制最大页数,防止异常服务端导致的无限翻页。

tools/call 返回的数据太大怎么办?
协议层面没有分页支持,只能在工具入参里自行设计 limit / offset 或游标参数,并在工具描述里写清楚默认上限。返回体过大还可以考虑改用嵌入式资源(resource)让客户端按需再取。

一句话收尾:分页在 MCP 里是一件”服务端决定、客户端跟随”的事。服务端把游标做成可校验可失效的 token,客户端把它当黑盒并以字段缺失为终止条件,这条链路就不会出错。

这篇有帮助吗?
云上的幻象
云上的幻象查看主页

七彩云博客,分享 WordPress 建站实战与 AI 工具测评,覆盖服务器运维、站长工具、软件资源与电商运营干货,专注原创实用的主题插件、网站加速与安全优化教程。

968文章4评论

相关文章

评论 (0)

欢迎你,新朋友,感谢参与互动!文明发言,理性交流 · 首次评论将在审核后展示