Metadata-Version: 2.5
Name: mcp-token-lint
Version: 0.2.0
Summary: Token budget linter for MCP tool schemas — see what your connected servers are costing you before you burn the context window.
Project-URL: Homepage, https://github.com/yashdhingra0/mcp-token-lint
Project-URL: Repository, https://github.com/yashdhingra0/mcp-token-lint
Project-URL: Issues, https://github.com/yashdhingra0/mcp-token-lint/issues
Author-email: Yash Dhingra <dhingrayash001@gmail.com>
License: MIT
License-File: LICENSE
Keywords: agents,ci,context-window,linter,llm,mcp,model-context-protocol,tokens
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Requires-Dist: mcp>=1.0.0
Requires-Dist: tiktoken>=0.7.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# mcp-token-lint

[![CI](https://github.com/yashdhingra0/mcp-token-lint/actions/workflows/ci.yml/badge.svg)](https://github.com/yashdhingra0/mcp-token-lint/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/mcp-token-lint.svg)](https://pypi.org/project/mcp-token-lint/)
[![Python](https://img.shields.io/pypi/pyversions/mcp-token-lint.svg)](https://pypi.org/project/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]ccept` takes the suggested trim as-is.
- `[e]dit` lets you type your own replacement instead.
- `[s]kip` leaves that tool untouched.
- `[q]uit` stops 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

```bash
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, calls `tools/list`, and tokenizes the exact JSON a model would see: name, description, and input schema.
- Token counts use `tiktoken`'s `cl100k_base` encoding 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-json` instead.

## 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

```bash
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
