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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_pager-0.8.0.tar.gz (14.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_pager-0.8.0-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

Details for the file mcp_pager-0.8.0.tar.gz.

File metadata

  • Download URL: mcp_pager-0.8.0.tar.gz
  • Upload date:
  • Size: 14.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for mcp_pager-0.8.0.tar.gz
Algorithm Hash digest
SHA256 af9f343a440ac4b6099e62d94f0303458aa34790d2c0764016a0291174342270
MD5 8c78a53329c6893c820f9413890fd073
BLAKE2b-256 e8a65e83ca6efe94dc7cbb9f41d83235472fcc993bd6da445297e795774e94cc

See more details on using hashes here.

File details

Details for the file mcp_pager-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: mcp_pager-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 11.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for mcp_pager-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 910aae668a4a71d279625982e185cbd0932b43b74316e10f6287272bab25537d
MD5 d074fad885f9756156c240884d101a1c
BLAKE2b-256 01e0e02e23d52dde8d92b4b2b1c71243d377869b14da00e7b9cf17f63bb3341a

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 files

0.6.0

2 files

0.5.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page