登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  业界新闻

MCP 分页游标为何必须由服务端生成

来源:17golang原创

时间:2026-10-13 08:17:36 165浏览 收藏

MCP 分页游标不能由客户端按照“第几页”自行生成。正确做法是:第一次请求不带 cursor,服务端返回当前页和可选的 nextCursor;客户端下一次请求原样带回这个值,直到响应中不再出现 nextCursor。游标的格式、页大小以及它如何映射到数据位置,全部由服务端掌握。

官方地址:https://modelcontextprotocol.io/specification/2026-07-28

MCP 把游标定义成不透明令牌。客户端只负责保存和转发,不能解析、递增、拼接或猜测它;服务端用自己的规则生成并解释下一页位置。

先分清 cursor 和 nextCursor

一次分页请求里有两个方向相反的字段:

  • cursor 出现在请求参数中,表示“从服务端给出的某个位置继续”。首个请求通常没有它。
  • nextCursor 出现在响应结果中,表示“还有下一页,以及下一页应该使用的令牌”。

因此,客户端并不拥有一个可以自由递增的页码。它只接力传递上一页的结果。服务端可以把令牌实现成数字偏移、最后一条记录的主键、带签名的编码值,或者带有效期的状态引用;这些实现细节都不应该泄露为客户端契约。

MCP 客户端、服务端和数据源之间传递不透明分页游标的结构示意图
图1:客户端只转发服务端返回的 nextCursor,服务端负责把令牌解释为数据位置的静态结构图。

最小可用配方:原样回传上一页的令牌

下面的伪客户端只处理分页职责,不假设游标内容是什么。它先消费当前页,再判断是否还存在下一页,避免把最后一页的数据漏掉。

async def list_all(client):
    # None 表示从第一页开始,不能自行写成 page-1 或 0。
    cursor = None
    items = []

    while True:
        # 请求只携带服务端上一次返回的游标。
        page = await client.list_resources(cursor=cursor)
        items.extend(page.resources)

        # 先收集当前页,再用缺省 next_cursor 判断是否结束。
        if page.next_cursor is None:
            break

        # 不解析、不拼接,原样交给下一次请求。
        cursor = page.next_cursor

    return items

这段写法有三个关键点:第一页的 cursor 是空值;每一页都先加入结果;结束条件是服务端不再返回游标,而不是“本页数量小于固定页大小”。即使最后一页刚好装满,也可能需要再请求一次才能知道分页已经结束。

为什么服务端必须生成游标

把游标交给服务端生成,不是为了增加神秘感,而是为了让分页策略与数据源保持一致。

  • 页大小属于服务端约束。工具、资源或提示列表的单项大小不同,服务端需要控制响应体积,协议并没有要求客户端通过 limit 指定服务端必须返回多少条。
  • 排序和位置属于数据源语义。服务端可能按稳定主键、更新时间加主键,或数据库游标排序。客户端若把令牌当偏移量,无法理解这些规则。
  • 部署方式可以变化。令牌可以携带签名、租户边界或短期状态;客户端只转发它,服务端才能在变更内部实现后保持外部契约。
  • 错误处理集中。格式不合法、已过期或超出范围的令牌,应由服务端按自身 API 规则返回错误,而不是让不同客户端各自猜测修复方式。
MCP 分页中客户端只转发而服务端决定页大小编码和结束信号的对比示意图
图2:客户端与服务端的职责边界,以及 nextCursor 缺省代表最后一页的静态说明图。

服务端设计时要固定的三个边界

实现 MCP 的 tools/list、resources/list、prompts/list 或相近的列表操作时,可以把选择集中在服务端处理器:

  1. 页大小:根据单条数据的体积和上游限制设定,不把它伪装成客户端必须理解的协议字段。
  2. 令牌格式:只要服务端能在下一次请求中识别即可。若采用偏移量,也应把它当作服务端内部实现,而不是允许客户端依赖数字含义。
  3. 数据稳定性:分页期间如果数据会插入、删除或更新,要选择稳定排序,并决定令牌是否绑定查询条件、租户和有效期。

服务端返回下一页时,可以是下面这种严格 JSON 结构。JSON 本身不插入注释,字段含义写在代码块后方,避免破坏可解析性。

{
  "resources": [
    {"uri": "books://catalog/book-11", "name": "book-11"}
  ],
  "nextCursor": "opaque-token-from-server"
}

nextCursor 存在时,客户端把它放回下一次请求的 cursor;最后一页则省略该字段或使用 SDK 约定的空值。不要把一个空字符串擅自解释为结束,是否还有下一页应遵循目标 SDK 与服务端实现的明确约定。

三个容易写错的变体

写法问题改法
cursor = "page-" + str(page_no)客户端假定服务端使用页码,换实现就失效直接使用上一页的 nextCursor
先判断游标,再处理当前页最后一页有数据但没有下一页游标,结果会被跳过先追加当前页,再判断结束
把空字符串固定当作结束空字符串可能仍是服务端发出的游标值按 SDK/服务端约定判断缺省或空值语义

落地前的速查清单

  • 首个请求没有人为构造的页码或游标。
  • 客户端没有解码、递增或重写服务端游标的逻辑。
  • 每次循环都先处理本页,再检查 nextCursor。
  • 服务端的排序、页大小、令牌有效期和错误策略能够独立调整。
  • 分页条件不会在下一页请求中被客户端悄悄改变。

如果只记住一句话,就是“客户端传递位置,服务端定义位置”。这让 MCP 客户端保持简单,也让服务端可以在数据库偏移、稳定键集和签名令牌之间演进,而不把内部实现变成跨客户端的隐性协议。

相关问题

MCP 的分页一定要使用数据库游标吗?

不一定。数据库游标、主键锚点、偏移量或编码令牌都可以,关键是由服务端生成并能在后续请求中解释。

客户端能不能保存 nextCursor?

可以保存和原样重放,但不要把它拆开修改。是否允许跨会话、跨用户或长时间重放,应由服务端的令牌策略决定。

没有 nextCursor 是不是代表本页没有数据?

不是。最后一页可以包含数据,只是服务端没有继续提供下一页的令牌,所以客户端必须先处理当前页。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>