mcp-token-lint
See what your MCP servers are costing you before you connect.
Three ordinary MCP servers — GitHub, Slack, Sentry, about 40 tools total — can burn 72% of a 200,000-token context window on tool schemas alone, before an agent reads a single message. Nothing in the MCP client stack warns you about this cost until your session is already thin.
mcp-token-lint tokenizes every tool schema across every server in your MCP config, reports the exact cost per server and per tool, and fails like a CI budget check if the total crosses a threshold you set — before you ever connect.
Install
pip install mcp-token-lint
Usage
Point it at a standard MCP client config (the mcpServers format used by Claude Desktop, Claude Code, Cursor, and similar tools):
mcp-token-lint check ~/.config/claude/mcp.json --budget 40000
Or check a pre-exported list of tool definitions without launching any servers — useful in CI, or for transports mcp-token-lint doesn't talk to yet:
mcp-token-lint check --tools-json tools.json --budget 40000
tools.json is a {server_name: [tool, ...]} map, where each tool has name, description, and inputSchema — the same shape returned by an MCP server's tools/list.
Example output:
SERVER TOOLS TOKENS
----------------------------------------
github 34 18,412
slack 9 3,105
sentry 12 6,880
----------------------------------------
TOTAL 55 28,397
Budget: 40,000 tokens (28,397 used, 71%) [within budget]
FLAGGED TOOLS (waste detected)
------------------------------------------------------------
github/search_repositories 612 tok verbose_description (-140 est.)
github/create_issue 210 tok boilerplate_prefix (-18 est.)
------------------------------------------------------------
Run `mcp-token-lint fix` to review and apply suggested trims.
mcp-token-lint check exits non-zero when the total is over budget, so it works as a CI gate:
mcp-token-lint check .mcp.json --budget 40000 || exit 1
Add --json for machine-readable output, and --timeout to change how long mcp-token-lint waits for a slow server to start (default 20s).
Fixing what it finds
check tells you which tools are wasting tokens and why. fix walks through them and lets you actually trim the fat:
mcp-token-lint fix ~/.config/claude/mcp.json
github/search_repositories (flags: verbose_description, est. savings: 140 tok)
current: This tool allows you to search for GitHub repositories matching a query...
suggested: Search for GitHub repositories matching a query string, with optional...
[a]ccept / [e]dit / [s]kip / [q]uit >
[a]ccepttakes the suggested trim as-is.[e]ditlets you type your own replacement instead.[s]kipleaves that tool untouched.[q]uitstops the session; anything already accepted is still saved.
Accepted trims are written to mcp-token-lint.overrides.json (--out to change the path) — a plain {server: {tool: {"description": "..."}}} map you (or a teammate) can use as the checklist for editing the server's actual source, or feed into your own MCP proxy to rewrite tools/list responses live.
Two non-interactive modes:
mcp-token-lint fix .mcp.json --yes # accept every suggestion, for a first pass or CI
mcp-token-lint fix .mcp.json --dry-run # preview suggestions, write nothing
Waste detection is rule-based, not model-based — deterministic and fast enough for CI:
boilerplate_prefix— description opens with filler like "This tool allows you to..." that a model doesn't need.verbose_description— description runs well past what's needed to disambiguate the tool; suggests trimming to the first sentence.schema_bloat— the input schema is disproportionately large relative to the description, usually from repeated per-property boilerplate (flagged for manual review — schema structure isn't auto-rewritten, since a bad automatic edit there can break validation).
verbose_description trims to the first sentence where there is one. When the
whole description is a single run-on sentence — the shape bloated descriptions
usually take — it cuts at the last clause boundary that fits under the limit
instead, rather than flagging something it can't act on.
Exit codes
check is meant to be wired into CI, so the exit code is the contract:
| Code | Meaning |
|---|---|
0 |
Within budget (or no budget set) |
1 |
Over budget |
2 |
One or more servers could not be measured — results are incomplete |
Code 2 matters more than it looks. A budget gate that reports success because
it silently failed to reach every server is worse than no gate at all, so an
unreachable server fails the run by default. Pass --ignore-fetch-errors to
downgrade that to a warning when you genuinely want a partial check.
Running offline
tiktoken downloads its BPE file the first time it encodes anything — a poor
dependency for a tool built to run in CI, where the network is often locked
down. mcp-token-lint degrades instead of crashing: if cl100k_base can't be loaded
it falls back to a deterministic approximate tokenizer, says so once on stderr,
and marks the report and JSON output as approximate.
NOTE: counts are approximate — the cl100k_base encoding could not be loaded,
so a fallback tokenizer was used.
Set MCP_TOKEN_LINT_TOKENIZER=approx to skip tiktoken entirely and silence the
warning. Approximate counts are stable run-to-run, so they still catch
regressions and compare servers against each other — they're just not exact.
Docker
docker build -t mcp-token-lint .
docker run --rm -v "$PWD:/work" mcp-token-lint check /work/.mcp.json --budget 40000
The image bakes the cl100k_base encoding into its tiktoken cache at build
time, so the container gives exact counts even on a restricted network.
Note that check <config> launches the stdio servers in your config — inside
a container those commands (npx, uvx, your own binaries) need to exist in
the image or be mounted in. For CI, --tools-json is usually the better fit.
Compatibility
Python 3.10+. Works with both mcp 1.x and 2.x — the SDK renamed
Tool.inputSchema to Tool.input_schema in 2.0, and mcp-token-lint reads either.
What it does and doesn't do
- Launches each configured
command-based (stdio) server, callstools/list, and tokenizes the exact JSON a model would see: name, description, and input schema. - Token counts use
tiktoken'scl100k_baseencoding as a consistent, practical approximation — not an exact match for every model's tokenizer, but stable enough to catch regressions and to compare servers against each other. - URL-based (HTTP/SSE) servers are skipped when reading from a config file for now — export their tool list and use
--tools-jsoninstead.
Why this exists
MCP standardized how a model talks to a tool. It never standardized how much that conversation should cost. Server authors write schemas that mirror their REST API surface — the easy path for an SDK generator, the expensive path for your context window. mcp-token-lint is the linter that was missing.
Development
pip install -e ".[dev]"
pytest
The test suite installs its own deterministic tokenizer, so it runs fully offline and never depends on tiktoken's download.
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
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_token_lint-0.2.0.tar.gz.
File metadata
- Download URL: mcp_token_lint-0.2.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6ddfcae1a4252f7b5b4ab9b65078ab04d5cbb761c9d8c37113c5f3e31641200d
|
|
| MD5 |
c4e5ebc2d095551dbcab01624863f782
|
|
| BLAKE2b-256 |
723f746faf6239d7de5b37c4bed6e0cdfd3a70b1b8bd4ad96732893d47a7760e
|
Provenance
The following attestation bundles were made for mcp_token_lint-0.2.0.tar.gz:
Publisher:
publish.yml on yashdhingra0/mcp-token-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_token_lint-0.2.0.tar.gz -
Subject digest:
6ddfcae1a4252f7b5b4ab9b65078ab04d5cbb761c9d8c37113c5f3e31641200d - Sigstore transparency entry: 2598585390
- Sigstore integration time:
-
Permalink:
yashdhingra0/mcp-token-lint@59ac0095d69c10504ddf7c07909360f032d61c18 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yashdhingra0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59ac0095d69c10504ddf7c07909360f032d61c18 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcp_token_lint-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp_token_lint-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4fe06351617dbce87a6b499478a01d846318edbf6ef99a033e81e2a155a2a3a8
|
|
| MD5 |
e45a4afda182b28038033efcf0c05b8d
|
|
| BLAKE2b-256 |
915ccc1912bd0d73234499a4ecf8b2e1e612fd66df72274682e7d45c6fe63d58
|
Provenance
The following attestation bundles were made for mcp_token_lint-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on yashdhingra0/mcp-token-lint
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_token_lint-0.2.0-py3-none-any.whl -
Subject digest:
4fe06351617dbce87a6b499478a01d846318edbf6ef99a033e81e2a155a2a3a8 - Sigstore transparency entry: 2598585734
- Sigstore integration time:
-
Permalink:
yashdhingra0/mcp-token-lint@59ac0095d69c10504ddf7c07909360f032d61c18 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/yashdhingra0
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@59ac0095d69c10504ddf7c07909360f032d61c18 -
Trigger Event:
release
-
Statement type: