npm package: @mcp-utils/pagination
Description: Cursor-based pagination helpers for MCP tool responses — powered by vurb.
Latest version: 1.0.0

# @mcp-utils/pagination

<div align="center">

[![npm](https://img.shields.io/npm/v/@mcp-utils/pagination?color=ff4d00&label=npm)](https://npmjs.com/package/@mcp-utils/pagination)
[![License](https://img.shields.io/npm/l/@mcp-utils/pagination)](LICENSE)

</div>

---

Your `users.list` tool returns 10,000 records. The LLM tries to process all of them at once. The context window overflows. The agent loses track. The user gets a broken response.

Dumping your entire database into a context window isn't a feature — **it's a bug waiting to happen**.

`@mcp-utils/pagination` gives you cursor-based pagination with a single function call:

```ts
import { paginate } from '@mcp-utils/pagination';

handler: async (ctx, args) => {
    const all = await db.users.findAll();
    return success(paginate(all, { cursor: args['cursor'], limit: 25 }));
    // → { items, nextCursor, total, hasMore, limit, offset }
}
```

The agent gets `nextCursor` in the response and knows exactly how to ask for the next page. No guessing, no overflows, no broken experiences.

## Install

```bash
npm install @mcp-utils/pagination
```

## Usage

### In-memory array pagination

For collections already in memory: config lists, processed results, filtered datasets.

```ts
import { paginate } from '@mcp-utils/pagination';

const listUsers = async (ctx, args) => {
    const all = await db.users.findAll();

    return success(paginate(all, {
        cursor:   args['cursor']   as string | undefined,
        limit:    args['limit']    as number | undefined ?? 20,
        maxLimit: 100, // hard cap — no client can request more than 100
    }));
};
```

### Database-native pagination (SQL offset/limit)

For large tables: let the database do the heavy lifting.

```ts
import { buildPage, decodeCursor } from '@mcp-utils/pagination';

const listOrders = async (ctx, args) => {
    const offset = decodeCursor(args['cursor'] as string | undefined);
    const limit  = Math.min(args['limit'] as number ?? 25, 100);

    const [rows, total] = await Promise.all([
        db.orders.findMany({ skip: offset, take: limit }),
        db.orders.count(),
    ]);

    return success(buildPage(rows, total, { offset, limit }));
};
```

### What agents receive

Every response is consistent and predictable:

```ts
{
    items:       User[],      // current page — never more than `limit`
    nextCursor?: string,      // pass as `cursor` to get the next page (absent on last page)
    total:       number,      // full dataset size
    hasMore:     boolean,
    limit:       number,
    offset:      number,
}
```

## API

### `paginate(items, options)`

| Option | Type | Default | Description |
|---|---|---|---|
| `cursor` | `string \| undefined` | — | Cursor from previous `nextCursor` |
| `limit` | `number` | `20` | Items per page |
| `maxLimit` | `number` | `100` | Hard cap on page size |

### `buildPage(slice, total, { offset, limit })`

Constructs a `PaginationResult` from a pre-fetched database slice and total count.

### `encodeCursor(offset)` / `decodeCursor(cursor?)`

Low-level cursor helpers. `decodeCursor` returns `0` for `undefined` or invalid input — always safe to use without null checks.

---

Part of **[@mcp-utils](https://npmjs.com/org/mcp-utils)** — production-grade utilities for MCP server development.

## License

Apache-2.0