Skip to content

mcp/client: add list_all_* helpers with pagination safety guards#3156

Open
dongjiang1989 wants to merge 1 commit into
modelcontextprotocol:mainfrom
dongjiang1989:update-paginate-py
Open

mcp/client: add list_all_* helpers with pagination safety guards#3156
dongjiang1989 wants to merge 1 commit into
modelcontextprotocol:mainfrom
dongjiang1989:update-paginate-py

Conversation

@dongjiang1989

@dongjiang1989 dongjiang1989 commented Jul 24, 2026

Copy link
Copy Markdown

The Python SDK lacks auto-pagination helpers for the four list verbs (tools/list, resources/list, resources/templates/list, prompts/list). Callers must write their own cursor-draining loops, and those loops have no protection against infinite pagination or cursor cycles.

The TypeScript SDK already provides this via _listAllPages with a listMaxPages cap and cursor cycle detection (see typescript-sdk#2336). The Go SDK is getting the same guards in go-sdk#1110. This PR brings the Python SDK to parity.

What changed

Four new methods on the high-level Client:

await client.list_all_tools()                -> ListToolsResult
await client.list_all_resources()            -> ListResourcesResult
await client.list_all_resource_templates()   -> ListResourceTemplatesResult
await client.list_all_prompts()              -> ListPromptsResult

Each drains all pages into a single aggregated result via a shared
_drain_all_pages helper with two safety guards:

  1. Page cap: Client.list_max_pages (default 64, matching the TS
    SDK's DEFAULT_LIST_MAX_PAGES). Exceeding the cap raises
    PaginationExceededError. Setting to 0 or a negative value
    disables the cap (unlimited).

  2. Cursor cycle detection: a seen set tracks all cursors
    returned by the server. A repeated cursor raises
    CursorCycleError, preventing infinite loops from buggy servers.

Both exception types are exported from mcp.client.

Configuration

# Default: 64 pages
client = Client(server)

# Custom cap
client = Client(server, list_max_pages=100)

# Unlimited
client = Client(server, list_max_pages=0)

How Has This Been Tested

9 new tests in tests/client/test_list_all_pagination.py:

  • Normal multi-item drain for all 4 verbs
  • Empty server returns empty lists
  • PaginationExceededError when cap exceeded
  • CursorCycleError on repeated cursor
  • Unlimited mode (list_max_pages=0)
  • Terminal cursor stripping (next_cursor is None)

All 709 existing client tests continue to pass. Lint (ruff check)
and type check (pyright) are clean.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/mcp/client/client.py Outdated
Comment thread src/mcp/client/client.py Outdated
dongjiang1989 added a commit to dongjiang1989/python-sdk that referenced this pull request Jul 24, 2026
- Collapse super().__init__() to single line for ruff format
- Add blank line after CursorCycleError class
- Simplify cursor cycle test to eliminate uncovered branch
- Add pragma: no branch for coverage.py false positive on nested async

Fixes CI failures on PR modelcontextprotocol#3156

Signed-off-by: dongjiang <dongjiang1989@126.com>
dongjiang1989 added a commit to dongjiang1989/python-sdk that referenced this pull request Jul 24, 2026
Replace **kwargs: object with explicit meta and cache_mode parameters
matching the list_tools signature to satisfy pyright strict checking.

Fixes CI pyright failure on PR modelcontextprotocol#3156

Signed-off-by: dongjiang <dongjiang1989@126.com>
dongjiang1989 added a commit to dongjiang1989/python-sdk that referenced this pull request Jul 24, 2026
- Use accumulator with extend() instead of list concatenation to avoid
  quadratic CPU/memory churn on large paginated listings
- Absorb final aggregated tools result as complete to prune stale session
  state (x-mcp-header maps and output schemas)

Fixes cubic review comments on PR modelcontextprotocol#3156

Signed-off-by: dongjiang <dongjiang1989@126.com>
Add four auto-pagination helpers (list_all_tools, list_all_resources,
list_all_resource_templates, list_all_prompts) that drain all pages
into a single aggregated result with safety guards:

- Cursor cycle detection via a seen-set; raises CursorCycleError
- Configurable page cap via list_max_pages (default 64); raises
  PaginationExceededError
- Use accumulator with extend() to avoid quadratic list copying
- Absorb final aggregated tools result as complete to prune stale
  session state (x-mcp-header maps and output schemas)

Aligns with TypeScript SDK's _listAllPages helper (DEFAULT_LIST_MAX_PAGES=64).

Signed-off-by: dongjiang <dongjiang1989@126.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant