feat(mcp): cursor pagination shape for list tools (§6a #3) + AutoPager.page()#258
Merged
Conversation
The missing primitive for caller-driven cursor pagination: pass the prior page's
next_cursor (omit for the first page) → get { items, next_cursor }. Normalizes a
null/empty next_cursor to undefined (= last page) and treats an empty-string
cursor as the first page. Existing iteration / toArray / forEach unchanged.
This is what the MCP list tools need to expose the §6a #3 `cursor`+`limit` in,
`next_cursor` out shape (next slice), and is useful to SDK users who want manual
paging. SDK 95 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cursor-paginated list tools now expose ONE shape — `cursor` + `limit` in,
`{ <items>, next_cursor }` out (one page; pass next_cursor back for the next).
Replaces the prior mix where `token` was accepted-but-ignored and `page_size`
was a total cap that `.toArray()` walked internally — which meant an agent could
never page past the cap, and `token` was a lie.
- list_messages, list_conversations, list_events: schemas drop token/page_size →
shared `paginationInput` (cursor+limit, in util.ts); wrapper methods return
Page<T> via the new AutoPager.page(cursor); handlers return { items, next_cursor }
(next_cursor omitted on the last page).
- Unchanged by design: small fixed lists (list_agents/list_domains/list_webhooks)
stay non-paginated (decision 7); list_webhook_deliveries is single-page `limit`
(the API has no cursor); list_pending_messages stays a cross-agent aggregate.
Tests: next_cursor surfaced / omitted-on-last-page; cursor+limit forwarded;
stubs updated to Page shape; over-the-wire cursor round-trip (page1→cursor→page2)
across the real Streamable-HTTP transport. MCP 132 + SDK 95 green.
Docs: §6a banner #3 → done; mcp/README list_messages row (cursor+limit; also
fixed the pre-existing status→read_status drift in that line).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Independent review: PASS. Adversarial review: SAFE (proved distinct pages, next_cursor correctly signals done, and list_pending_messages still aggregates all pages — no one-page regression). Findings applied, each with a test: - [LOW, adversarial] AutoPager.page() used `?? undefined`, which leaks an empty-string next_cursor as a truthy "more pages" — contradicting its docstring and the iterator's `!next` termination. Normalize ""/null/undefined → undefined. Test: page() with next_cursor:"" → undefined. - [should-fix, independent] only list_messages exercised the "more pages" output branch. Added next_cursor-surfacing tests for list_conversations + list_events. - [note] caller-driven loop: strengthened the cursor description — stop when next_cursor is absent. - [FYI, pre-existing, in-theme] corrected the stale ListConversationsInput comment in conversations.go that claimed single-page / next_cursor-always-null; the handler does real keyset continuation (hasMore → EncodeCursor). Comment-only; no spec change (TestSpecGoldenNoDrift green). SDK 96 + MCP 134 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
§6a #3 — one pagination shape for the MCP list tools. The cursor-paginated lists now take
cursor+limitand return{ <items>, next_cursor }(one page; passnext_cursorback for the next), replacing the prior mix wheretokenwas accepted-but-ignored andpage_sizewas a total cap that.toArray()walked internally — so an agent could never page past the cap, andtokenwas a lie.Slices
AutoPager.page(cursor?)— the missing single-page primitive ({items, next_cursor}); normalizes null/empty cursor → undefined; empty-string cursor = first page. Iteration/toArray/forEachunchanged.list_messages,list_conversations,list_events: schemas droptoken/page_sizefor a sharedpaginationInput(cursor+limit); wrapper methods returnPage<T>via.page(cursor); handlers return{ items, next_cursor }(next_cursor omitted on the last page).Unchanged by design
list_agents/list_domains/list_webhooks) stay non-paginated (decision 7).list_webhook_deliveriesstays single-pagelimit(the API has no cursor).list_pending_messagesstays a cross-agent aggregate.Verification
page()cursor walk + empty-cursor) and MCP 132 tests green.list_messagescursor round-trip (page 1 → follownext_cursor→ page 2 = last) across the real Streamable-HTTP/JSON-RPC transport.Docs
mcp/READMElist_messagesrow updated (cursor+limit; also fixed a pre-existingstatus→read_statusdrift in that line).Follow-up
AutoPager.page()parity (mirrors the feat(mcp): surface structured error code in tool errors (§6a #4) + SDK envelope-parse fix #255 Python note).🤖 Generated with Claude Code