GitHub 的 MCP 服务器一开始就分页,上下文窗口照样被撑爆

分页限制的是返回多少条记录,上下文窗口却是一笔 token 预算。GitHub 的服务器每页 30 条照样溢出,直到精简响应类型把项目条目砍掉 93%;Datadog 出于同样原因改成按 token 预算分页。而 MCP 只给工具目录分页、不给工具调用分页,于是每个宿主处理溢出的方式都不一样。

作者 智能体 AI 维基 24 分钟读完

GitHub 官方的 MCP 服务器从一开始就做了分页——perPage 最多 100,较新的工具还带 after 游标——可它的 issue 列表里照样堆满了单次调用就撑爆上下文窗口的报告:30 个提交超过 64k token,30 个拉取请求 508KB,50 个项目条目约 100K token。分页限制的是返回多少条记录。上下文窗口却是一笔 token 预算,而一条记录多重都有可能。按 token 来数你的分页,先裁字段再分页,并且要假定:你的用户实际在用的宿主,处理溢出的方式和你测试时用的那个不一样。

一览

github/github-mcp-server 上的溢出报告,截至 2026 年 10 月。大小均为各报告者自己测得,唯一的例外是 PR #2563,那是维护者测的。

提交日期工具报告的大小后续处理
2025-04-06(#142)list_commits30 个提交,>64k token;每个提交约 5–6KB报告者要求支持字段过滤
2025-05-22(#425)get_pull_request_comments26,688 token,而 Claude Code 的上限是 25,000随后出现了分页需求(#567、#625)
2025-06-29(#608)get_job_logs完整日志「很容易超出 LLM 的上下文上限」新增 tail_lines 参数
2026-02-02(#1934)list_pull_requests30 个 PR,508KB;每个 PR 约 17KB,另一次查询中达 36KB仍未关闭;PR #2675 让 search_pull_requests 默认返回精简结果
2026-03-02(#2122)pull_request_read超过约 50–100KB 的载荷会让 Cursor 和 VS Code 的 CPU 跑满 100%PR #2489 补上了缺失的 after 游标
2026-04-24(#2383)projects50 个条目,约 400KB,约 100K tokenPR #2563 引入精简类型:每个条目 −93%
2026-09-07(#3236)pull_request_read get_files总是返回完整补丁仍未关闭;#3286 要求支持字段过滤
Tokens for one page of 50 GitHub project items Two horizontal bars. With full objects, a page of 50 project items comes to about 128,750 tokens, far past Claude Code's 25,000-token default cap and its 10,000-token warning. With the minimal types added in PR #2563 the same page comes to about 9,010 tokens, under the warning line. The maintainers measured 5 live items, 12,875 against 901 tokens, and scaled the result to 50. Tokens for one page of 50 GitHub project items Claude Code warns · 10k default cap · 25k Full objects ~128,750 tokens Minimal types ~9,010 tokens 0 50k 100k 150k Measured on 5 live items (12,875 vs 901 tokens) and scaled to 50 by the maintainers · ~2,575 vs ~180 tokens per item
同样一页 50 条记录,做投影之前和之后。页大小没变,变的是每条记录携带的内容。

一台服务器留下的一串记录

大多数撑爆上下文窗口的 MCP 服务器都没留下任何记录。GitHub 的留下了十八个月的记录,因为它的用户会提 issue,维护者也会公开回复。按时间顺序读下来,这些 issue 每一次讲的都是同一个故事。

第一份是 2025 年 4 月的 #142,一次针对 zalando/skipper 的 list_commits 调用。三十个提交在下一次模型调用时产生了超过 64k token,每个提交约 5–6KB。报告者是 OpenAI 的 tier-1 用户,收到了一个 429:这次请求需要 68,490 token,而限额是每分钟 30,000 token。这是整个故事里唯一一个硬性的成本数字。这次调用完全在页大小范围之内,却还是发不出去。

这个模式一直延续下去。2025 年 5 月,get_pull_request_comments 给一位上限为 25,000 的 Claude Code 用户返回了 26,688 token(#425)。6 月,一份报告指出完整的作业日志「很容易超出 LLM 的上下文上限」,由此催生了 tail_lines 参数(#608)。2026 年 2 月,list_pull_requests 为 30 个 PR 返回了 508KB——每个约 17KB,另一次查询中每个 36KB——报告者希望加上 search_repositories 早已具备的 minimal_output 选项(#1934,仍未关闭)。4 月,50 个项目条目合计约 400KB,大约 100K token,报告者称这「大约是一个典型 LLM 上下文窗口的一半」;两页 50 条就能把一个窗口填满(#2383)。

问题出在记录本身,而不是条数

GitHub 的 REST 对象是为那些会忽略无用字段的程序设计的:一个用户对象就带着十五个以上的 URL 模板,外加模型永远用不上的 reactions 和元数据。原样透传的话,每条记录五到十七 KB,乘以 perPage 上限 100,就是一页任何模型都装不下的数据。这台服务器的页大小对 HTTP 客户端来说很合理,对上下文窗口来说却大得离谱。

每一次真正奏效的修复都是投影。2026 年 5 月 28 日合并的 PR #2563 为项目条目加上了精简类型。维护者对五个线上条目做了测量:修改前约 12,875 token,修改后 901——削减 93.0%,每个条目从约 2,575 token 降到 180。按一页 50 条换算,就是从 128,750 token 降到 9,010。页大小没变,每条记录都变小了。同样的修复如今正在一个端点一个端点地铺开:issue 和拉取请求的列表与搜索工具新增了 fields 参数,其 README 写道「省略 body 就去掉了每条结果中最大的那部分数据」;PR #2675 让 search_pull_requests 默认返回紧凑结果,需要原始对象时传 minimal_output: false;精简类型则集中放在 pkg/github/minimal_types.go 里。截至 2026 年 10 月,下一个排队的是 pull_request_read,它的 get_files 仍然返回完整补丁(#3236)。

够不着的分页不算分页

2026 年 3 月的 issue #2122 展示了按记录分页失效的另一种方式。pull_request_read 的 get_review_comments 方法宣称支持游标分页,但工具的 schema 里根本没有 after 参数,而且有三个方法会悄无声息地忽略 page 和 perPage。模型没法请求第二页,因为 schema 里没有任何东西允许它这么做。超过约 50–100KB 的结果会让 Cursor 和 VS Code 的 CPU 飙到 100%,只能强制重启。PR #2489 补上了 after,这个 issue 在 5 月关闭。游标只有在模型能从 schema 里看到它时才管用,也只有在每一页都装得下时才有帮助。

为什么记录条数框不住窗口

A page bounded by record count against a page bounded by tokens Two rows of the same five records, drawn with widths proportional to their size; the third record is very large. In the top row the server returns five records because the page size is five, and the page runs past the token budget line. In the bottom row the server adds records until the next one would cross the budget, stops after four, and returns a cursor so the fifth starts the next page. token budget Page by record count perPage = 5 records 1–5, whatever they weigh over budget one 1 MB log Page by token budget stop before the next record crosses the same 1 MB log nextCursor records 1–4; record 5 opens the next page truncated: true
以记录条数定页大小,一页的 token 成本就取决于那些记录碰巧有多重。用预算定页,成本固定,条数随之变化。

Datadog 从截然相反的一类数据出发,得出了同样的结论:「一条 Datadog 日志消息可能只有 100 个字符,也可能有 1 MB」,没有哪个条数能框住这种情况。于是他们改变了一页的含义——「服务器在达到一定 token 数后截断响应,并返回一个用于获取更多内容的游标」——剩下的交给游标。

一个感知大小的分页写成代码是什么样子,MCP 中的大结果与分页的 Step 4 有完整的说明,并以 MotherDuck 和 Grafana 的服务器为实例。只按条数分页的服务器,把剩下的事全丢给了宿主,而协议并没有给宿主任何可依据的东西。

协议分页的是目录,不是调用

MCP 有分页,但只针对 tools/list 这类列表方法。tools/call 没有游标、没有大小上限、也没有分块,所以每一台对工具结果做分页的服务器,都得像 GitHub 那样自己发明 page、perPage、after 或 cursor 参数。想改变这一点的提案提过、也关过,路线图上可能有帮助的条目都没有日期;规范的规则和这段历史见MCP 中的大结果与分页。

两种膨胀,只解决了一种

过去一年那些广为人知的上下文节省,大多落在这条线的另一边。工具搜索、代码模式的目录、同步到文件夹里的工具描述,缩小的都是模型在调用任何东西之前要读的工具定义;没有一个能让单次 list_pull_requests 的响应变小。协议分页的是目录,业界大部分优化工作针对的也是目录。具体数字,以及为什么它们不该为结果侧的修复买单,见当 MCP 结果撑爆上下文。

于是由宿主说了算,而宿主们各执一词

What six hosts do with the same oversized tool result One oversized tools/call result fans out to six hosts. Claude Code saves results over its 25,000-token default to a file and puts the path in the conversation. claude.ai and Claude Desktop cap tool results at about 150,000 characters and do not document what happens past it. Codex CLI applies a token budget that can be set per MCP tool. Gemini CLI truncates at 40,000 characters by default. Cursor writes long output to a file the agent can read. VS Code truncates and points at a URI the agent cannot read. tools/call one oversized result, no size field Claude Code over 25,000 tokens (or 50,000 characters): saved to a file, path in the message claude.ai and Claude Desktop ~150,000-character cap; what happens past it is not documented Codex CLI token budget per output, settable per MCP tool; default not documented Gemini CLI truncated at 40,000 characters by default Cursor written to a file the agent can read VS Code Copilot truncated; the read-the-rest URI is refused as outside the workspace spills to a file caps, truncates or leaves it undocumented
一台服务器,一份响应,六种不同的结局。结果里没有任何信息告诉宿主,服务器希望它选哪一种。

协议里什么都没规定,每个宿主就各自设定上限,以及越过上限之后的行为,而且连单位都不统一:有的数 token,有的数字符。服务器作者为某一个宿主调好的页,换到另一个宿主上可能超线好几倍。各宿主当前的上限,以及运维方能调的旋钮,统一列在当 MCP 结果撑爆上下文的 Step 2。

落盘不是分页

落盘看起来像是宿主把问题解决了:结果保住了,窗口省下了。Datadog 的文章提到 Cursor 和 Claude Code 现在都这么做,同时也指出这「还没有写进 MCP 规范」。只有当模型能读取文件的一部分、并根据读到的内容采取行动时,这个文件才有用;针对 Notion、Atlassian 和 VS Code 集成的公开报告,展示了这几步各自怎么断掉,它们作为宿主侧的测试用例收录在运维指南的 Step 3。每个宿主都按自己的节奏修自己的落盘逻辑,所以服务器不能指望其中任何一个。

服务端该怎么做

服务器是唯一知道一条记录里装了什么、哪些字段重要的地方。GitHub 这十八个月可以归结为四条做法;包括参数、信封和尺寸在内的完整契约,见MCP 中的大结果与分页。

按 token 来数一页

设一个预算,而不只是一个条数:不断加入记录,直到下一条会越过预算,然后返回一个游标,条数上限则留作兜底。这正是 Datadog 做出的改变,也是 GitHub 那些按记录数计算的页从来没做的事。

先投影,再分页

默认只返回智能体会据以行动的字段,完整对象则让它主动去要,同时留下后续调用需要的标识符。GitHub 的 −93% 全部来自这一点,页大小一点没动。

把截断标记和补救办法放进结果

模型永远不该去猜自己是否拿到了全部。多取一条来判断,在一个字段里明说,再加一句下一步该怎么做;Anthropic 的工具编写指南要求截断提示文字引导智能体去做「许多小而有针对性的搜索」。一个完整的信封示例——游标放在参数里,标记放在结果里——见深入解析的 Step 4。

为最小的宿主而设计

按你的用户在用的最严格的宿主来定默认页大小,而不是按你桌上那一个;某个宿主按工具放宽上限的设置,只能当逃生口,不能当解决方案。眼下定出这条天花板的两个数字,见深入解析的 Step 3。

常见问题

MCP 支持对工具结果分页吗?

不支持。MCP 分页只覆盖 tools/list 这类列表方法,所以要对工具结果分页的服务器只能自己定义游标参数;具体规则见MCP 中的大结果与分页。

GitHub 的 MCP 服务器明明做了分页,为什么还会撑爆上下文窗口?

它的页是按记录数计算的,而它的记录很大——在已报告的案例中,每个提交 5–6KB,每个拉取请求 17–36KB。解决问题的是精简响应类型,而不是更小的页:PR #2563 把项目条目削减了 93%。

在 Claude Code 中,MCP 工具结果太大会怎样?

Claude Code 会先发出警告,然后把超限的成功结果存到文件、把路径交给模型,而超大的错误结果只保留开头和结尾。当前的各项阈值列在当 MCP 结果撑爆上下文里。

一个 MCP 工具的页应该多大?

用 token 或字符来设定,而不是记录数,按用户在用的最严格的宿主来定大小,并同时返回截断标记和游标。完整的契约见MCP 中的大结果与分页。

MCP 规范会加入工具结果分页吗?

短期内不会:路线图计划重新设计 tools/call 的结果形态,但没有日期,关于流式返回部分结果的提案也已被关闭。详情见MCP 中的大结果与分页。

延伸阅读

本站相关:

资料来源: