Skip to content

Commit 83d4890

Browse files
committed
feat: add pagination examples and documentation
- Create mcp_simple_pagination example server demonstrating all three paginated endpoints - Add pagination snippets for both server and client implementations - Update README to use snippet-source pattern for pagination examples - Move mutually exclusive note to blockquote format for better visibility - Complete example shows tools, resources, and prompts pagination with different page sizes
1 parent 9d72703 commit 83d4890

File tree

8 files changed

+546
-0
lines changed

8 files changed

+546
-0
lines changed

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
- [Mounting to an Existing ASGI Server](#mounting-to-an-existing-asgi-server)
4848
- [Advanced Usage](#advanced-usage)
4949
- [Low-Level Server](#low-level-server)
50+
- [Pagination (Advanced)](#pagination-advanced)
5051
- [Writing MCP Clients](#writing-mcp-clients)
5152
- [Client Display Utilities](#client-display-utilities)
5253
- [OAuth Authentication for Clients](#oauth-authentication-for-clients)
@@ -1530,6 +1531,124 @@ Tools can return data in three ways:
15301531

15311532
When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early.
15321533

1534+
### Pagination (Advanced)
1535+
1536+
For servers that need to handle large datasets, the low-level server provides paginated versions of list operations. This is an optional optimization - most servers won't need pagination unless they're dealing with hundreds or thousands of items.
1537+
1538+
#### Server-side Implementation
1539+
1540+
<!-- snippet-source examples/snippets/servers/pagination_example.py -->
1541+
```python
1542+
"""
1543+
Example of implementing pagination with MCP server decorators.
1544+
"""
1545+
1546+
import mcp.types as types
1547+
from mcp.server.lowlevel import Server
1548+
from pydantic import AnyUrl
1549+
1550+
# Initialize the server
1551+
server = Server("paginated-server")
1552+
1553+
# Sample data to paginate
1554+
ITEMS = [f"Item {i}" for i in range(1, 101)] # 100 items
1555+
1556+
1557+
@server.list_resources_paginated()
1558+
async def list_resources_paginated(cursor: types.Cursor | None) -> types.ListResourcesResult:
1559+
"""List resources with pagination support."""
1560+
page_size = 10
1561+
1562+
# Parse cursor to get offset
1563+
start = 0 if cursor is None else int(cursor)
1564+
end = start + page_size
1565+
1566+
# Get page of resources
1567+
page_items = [
1568+
types.Resource(
1569+
uri=AnyUrl(f"resource://items/{item}"),
1570+
name=item,
1571+
description=f"Description for {item}"
1572+
)
1573+
for item in ITEMS[start:end]
1574+
]
1575+
1576+
# Determine next cursor
1577+
next_cursor = str(end) if end < len(ITEMS) else None
1578+
1579+
return types.ListResourcesResult(
1580+
resources=page_items,
1581+
nextCursor=next_cursor
1582+
)
1583+
```
1584+
1585+
_Full example: [examples/snippets/servers/pagination_example.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/pagination_example.py)_
1586+
<!-- /snippet-source -->
1587+
1588+
Similar decorators are available for all list operations:
1589+
- `@server.list_tools_paginated()` - for paginating tools
1590+
- `@server.list_resources_paginated()` - for paginating resources
1591+
- `@server.list_prompts_paginated()` - for paginating prompts
1592+
1593+
#### Client-side Consumption
1594+
1595+
<!-- snippet-source examples/snippets/clients/pagination_client.py -->
1596+
```python
1597+
"""
1598+
Example of consuming paginated MCP endpoints from a client.
1599+
"""
1600+
1601+
import asyncio
1602+
from mcp.client.session import ClientSession
1603+
from mcp.client.stdio import StdioServerParameters, stdio_client
1604+
1605+
1606+
async def list_all_resources():
1607+
"""Fetch all resources using pagination."""
1608+
async with stdio_client(
1609+
StdioServerParameters(command="uv", args=["run", "mcp-simple-pagination"])
1610+
) as (read, write):
1611+
async with ClientSession(read, write) as session:
1612+
await session.initialize()
1613+
1614+
all_resources = []
1615+
cursor = None
1616+
1617+
while True:
1618+
# Fetch a page of resources
1619+
result = await session.list_resources(cursor=cursor)
1620+
all_resources.extend(result.resources)
1621+
1622+
print(f"Fetched {len(result.resources)} resources")
1623+
1624+
# Check if there are more pages
1625+
if result.nextCursor:
1626+
cursor = result.nextCursor
1627+
else:
1628+
break
1629+
1630+
print(f"Total resources: {len(all_resources)}")
1631+
return all_resources
1632+
1633+
1634+
if __name__ == "__main__":
1635+
asyncio.run(list_all_resources())
1636+
```
1637+
1638+
_Full example: [examples/snippets/clients/pagination_client.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/pagination_client.py)_
1639+
<!-- /snippet-source -->
1640+
1641+
#### Key Points
1642+
1643+
- **Cursors are opaque strings** - the server defines the format (numeric offsets, timestamps, etc.)
1644+
- **Return `nextCursor=None`** when there are no more pages
1645+
- **Backward compatible** - clients that don't support pagination will still work (they'll just get the first page)
1646+
- **Flexible page sizes** - Each endpoint can define its own page size based on data characteristics
1647+
1648+
> **NOTE**: The paginated decorators (`list_tools_paginated()`, `list_resources_paginated()`, `list_prompts_paginated()`) are mutually exclusive with their non-paginated counterparts and cannot be used together on the same server instance.
1649+
1650+
See the [simple-pagination example](examples/servers/simple-pagination) for a complete implementation.
1651+
15331652
### Writing MCP Clients
15341653

15351654
The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports):
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# MCP Simple Pagination
2+
3+
A simple MCP server demonstrating pagination for tools, resources, and prompts using cursor-based pagination.
4+
5+
## Usage
6+
7+
Start the server using either stdio (default) or SSE transport:
8+
9+
```bash
10+
# Using stdio transport (default)
11+
uv run mcp-simple-pagination
12+
13+
# Using SSE transport on custom port
14+
uv run mcp-simple-pagination --transport sse --port 8000
15+
```
16+
17+
The server exposes:
18+
- 25 tools (paginated, 5 per page)
19+
- 30 resources (paginated, 10 per page)
20+
- 20 prompts (paginated, 7 per page)
21+
22+
Each paginated list returns a `nextCursor` when more pages are available. Use this cursor in subsequent requests to retrieve the next page.
23+
24+
## Example
25+
26+
Using the MCP client, you can retrieve paginated items like this using the STDIO transport:
27+
28+
```python
29+
import asyncio
30+
from mcp.client.session import ClientSession
31+
from mcp.client.stdio import StdioServerParameters, stdio_client
32+
33+
34+
async def main():
35+
async with stdio_client(
36+
StdioServerParameters(command="uv", args=["run", "mcp-simple-pagination"])
37+
) as (read, write):
38+
async with ClientSession(read, write) as session:
39+
await session.initialize()
40+
41+
# Get first page of tools
42+
tools_page1 = await session.list_tools()
43+
print(f"First page: {len(tools_page1.tools)} tools")
44+
print(f"Next cursor: {tools_page1.nextCursor}")
45+
46+
# Get second page using cursor
47+
if tools_page1.nextCursor:
48+
tools_page2 = await session.list_tools(cursor=tools_page1.nextCursor)
49+
print(f"Second page: {len(tools_page2.tools)} tools")
50+
51+
# Similarly for resources
52+
resources_page1 = await session.list_resources()
53+
print(f"First page: {len(resources_page1.resources)} resources")
54+
55+
# And for prompts
56+
prompts_page1 = await session.list_prompts()
57+
print(f"First page: {len(prompts_page1.prompts)} prompts")
58+
59+
60+
asyncio.run(main())
61+
```
62+
63+
## Pagination Details
64+
65+
The server uses simple numeric indices as cursors for demonstration purposes. In production scenarios, you might use:
66+
- Database offsets or row IDs
67+
- Timestamps for time-based pagination
68+
- Opaque tokens encoding pagination state
69+
70+
The pagination implementation demonstrates:
71+
- Handling `None` cursor for the first page
72+
- Returning `nextCursor` when more data exists
73+
- Gracefully handling invalid cursors
74+
- Different page sizes for different resource types

examples/servers/simple-pagination/mcp_simple_pagination/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import sys
2+
3+
from .server import main
4+
5+
sys.exit(main()) # type: ignore[call-arg]

0 commit comments

Comments
 (0)