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

DO
2mo ago
+342-1

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