Skip to content
Back to skills

Mcp Pagination

ASecurity

Internal guardrail invoked by mcp-builder only — audits MCP tool schemas with list_*, search_*, find_*, get_all_* or SQL-executing operations for missing pagination, limit, offset or cursor parameters and unbounded results. Not for general MCP questions, single-item getters, or mutation-only tools.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 19, 2026
ai-agentspythongoshellbashsqlnoderailstestinggitapi

Works with

  • cursor
  • cli
  • api
  • mcp

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned October 3, 2026

npx -y skills add Tamircohen28/tamirs-superpowers --skill mcp-pagination --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mcp Pagination?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mcp Pagination
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tamircohen28-mcp-pagination/badge)](https://www.skillsdirectory.com/skills/tamircohen28-mcp-pagination)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mcp-pagination
description: 'Internal guardrail invoked by mcp-builder only — audits MCP tool schemas with list_*, search_*, find_*, get_all_* or SQL-executing operations for missing pagination, limit, offset or cursor parameters and unbounded results. Not for general MCP questions, single-item getters, or mutation-only tools.'
when_to_use: 'mcp-builder Phase 1.3 collection endpoints, audit these tool definitions for missing limit parameters, unbounded SQL-executing results, the pagination quick-reference checklist against implemented tools, caller-driven pagination in evals.'
argument-hint: '[path to MCP tool schema or server source file]'
arguments: []
disable-model-invocation: false
user-invocable: false
allowed-tools:
- Read
disallowed-tools: []
model: sonnet
effort: low
context: ''
agent: ''
hooks: {}
paths: []
shell: bash
metadata:
  tamirs:
    visibility: internal
    category: mcp
    role: reviewer
    validation-tier: 1
    updated-date: '2026-08-19'
    capabilities:
      required:
        - skills
      optional:
        - shell
    tags:
      - mcp
      - pagination
      - guardrail
      - portable
  capability: mcp-guardrail
  updated-date: '2026-08-19'
---

# MCP Pagination Guardrails (Internal)

## Why this skill exists

MCP tools that return lists or search results have no built-in safety net against unbounded responses. Calling `list_issues`, `search_code`, or any similar tool without a limit parameter can return hundreds or thousands of items, consuming 10-100x more tokens than needed, slowing responses, and inflating API costs. Naive implementations skip pagination because the tool "works" without it — this guardrail enforces limits before that mistake reaches production.

This skill is invoked by `mcp-builder` via `Skill("mcp-pagination")` when list/search operations appear in an MCP server design. It is not user-facing — users should run `mcp-builder` instead.

## When mcp-builder invokes this skill

| mcp-builder phase | Why |
|-------------------|-----|
| **Phase 1.3** — after cataloging collection endpoints | Set pagination strategy before any tool schemas |
| **Phase 3.0** — before each `list_*` / `search_*` / SQL tool | Apply limits and cursor mapping per tool |
| **Phase 4.1** — after Inspector testing | Run quick-reference checklist against implemented tools |
| **Phase 5** — eval design | Confirm evals use caller-driven pagination, not full scans |

If `mcp-builder` passes planned tool names or API pagination params, use them as input for Steps 1–3 below.

## Workflow

**Step 1 — Identify list/search operations in the MCP server design.**

Any tool whose name matches these patterns needs a limit strategy:
- `list_*` — always paginated
- `search_*` — always paginated
- `get_*` that returns an array at the top level — treat as paginated (name prefix alone does not exempt a tool; return type is what matters)
- SQL-executing tools — require `LIMIT` clause enforcement

**Exempt from guardrails (do not flag these):**
- Single-item getters: `get_issue(id)`, `get_user(id)` — return one object, not an array
- Mutations: `create_*`, `update_*`, `delete_*` — not collection operations
- Streaming/generative tools: LLM completion streams, SSE feeds, WebSocket subscriptions — these push generated data, not paginated lists

For API-specific pagination parameter names (GitHub REST `per_page`, GraphQL `first`/`after`, Slack `cursor`, Notion `page_size`, etc.), load `references/pagination-patterns.md`.

**Step 2 — Verify each operation has a limiting mechanism.**

For each identified operation, confirm one of the following is present:

| Mechanism | When to use | Example parameter |
|-----------|-------------|-------------------|
| `limit` / `max_results` / `per_page` | Primary list endpoints (REST) | `limit=20` |
| `page` + `per_page` | Offset pagination | `page=1, per_page=10` |
| `cursor` / `page_token` | Token-based continuation (REST) | `cursor="<token>"` |
| `first` + `after` | GraphQL Relay cursor pagination | `first=20, after="<endCursor>"` |
| SQL `LIMIT` clause | Any tool that executes raw SQL | `SELECT ... LIMIT 50` |
| Date/time filter | When no explicit limit param exists | `since="2024-06-01T00:00:00Z"` |

**Step 3 — Set safe starting limits.**

Use these defaults unless the tool's documentation specifies lower maximums:

| Operation type | Safe starting limit | Rationale |
|----------------|--------------------|-|
| Code search | 5-10 | Results are large; 10 is usually enough to find the target |
| Issue / PR search | 10-20 | Each item has metadata; 20 fits comfortably in context |
| Channel / message list | 50-100 | Lightweight items; higher limit reduces round-trips |
| SQL queries | 50-100 | Depends on row width — prefer lower for wide schemas |
| GraphQL list | 20 | Use `first=20`; avoid `last` unless reverse pagination needed |
| Generic list | 20 | Safe default when no guidance exists |

**Step 4 — Add alternative filters when no limit param exists.**

If a tool has no pagination parameter, narrow scope with filters before fetching:

```bash
# Date filtering — prefer ISO 8601 with timezone
since="2024-06-01T00:00:00Z"
before="2024-07-01T00:00:00Z"

# State filtering — exclude irrelevant items
state="open"       # or "closed", "merged"
status="active"    # or "inactive", "archived"

# Owner/author filtering
author="username"
assignee="username"

# Path/extension filtering (code search)
path="src/api"
extension="ts"
```

**Step 5 — Enforce incremental fetching.**

The server design must not auto-paginate through all results. The correct pattern:

```
1. Fetch first page with safe limit
2. Return results to caller
3. Only fetch next page if caller explicitly requests more
```

Do NOT pre-fetch all pages and merge results — this defeats the guardrail entirely.

## Hard Rules

1. **Never generate or approve a list/search MCP tool that has no limiting strategy** — every such tool must expose at least one of: `limit`, `max_results`, `per_page`, `cursor`, `first`, or a date filter parameter.
2. **Never set a default limit above 100** — even if the tool supports higher values; the caller can increase it explicitly.
3. **Never auto-paginate** — fetching page 2+ without an explicit caller request is forbidden; it silently multiplies token consumption.
4. **Always start at the low end of the safe range** — prefer 10 over 50 when context size is unknown.
5. **SQL-executing tools must reject queries without a `LIMIT` clause** — validate the SQL string server-side before executing.
6. **Do not exempt a tool from guardrails based on name prefix alone** — a `get_*` tool that returns an array needs limits just like `list_*`.
7. **Do not apply guardrails to streaming/generative tools** — LLM completions, SSE, and WebSocket tools are not collection-listing operations.
8. **Do not hardcode absolute paths or employer-specific tool names** in server designs — keep examples generic (`owner="my-org"`, not a real org name).

## Anti-patterns

**Missing limit entirely:**
```
# BAD — no limit; can return thousands of items
list_issues(repo="my-repo", state="open")

# GOOD
list_issues(repo="my-repo", state="open", limit=20)
```

**Auto-paginating all results:**
```
# BAD — fetches every page and returns merged array
results = []
page = 1
while True:
    batch = list_issues(repo="my-repo", page=page, per_page=100)
    if not batch: break
    results.extend(batch)
    page += 1
return results

# GOOD — return page 1 and expose a cursor for the caller to continue
return list_issues(repo="my-repo", page=1, per_page=20, cursor=None)
```

**Unbounded SQL:**
```sql
-- BAD
SELECT * FROM events WHERE user_id = $1

-- GOOD
SELECT * FROM events WHERE user_id = $1 ORDER BY created_at DESC LIMIT 50
```

**Overly broad search without filters:**
```
# BAD — searches entire GitHub with no scope
search_code(query="auth")

# GOOD
search_code(query="auth", repo="my-org/my-repo", path="src/auth", limit=10)
```

**GraphQL auto-cursor-following:**
```python
# BAD — follows all cursors before returning
cursor = None
all_items = []
while True:
    page = client.issues(first=100, after=cursor)
    all_items.extend(page.nodes)
    if not page.pageInfo.hasNextPage:
        break
    cursor = page.pageInfo.endCursor
return all_items

# GOOD — return first page with cursor for caller to continue
page = client.issues(first=20, after=after_cursor)
return {"items": page.nodes, "endCursor": page.pageInfo.endCursor, "hasNextPage": page.pageInfo.hasNextPage}
```

## Quick-reference checklist

Before approving any MCP server design that includes list or search tools, verify:

- [ ] Every `list_*` tool has `limit` or `per_page` (REST) or `first` (GraphQL) with a documented default <= 100
- [ ] Every `search_*` tool has `limit` or `max_results` with a documented default <= 20
- [ ] GraphQL list tools expose `first` + `after` and return `pageInfo.endCursor` + `pageInfo.hasNextPage`
- [ ] SQL-executing tools validate for a `LIMIT` clause before running
- [ ] No tool auto-paginates; pagination is always caller-driven
- [ ] Streaming/generative tools (LLM completions, SSE, WebSocket) are correctly exempted
- [ ] `get_*` tools that return arrays are treated as list operations (not exempted by name prefix)
- [ ] Date/state/author filters are available as fallback when no limit param exists
- [ ] Default limits are at the conservative end of the safe range for the operation type

When invoked from `mcp-builder` Phase 4.1, report pass/fail per item. Return control to `mcp-builder` only after all items pass or you list concrete fixes for each failure.

Files in this skill

  • SKILL.md9.6 KB
  • evals/evals.json7.6 KB
  • evals/trigger-evals.json768 B
  • references/pagination-patterns.md5.3 KB
  • scripts/check-pagination.sh4.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.