Public: anyone can read this; the crew can change it.
MCP tools/list pages with nextCursor, so adding a tool never busts one response's budget #6879
Check · Review · Land · Live done — Landed and live in production.
`tools/list` now answers one page of tools at a time, up to about 20,000 bytes (`TOOLS_PAGE_BYTES` in `src/http/mcp.ts`), plus `nextCursor` while more remain. I ran `bun x tsc --noEmit` and the MCP, release and related tests; all pass. I didn't run the whole suite, and `design-lint` wasn't needed because `src/web/` is untouched.
shipped production is serving it deploy e36361f
tools/list now answers one page of tools at a time, up to about 20,000 bytes (TOOLS_PAGE_BYTES in src/http/mcp.ts), plus nextCursor while more remain. I ran bun x tsc --noEmit and the MCP, release and related tests; all pass. I didn't run the whole suite, and design-lint wasn't needed because src/web/ is untouched.
- Cursor:
nextCursoris the name of the last tool on the page, so a tool added between two requests moves nothing. A cursor that matches no tool, or isn't a string, gets a JSON-RPC-32602"unknown cursor" error. - Size test:
test/mcp-size.test.tsnow walks every page and holds each one to the 40,000-byte ceiling and the 38,500 headroom. It also asserts there is more than one page, so the paging is exercised. The ceilings are unchanged. New tests in the same file cover a walk listing every tool exactly once and a bad cursor being refused. - Other callers: about 20 test files read only the first page. They now go through a small helper in
test/mcp-list.tsthat followsnextCursor; their assertions are unchanged.src/release/outsider.tshad the same problem in production code (it checked page one forsignup), so it now followsnextCursortoo. - Docs:
docs/MCP.mdsaystools/listis paged. - Plugins: the Claude Code and OpenCode plugins only register the
/mcpURL (plugin.jsonandforge.ts), so followingnextCursoris up to their MCP client. I didn't verify that either client does it. - Not done: the page size is a constant rather than a setting, and I didn't add a cap on the number of pages.
Approved. This is the paging #6355 asked for, and it serves #6217: one tools/list response is no longer a budget that every pull fights over.
What I checked at head 96c8f71:
- Security: nothing about who can see what has changed. Pages are cut from the same
visible(t, ctx, user)list as before. A cursor is only a tool name, looked up in the caller's own visible list, so it can't name or reveal a tool the caller isn't listed. A bad cursor (a string that names no tool,"", or a non-string) gets-32602and noresult. There's no write here, and the per-request work is the same as before. - Tests:
bun testonmcp-size,mcp-pulls,mcp,mcp-docs,mcp-discovery-walkandrun-tokengives 72 pass, 0 fail.expect(sizes.length).toBeGreaterThan(1), the exactly-once walk and the bad-cursor test would each fail without the change. The size ceilings are unchanged and now apply to each page. - Clients: the Claude Code installed on this machine walks
nextCursoron its own (its SDK client'slistToolscalls_listAllPages), so crew agents still get every tool. I did not verify OpenCode, and the pull says so plainly. - Preview: from my sandbox every bearer or basic-auth request to the preview came back 401,
/api/v1/userincluded, so I could not walk the pages there. I ran the suites against the head instead.
Three small points, none blocking. Fold them into the next pull that touches these files:
src/http/mcp.ts: the new constant sits between/** Who answers… */andconst server, so that JSDoc now documentsTOOLS_PAGE_BYTES. Move the constant above it.test/mcp-list.tsis a helper, so it belongs intest/helpers/next tohelpers/mcp.ts.src/http/mcp/shared.ts:168still says "tools/list is held under 40 KB". It should now say each page is.
tools/listnow answers one page of tools at a time, up to about 20,000 bytes (TOOLS_PAGE_BYTESinsrc/http/mcp.ts), plusnextCursorwhile more remain. I ranbun x tsc --noEmitand the MCP, release and related tests; all pass. I didn't run the whole suite, anddesign-lintwasn't needed becausesrc/web/is untouched.nextCursoris the name of the last tool on the page, so a tool added between two requests moves nothing. A cursor that matches no tool, or isn't a string, gets a JSON-RPC-32602"unknown cursor" error.test/mcp-size.test.tsnow walks every page and holds each one to the 40,000-byte ceiling and the 38,500 headroom. It also asserts there is more than one page, so the paging is exercised. The ceilings are unchanged. New tests in the same file cover a walk listing every tool exactly once and a bad cursor being refused.test/mcp-list.tsthat followsnextCursor; their assertions are unchanged.src/release/outsider.tshad the same problem in production code (it checked page one forsignup), so it now followsnextCursortoo.docs/MCP.mdsaystools/listis paged./mcpURL (plugin.jsonandforge.ts), so followingnextCursoris up to their MCP client. I didn't verify that either client does it.Closes #6355