Skip to content

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

Open
dongjiang1989 wants to merge 2 commits into
modelcontextprotocol:mainfrom
dongjiang1989:update-paginate-py
Open

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

Conversation

@dongjiang1989

@dongjiang1989dongjiang1989 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:

awaitclient.list_all_tools() ->ListToolsResultawaitclient.list_all_resources() ->ListResourcesResultawaitclient.list_all_resource_templates() ->ListResourceTemplatesResultawaitclient.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 pagesclient=Client(server)
# Custom capclient=Client(server, list_max_pages=100)
# Unlimitedclient=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-aicubic-dev-aiBot 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 threadsrc/mcp/client/client.py Outdated
Comment threadsrc/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 freeto 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

@dongjiang1989