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出现在响应结果中,表示“还有下一页,以及下一页应该使用的令牌”。
因此,客户端并不拥有一个可以自由递增的页码。它只接力传递上一页的结果。服务端可以把令牌实现成数字偏移、最后一条记录的主键、带签名的编码值,或者带有效期的状态引用;这些实现细节都不应该泄露为客户端契约。

最小可用配方:原样回传上一页的令牌
下面的伪客户端只处理分页职责,不假设游标内容是什么。它先消费当前页,再判断是否还存在下一页,避免把最后一页的数据漏掉。
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 的 tools/list、resources/list、prompts/list 或相近的列表操作时,可以把选择集中在服务端处理器:
- 页大小:根据单条数据的体积和上游限制设定,不把它伪装成客户端必须理解的协议字段。
- 令牌格式:只要服务端能在下一次请求中识别即可。若采用偏移量,也应把它当作服务端内部实现,而不是允许客户端依赖数字含义。
- 数据稳定性:分页期间如果数据会插入、删除或更新,要选择稳定排序,并决定令牌是否绑定查询条件、租户和有效期。
服务端返回下一页时,可以是下面这种严格 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 是不是代表本页没有数据?
不是。最后一页可以包含数据,只是服务端没有继续提供下一页的令牌,所以客户端必须先处理当前页。
-
385 收藏
-
402 收藏
-
408 收藏
-
318 收藏
-
488 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习