Skip to main content

toolcap

Keep MCP tool results under the client's token limit, with pages the model can follow.

A tool that returns 200 flow runs, a long log or a big page snapshot can blow past the client's limit (Claude Code rejects results over 25,000 tokens) or fill the context in one call. toolcap cuts any result into pages of whole items that fit a token budget, each with a next_cursor and a note telling the model how to get the next page, in one call or one decorator.

pip install toolcap

Python 3.9+, no dependencies. Works with any MCP framework (the official MCP Python SDK 1.x FastMCP and 2.x MCPServer, the standalone fastmcp) or none, because it only turns a value into text.

Example

from mcp.server.fastmcp import FastMCP     # SDK 2.x: from mcp.server.mcpserver import MCPServer
from toolcap import Cap

mcp = FastMCP("prefect")
cap = Cap(max_tokens=20_000)

@mcp.tool()
@cap.paginate                      # adds a `cursor: str = ""` parameter for the model
def get_flow_runs(state: str = "COMPLETED", limit: int = 200) -> list:
    """List flow runs."""
    return api.flow_runs(state=state, limit=limit)    # any JSON-able value

If the 200 runs fit in 20,000 tokens the model gets them unchanged. If not, it gets as many whole runs as fit, plus a page object:

{"items": [{"id": "...", "name": "..."}, ...],
 "page": {"items": "1-37 of 200", "total": 200, "next_cursor": "MTozNzo1YzBmYTE5Mw",
          "note": "Showing items 1-37 of 200. Call this tool again with the same arguments and cursor=\"MTozNzo1YzBmYTE5Mw\" for the next page."}}

The model calls get_flow_runs(state="COMPLETED", limit=200, cursor="MTozNzo1YzBmYTE5Mw") and gets items 38 onward. The decorator also adds one sentence about next_cursor to the tool's description. It works on sync and async tools, keeps Context parameters hidden from the model as before, and resolves string annotations (from __future__ import annotations).

Without the decorator, call it yourself. This is the way to go when you want the cursor in your own signature, or to page only some of the result:

@mcp.tool(structured_output=False)
def read_log(run_id: str, cursor: str = "") -> str:
    """Read a flow run's log."""
    return cap.page(api.log_text(run_id), cursor)

What it does with each kind of result

Result What the model gets when it is too big
A list A page of whole items, wrapped as {"items": [...], "page": {...}}
A dict with a list inside ({"count": 500, "results": [...]}) The same dict with the largest list paged, the other fields kept, and a page key next to it (_page if the dict already has a page). Pick the list with key="data.items"
Text A chunk cut at a line break (or a space), then [Output cut: showing characters 1-5850 of 65000. Call this tool again ... cursor="..."]
One item bigger than the whole budget That item with its long strings (then long lists) cut, marked ... [N more characters cut], and paging moves on to the next item
Anything else (a dict without lists, a huge string field) Every key kept, long strings and lists cut until it fits

cap.trim(data) does the same without cursors, for tools where paging makes no sense: the model gets the first part and a note saying how many items were left out and to narrow the request. Pydantic models, dataclasses, tuples and sets are converted to JSON first.

Settings

Cap(
    max_tokens=None,        # default: the TOOLCAP_MAX_TOKENS env var, else 20,000
    count=None,             # text -> token count; default is a high estimate (UTF-8 bytes / 3)
    indent=None,            # JSON indent; None (compact) uses the fewest tokens
    cursor_param="cursor",  # name of the parameter the decorator adds
)

For an exact fit, pass a real tokenizer: Cap(count=lambda s: len(enc.encode(s))). The default estimate counts one token per 3 bytes of UTF-8, which is above what real tokenizers give for English, JSON, code and CJK text, so pages come out a little smaller than they could be but never over the limit. Users of your server can change the budget with TOOLCAP_MAX_TOKENS without code changes (match it to MAX_MCP_OUTPUT_TOKENS if they raised that in Claude Code).

Module-level shortcuts with default settings: toolcap.page(data, cursor), toolcap.trim(data) and @toolcap.paginate.

How paging works, and its limits

  • Cursors are stateless: an offset plus a short fingerprint of the full result. The server runs the tool again on every page and slices it, so nothing is stored between calls and it works across processes and restarts. The cost is that each page re-runs the query; for an expensive backend, cache the result yourself or pass the cursor's offset on to the backend.
  • If the result changed between pages (new rows came in), the page's note says so and that items may be repeated or missed. Give results a stable sort order.
  • A cursor that toolcap did not issue is rejected with an error the model can act on ("call the tool without a cursor to start from the beginning"). It is the SDK's own ToolError, so the text reaches the model on SDK 2.x too.
  • The decorated tool returns text and declares no output schema, so the SDKs don't also copy the page into structuredContent (which would double its size, and some clients skip their truncation for structured content). Use structured_output=False on hand-written -> str tools for the same reason.
  • The budget covers one result. It does not know how full the model's context already is.
  • Lists are paged at one level (the largest list reached through dict keys). Lists inside list items are cut, not paged.

Why this exists

MCP servers keep running into the client's result-size limit, and each one fixes it by hand with ad-hoc limit parameters or a second "minimal" tool:

License

MIT

Metadata

Release files for toolcap 0.1.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 toolcap 0.1.0
File Size Uploaded Provenance Actions
toolcap-0.1.0.tar.gz 17.2 kB Without provenance Details for toolcap-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for toolcap 0.1.0
File Interpreter ABI Platform Actions
toolcap-0.1.0-py3-none-any.whl Python 3 none any Details for toolcap-0.1.0-py3-none-any.whl

Total release size: 30.5 kB

Release files / toolcap-0.1.0.tar.gz

Download URL toolcap-0.1.0.tar.gz
Size 17.2 kB
Tags Source
SHA-256 checksum
How to use checksums
6818b81ff7dea3a519a006597c19dc1d56d5c6dccf468790f880939eeb0efbf2
BLAKE2b-256 checksum
How to use checksums
3cd72f182dbae8b834a00631241e3a3c2838720c2ea9348b72f9f645b27c16be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / toolcap-0.1.0-py3-none-any.whl

Download URL toolcap-0.1.0-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
67990771b0039b790a3f3e690374885ac7dcd45e783ed31cbd898e26f7bad91b
BLAKE2b-256 checksum
How to use checksums
8c7e3bbb045f00354bacc35ffb117e9e7daedd9228b90ab967b1e3f19545fa06
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for toolcap 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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