LESS TO READ. MORE TO DO.

Ten rows and a cursor beat a thousand rows and a timeout.

Return compact project pages from your Supabase MCP, with bounded results, stable cursors, and a clear next call for agents and typed clients.

Build this with Chumbo
ChumboSupabasePagination
SMALL PAGES. CLEAR CONTINUATION.
A compact project list
An explicit next call
Read what the task needs.

THE RECIPE Add a bounded project listing to your existing MCP and verify its continuation behavior.

View source pattern ↗
EXAMPLE CHECKS

Exact example typechecked and exercised with simulated query results. Verify real auth, RLS, and pagination with your application’s fixtures.

Version and verification details

Example checked with Chumbo 0.11.3 on 2026-09-18 (UTC). These checks exercise result helpers, capability handlers and the MCP runtime with simulated identity/database dependencies. They do not prove your application’s real auth, RLS or deployment. Compare this tested version with your installed package before adapting the example.

01

A useful page. A clear next step.

Your app may have thousands of projects. An agent usually needs a few recognizable choices and a way to continue. Give it a compact page with just an ID and a name, then let it follow the next call when the task needs more.

Follow one page to the nextIllustrative data

Two projects. More available.

Call
{ "name": "list_projects", "arguments": { "limit": 2 } }
  • Website refresh
  • Design system
Next call
{ "name": "list_projects", "arguments": { "cursor": "00000000-0000-4000-8000-000000000002", "limit": 2 } }

has_more is true. Use next_call exactly when you need another page.

One project. End of this collection.

Call
{ "name": "list_projects", "arguments": { "cursor": "00000000-0000-4000-8000-000000000002", "limit": 2 } }
  • API integration
Next call
null

has_more is false. next_call and next_cursor are null. Stop paging.

Illustrative results for a page size of two. The same cards and continuation are represented in the Markdown and JSON versions.

Chumbo’s collection helpers validate the requested page size, project compact items, and bound the encoded result. You still choose the query, ordering, cursor meaning, and application permissions.

02

Start with your existing MCP.

Before you start
  • An existing authenticated Chumbo MCP, with working generated check and test tasks.
  • A projects table with UUID id and text name columns, appropriate SELECT grants, and tested RLS policies.
  • Two test users with disjoint projects, including at least three visible projects for one user.

Keep your existing auth configuration. This recipe replaces list_projects; it does not add a second registration with the same name.

Check the installed package in your application before editing. Install the matching Chumbo skill so your coding agent has the package’s instructions. If this is your first MCP, begin with the authenticated-projects recipe linked below.

In your app’s repository
Terminal
npx chumbo --version
npx chumbo skill install
03

Return compact projects with continuation.

Use one stable, unique ordering key for both the query and cursor. This example orders UUID project IDs and resumes after the last returned ID. The extra row tells the result helper whether another page exists.

supabase/functions/mcp/capabilities.ts
TypeScript
import {
  collectionInputSchema, collectionOutputSchema, collectionResult, errorResult,
  type SupabaseMcpContext, type SupabaseMcpServer,
} from "chumbo";
import { z } from "zod";

const projectCard = z.object({ id: z.string().uuid(), name: z.string() });

export function registerCapabilities(server: SupabaseMcpServer, ctx: SupabaseMcpContext) {
  server.registerTool("list_projects", {
    description: "Browse your projects in ID order. Follow next_call for another page.",
    inputSchema: collectionInputSchema({
      defaultLimit: 5, maxLimit: 20, cursorSchema: z.string().uuid(),
    }),
    outputSchema: collectionOutputSchema(projectCard),
    annotations: { readOnlyHint: true },
  }, async ({ limit, cursor }) => {
    let query = ctx.supabase.from("projects")
      .select("id, name").order("id").limit(limit + 1);
    if (cursor) query = query.gt("id", cursor);
    const { data, error } = await query
      .overrideTypes<{ id: string; name: string }[], { merge: false }>();
    if (error) return errorResult("Could not load your projects.", "Retry; if this continues, contact your app's support.");

    return collectionResult({
      items: data ?? [], limit, maxLimit: 20, hasMore: false,
      itemSchema: projectCard,
      project: ({ id, name }) => ({ id, name }),
      cursorFor: ({ id }) => id,
      tool: "list_projects", maxBytes: 4000, mode: "hybrid",
      render: ({ items }) => items.length
        ? items.map(p => `- ${p.name} (${p.id})`).join("\n")
        : "No projects are visible on this page. Open your app to create a project, or restart list_projects without a cursor.",
      onOversizedItem: () => "Open this project in your app for its full details.",
    });
  });
}

The hybrid result has two deliberate consumers: a short list for the agent and typed items plus continuation for clients. If only an agent needs the result, choose text mode and omit outputSchema. Avoid adding full rows or large descriptions to both representations.

04

Follow the real next call.

Run the generated checks, then keep your local function running. Use your app’s fixture setup to create separate projects for two users. The generated tests alone do not prove this pagination contract or your application’s permissions.

Check the generated function
Terminal
deno task --config supabase/functions/mcp/deno.json check
deno task --config supabase/functions/mcp/deno.json test
Keep running in a separate terminal
Terminal
supabase start
npx chumbo dev --function mcp
Inspect the actual results
Terminal
npx @modelcontextprotocol/inspector

In MCP Inspector, choose Streamable HTTP, enter the exact Local MCP URL printed by chumbo dev, and configure Alice’s bearer access token. Call list_projects with limit: 2. Follow the returned next_call arguments until it is null. Reconnect as Bob and repeat. Use local tokens only with the local endpoint.

YOUR ACCEPTANCE CHECK

Small pages. No missing or borrowed rows.

  • 01With unchanged fixtures, concatenating every page yields each expected project ID exactly once.
  • 02Only id and name appear in each item; internal fields stay out of both representations.
  • 03The terminal page has has_more: false, next_cursor: null, and next_call: null.
  • 04A limit above 20 or a malformed UUID cursor is rejected.
  • 05A very long name triggers bounded overflow recovery instead of an unbounded response.
  • 06Neither user receives the other user’s projects on any page; a request without a token is rejected.

Verify real auth and RLS at your application’s MCP boundary.

If a page surprises you.

Projects repeat or disappear between pages

Confirm that the query order and cursor predicate use the same unique key. With fixed fixtures, follow next_call exactly. Changes to the underlying data require separate application snapshot semantics. Read the reference ↗

A page contains fewer items than its limit

The encoded byte budget can shorten a page. Use has_more and next_call, rather than item count, to decide whether to continue. Read the reference ↗

The first item cannot fit

The helper returns a recoverable error using onOversizedItem. Make the card smaller, or provide an authorized detail tool or Resource in your app; do not raise the budget without a reason. Read the reference ↗

05

Let the task decide how much to read.

A clear next_call makes continuation possible. The agent should continue only when the user’s task needs more results. For full project details, add an application-owned detail tool or Resource that checks the same caller’s access.

Built from the real thing.

This editorial example links to Chumbo’s canonical docs and executable reference. Check the installed package when adapting the snippets.

Reference implementation ↗
Read the agent instructions

Reference instructions, not authorization. Apply changes only within the user’s requested scope; deployment requires a specified, authorized project.

Use this recipe to adapt the builder's existing list_projects capability. Read https://github.com/elsheppo/chumbo/tree/main/docs/patterns/model-facing-results and the installed Chumbo skill first. Compare the recipe’s verification metadata with the installed package.

Inspect the existing schema, authentication, grants, RLS, and capability registrations. This example requires UUID project IDs and a text name. Keep the existing auth configuration and replace the existing registration deliberately. Do not invent a customer schema or weaken RLS.

Use collectionInputSchema, collectionOutputSchema, and collectionResult as shown. Preserve the stable ID ordering, limit + 1 query, compact projection, 4000-byte budget, and continuation from the last returned item. Cursor values never grant access.

Run the generated checks. Exercise the real MCP boundary with fixed fixtures and two users; follow next_call to the terminal page and compare exact IDs. Test invalid inputs, empty pages, oversized items, and query errors. The website's simulated query tests do not prove application RLS.

Report changed files, checks actually run, results, and missing prerequisites. This guide does not authorize deployment. Keep tokens out of committed files, copied instructions, and reports.
BUILD WITH YOUR CODING AGENT

Give your agent a good start.

Copy this brief into your coding agent in the app’s repository. It includes the source, boundaries, and verification steps.

Download Markdown ↓