Skip to content

server: defaultPageSize is 0 everywhere, so mcpkit never paginates any list method #1356

Open

Description

@panyam

What

server/pagination.go sets defaultPageSize = 0, which paginate reads as "return every item, emit no cursor". Every list method takes it:

Method Call site
tools/list server/dispatch.go:567
resources/list server/dispatch.go:758
resources/templates/list server/dispatch.go:834
prompts/list server/dispatch.go:900

So an mcpkit server with thousands of tools returns all of them in one response, and there is no way for an operator to change that short of forking. ext/skills had the same shape until #1333 follow-up work added WithSkillsListPageSize and WithDirectoryReadPageSize (1e1fdb11); the four base-protocol methods still have no override.

Why it matters here rather than later

This is protocol-surface behavior, which is what this tracker says 1.0 commits to. Changing a server's default page size after the freeze is a breaking wire change. Before it, it is a default.

Not a conformance bug

The spec treats pagination as optional for servers: the request accepts a cursor, and a response carrying no nextCursor is a complete listing. Single-page is conformant, and mcpkit passes every conformance suite today. This is a scale and API-hygiene issue, not a spec violation.

The part that is a defect

The machinery exists and nothing can reach it. paginate is generic and correct, the wire types carry nextCursor, the handlers accept a cursor they can never issue. ext/skills had five green unit tests calling paginateDirectoryRead(items, cursor, 2) directly with a page size no caller could produce, and one wire test asserting NextCursor == "", which pinned the gap as the contract. Coverage looked healthy over a path production never took.

That combination surfaced concretely on 2026-09-04: the SEP-2640 conformance scenarios read a single page of resources/directory/read and every child-inspection check below saw only that page. Invisible against mcpkit, because mcpkit never emits a cursor. Found by Sam Bloomberg running the suite against the Go SDK, which does paginate.

Recommendation

Adopt a non-zero server-wide default, set high, with per-primitive overrides.

The tempting framing is 0 versus 1, and that is a false choice. The real risk of paginating by default is not round trips, it is silent truncation: a client that ignores nextCursor does not error, it quietly treats the first page as the whole catalog. That is a correctness failure that looks like normal operation, and it is worse than a large response, which at least works.

The threshold decides the blast radius:

Default Small catalogs Large catalogs Truncation risk
0 (today) fine broken none
1-50 behavior changes for everyone fixed wide
250-500 unchanged, never paginates fixed only servers already broken

A high default gets most of the safety with almost none of the risk. Servers under the threshold behave identically to today so no client breaks, and servers over it are already shipping a multi-megabyte response, so for them pagination is strictly an improvement.

Two secondary benefits: it retires the dead-path problem permanently, since any large deployment exercises paging continuously rather than only when someone opts in. And it surfaces cursor-ignoring clients against our own servers rather than in someone else's production, which is exactly how the conformance bug above came to light in reverse.

Decision needed

  1. Non-zero default, or keep 0 and only add the overrides?
  2. If non-zero, what value? Suggest 250, erring high.
  3. One server-wide default with per-primitive overrides, rather than four independent knobs, so the decision is made once.

Work

  • Decide the above
  • WithDefaultPageSize server option, plus per-primitive overrides where they earn their keep
  • Wire through the four dispatch.go call sites
  • End-to-end tests that page over the wire, not unit tests of paginate with hand-passed sizes
  • Audit existing tests for assertions like NextCursor == "" that would pin the old behavior
  • VERSIONING.md note if the default changes, since it alters wire behavior

Refs #1240.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Low priority — periodic reviewmission_wire_2026_07_28Mission: 2026-07-28 wire correctness on both wiresneeds confirmationUnclear if still relevant

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions