Token-aware response paging for MCP servers — chunks large tool responses and delivers them page by page with agent-readable metadata
Project description
mcp-pager — Python
Token-aware response management for MCP servers.
pip install mcp-pager
from mcp.server.fastmcp import FastMCP
from mcp_pager import paginate
mcp = FastMCP("my-server")
paginate(mcp, max_tokens=4000)
@mcp.tool()
async def list_records(limit: int = 500) -> str:
records = await db.fetch(limit=limit) # could be huge
return json.dumps(records)
How it works
Same as the TypeScript version — one call wraps every tool you register. Oversized responses are chunked and delivered page by page with agent-readable metadata:
{
"hasMore": true,
"pageIndex": 0,
"totalPages": 12,
"remainingPages": 11,
"nextCursor": "eyJpZCI6Ijkx...",
"instruction": "Call `get_next_page` with nextCursor to get the next page. Repeat until hasMore is false."
}
Installation
# Core only
pip install mcp-pager
# With exact tiktoken token counting (recommended)
pip install "mcp-pager[tiktoken]"
# With Redis backend
pip install "mcp-pager[redis]"
# Both
pip install "mcp-pager[tiktoken,redis]"
Requirements: Python ≥ 3.10, mcp ≥ 1.0.0
API
paginate(mcp, **options)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcp |
FastMCP |
— | The FastMCP server to wrap |
max_tokens |
int |
4000 |
Max tokens per page |
ttl_ms |
int |
600000 |
Cursor TTL in ms, sliding (10 min) |
token_counter |
Callable[[str], int] |
auto | Custom token counter |
page_tool_name |
str |
"get_next_page" |
Name of the injected pagination tool |
store |
StoreBackend |
MemoryBackend |
Custom storage backend |
signing_secret |
str |
None |
HMAC-sign cursors (sha256) |
on_paginate |
Callable[[PaginateEvent], None] |
None |
Lifecycle callback for logging |
Returns the same FastMCP instance.
Token counting
By default, mcp-pager auto-detects tiktoken and uses it for exact cl100k_base counts. If tiktoken is not installed it falls back to a content-aware heuristic (±10%).
pip install "mcp-pager[tiktoken]" # enables exact counting automatically
Custom counter:
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
paginate(mcp, token_counter=lambda text: len(enc.encode(text)))
Storage backends
Default: in-memory
Works out of the box with sliding TTL. Not suitable for multi-process or serverless deployments.
Redis (production)
pip install "mcp-pager[redis]"
from redis.asyncio import Redis
from mcp_pager.backends.redis import RedisBackend
redis = Redis.from_url(os.environ["REDIS_URL"])
paginate(mcp, store=RedisBackend(redis), ttl_ms=10 * 60 * 1000)
Custom backend
from mcp_pager import StoreBackend
class DynamoBackend(StoreBackend):
async def get(self, id: str) -> list[str] | None: ...
async def set(self, id: str, chunks: list[str], ttl_ms: int) -> None: ...
async def delete(self, id: str) -> None: ...
async def refresh(self, id: str, ttl_ms: int) -> None: ...
paginate(mcp, store=DynamoBackend())
Observability — on_paginate
from mcp_pager import ChunkedEvent, PageFetchedEvent, CursorExpiredEvent
def handle_event(event):
if event.type == "chunked":
print(f"{event.tool_name} → {event.total_chunks} pages ({event.total_tokens} tokens)")
elif event.type == "page_fetched":
print(f"page {event.page_index + 1}/{event.total_pages} hasMore={event.has_more}")
elif event.type == "cursor_expired":
print("cursor expired")
paginate(mcp, on_paginate=handle_event)
Cursor signing (HMAC)
import os
paginate(mcp, signing_secret=os.environ["CURSOR_SECRET"])
Cursors are signed with HMAC-sha256. Tampered cursors are rejected before any store lookup.
LLM prompting guide
Add this to your system prompt so the LLM reliably follows pagination:
When a tool response contains a pagination cursor (hasMore: true), you MUST call
get_next_page with that cursor before answering. Keep calling until hasMore is false.
Never answer from partial results.
Demo server
python examples/demo_server.py
Tools: list_records, list_files, fetch_logs, get_next_page
Development
git clone https://github.com/SatishKakollu/mcp-pager
cd mcp-pager/python
pip install -e ".[dev]"
pytest tests/ -v
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_pager-0.6.0.tar.gz.
File metadata
- Download URL: mcp_pager-0.6.0.tar.gz
- Upload date:
- Size: 13.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13551bc1b65ea7d65e7a14addbd0845f57fc2b86c9134589ff68ab351ec8cbc1
|
|
| MD5 |
4ae219c97d97e50b74333333473f2c87
|
|
| BLAKE2b-256 |
21e6c27e39a2f3b1ce27bc50d3566ec537e122afea6b7e31ac659b33646cbbe1
|
File details
Details for the file mcp_pager-0.6.0-py3-none-any.whl.
File metadata
- Download URL: mcp_pager-0.6.0-py3-none-any.whl
- Upload date:
- Size: 11.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f1ce33cb47a2b33462cf69f59bcc158dbd5ac45dbc4a47f34072f0560e88b16
|
|
| MD5 |
a9d77c68775ad489306d4f23c1698e28
|
|
| BLAKE2b-256 |
c50b4680e9c94d0956edc943b8f2e5e6b752713c425703806bba91a4f40cdbaf
|