Skip to main content

← Back to overview

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

Metadata

Release files for mcp-pager 0.8.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-pager 0.8.0
File Size Uploaded
mcp_pager-0.8.0.tar.gz 14.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-pager 0.8.0
File Interpreter ABI Platform
mcp_pager-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 26.2 kB

Release files / mcp_pager-0.8.0.tar.gz

Download URL mcp_pager-0.8.0.tar.gz
Size 14.5 kB
Tags Source
SHA-256 checksum
How to use checksums
af9f343a440ac4b6099e62d94f0303458aa34790d2c0764016a0291174342270
BLAKE2b-256 checksum
How to use checksums
e8a65e83ca6efe94dc7cbb9f41d83235472fcc993bd6da445297e795774e94cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release files / mcp_pager-0.8.0-py3-none-any.whl

Download URL mcp_pager-0.8.0-py3-none-any.whl
Size 11.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
910aae668a4a71d279625982e185cbd0932b43b74316e10f6287272bab25537d
BLAKE2b-256 checksum
How to use checksums
01e0e02e23d52dde8d92b4b2b1c71243d377869b14da00e7b9cf17f63bb3341a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.5

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page