Skip to main content

Token-aware response paging for MCP servers — chunks large tool responses and delivers them page by page with agent-readable metadata

Project description

← 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

Project details


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.6.0.tar.gz (13.9 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.6.0-py3-none-any.whl (11.2 kB view details)

Uploaded Python 3

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

Hashes for mcp_pager-0.6.0.tar.gz
Algorithm Hash digest
SHA256 13551bc1b65ea7d65e7a14addbd0845f57fc2b86c9134589ff68ab351ec8cbc1
MD5 4ae219c97d97e50b74333333473f2c87
BLAKE2b-256 21e6c27e39a2f3b1ce27bc50d3566ec537e122afea6b7e31ac659b33646cbbe1

See more details on using hashes here.

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

Hashes for mcp_pager-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3f1ce33cb47a2b33462cf69f59bcc158dbd5ac45dbc4a47f34072f0560e88b16
MD5 a9d77c68775ad489306d4f23c1698e28
BLAKE2b-256 c50b4680e9c94d0956edc943b8f2e5e6b752713c425703806bba91a4f40cdbaf

See more details on using hashes here.

Supported by

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