Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Context Cost

FreeMaintained

Measure what your MCP servers cost in context tokens — audit your own config, or badge the server you publish

GitHubEmbed

About

Measure what your MCP servers cost in context tokens — audit your own config, or badge the server you publish

README

npm CI GitHub Marketplace

Three entry points: audit, for the config you run (below); a GitHub Action, for the server you publish; and the measurement itself, as a library, for anything else. The Action is five lines in a workflow that fail a pull request adding more context cost than you meant to ship:

- uses: athakur3/mcp-context-cost@v1
  with:
    name: my-server
    command: node dist/index.js
    baseline: .context-cost/baseline.json
    max-increase: 500

Inputs: name, command or remote, baseline, max-increase, budget, timeout, version. Outputs: tokens, tools, status, measurement, badge — written whether the gate passed or not, so a later step can comment the number on the PR or publish the badge. Full workflow · how the gate decides · what the number is.

As a library, if you want the measurement without either wrapper — the same canonical form, the same tokenizer, the same badge:

import { measureTools, canonicalString, toBadge, METHODOLOGY_VERSION } from "mcp-context-cost";

It is the definition rather than the harness: measurement and canonical bytes, the color bands, the badge JSON, the Claude field-selection mapping, and the session-start load. It does not launch servers or read your config — that is what audit is for. The surface is pinned by a test, so a name is added or removed on purpose and says so in the changelog.

What do the MCP servers in your config cost you before you type anything — and what did that last config change add to every session you will ever run?

Every MCP server you wire into an agent has to put its tool schemas somewhere. For a client with no deferral on record that is the model's context, on every single request, whether or not the agent ends up using the tools; for a client that defers them — Claude Code by default, and Cursor, Codex CLI and VS Code by their vendors' own records — it is a smaller session-start cost plus whatever the agent reaches for. Which of the two you are paying is a property of your client, not of the server, and no client shows you either number. Point audit at your own MCP config:

npx -y mcp-context-cost audit
claude-desktop  ~/Library/Application Support/Claude/claude_desktop_config.json
  server               tools  tokens   share
  filesystem              14   2,823   35.7%
  memory                   9   2,378   30.1%
  everything              13   1,708   21.6%
  sequential-thinking      1     992   12.6%
  ────────────────────────────────────────────
  total                   37   7,901

  7,901 tokens of tool schemas — 4.0% of a 200,000-token context window.
  No default deferral is on record for claude-desktop, so every request
  carries these tokens before you type anything — an absence of a record
  about the client, not a measurement of it.

  heaviest tools
    sequential-thinking · sequentialthinking      990
    memory · search_nodes                         323

  trim: disabling 3 tools (sequential-thinking·sequentialthinking, memory·search_nodes,
  memory·open_nodes) would recover 1,635 tokens (20.7% of this config) — if your client
  supports per-tool filtering.

It finds configs for Claude Desktop, Claude Code (~/.claude.json, .mcp.json), Cursor, VS Code (.vscode/mcp.json), Windsurf, Codex CLI (~/.codex/config.toml), Gemini CLI (~/.gemini/settings.json), Zed (context_servers), Kiro (~/.kiro/settings/mcp.json) and Goose (~/.config/goose/config.yaml) — or pass --config <path>. Servers are measured by the same path as the published leaderboard (dual tools/list capture, o200k_base over canonical JSON), so a server in both places gets the same number. Nothing is written to your project, and no value an entry carries — an env var, a header — is ever read into the output. Only their names are.

A remote entry — url, or the client's own spelling of it — is probed before anything is launched, and reported as measured, auth-walled, protocol-mismatch or unreachable depending on what it answers; an auth-walled or protocol-mismatch row makes the total above it a floor rather than a number. The probe, what each verdict quotes, and why it exists at all are in METHODOLOGY §who pays the number. Nothing here opens a browser.

Totals are reported per config file, never merged: a context window belongs to one client session, so summing Cursor's servers into Claude Desktop's total would describe a session nobody runs.

Where this cost is paid in full, and where it is deferred away

Not every client puts every tool definition in context on every request, so the total above is not automatically your bill. Which client reads the config decides it, and for Claude Code so does how that client is configured on this machine — three environment variables and a per-server alwaysLoad pin, which audit reads rather than assumes, from the shell it runs in and from the env block of Claude Code's own settings files. It also reads what an organisation deploys: the managed MCP file that takes exclusive control of the server list where it exists, and the allow/deny lists that filter what loads — a clean deny is applied to the session's claims, an allowlist is reported and never subtracted. No other client's posture is readable from a file this opens, so for those the report gives what the vendor is on record with, or says there is nothing on record, and claims neither as a measurement.

The model is written down once, on the methodology page, because it gets corrected often and a second copy here would be a second place to correct: METHODOLOGY §who pays the number carries every variable and what it resolves to, the four kinds of first-party record the rule admits, the questions the report refuses to answer rather than guess, and the address and date behind every claim. What follows is what that model looks like when you run it.

On a machine where none of them is set, the same stack reads:

  7,901 tokens of tool schemas — 4.0% of a 200,000-token context window.
  claude-code defers every MCP tool definition (tool search), with no threshold —
  ENABLE_TOOL_SEARCH is unset here, which is the documented default. These tokens are NOT loaded
  up front at any size; they load when the model reaches for a tool. Size
  decides nothing here, so none of the arithmetic above changes the answer.
  Where this was read — Claude Code takes these variables from the shell it
  starts in and from the env block of its own settings files:
    this shell — sets none of them
    4 other settings file(s) it reads are not on this machine
  The full number is paid where deferral does not apply:
    a Microsoft Foundry deployment hosted on Azure, which rejects tool search server-side
    Google Cloud's Agent Platform on a model earlier than the Claude 4.5 generation
    a model without support for tool_reference blocks (before Sonnet 4.5 / Haiku 4.5 / Opus 4.5)
    a tool whose _meta carries "anthropic/alwaysLoad": true, which this audit does not read from a capture

Do not take that model on trust — your own client will tell you. All of it is read from Anthropic's documentation, and documentation about someone else's product is exactly the kind of claim this project refuses to leave unchecked elsewhere. Claude Code writes its own decision to a debug log, before it sends anything, so you can check your machine rather than believe this page. In a directory with an .mcp.json:

claude --debug-file /tmp/cc.txt -p "ok"
grep -E 'ToolSearch|Dynamic tool loading|Auto tool search' /tmp/cc.txt

Three line shapes answer three different questions:

the line what it tells you
[ToolSearch:optimistic] mode=…, ENABLE_TOOL_SEARCH=…, result=… which mode it picked at startup, and the value it read. Optimistic is its own word for a guess — it can be revised below
Dynamic tool loading: 0/N deferred tools included the one that settles it: how many of the N deferrable tools went into the request. 0/N is deferral actually happening
[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=… is not a first-party Anthropic host the base-URL fallback, firing, in the client's own words

Read the later requests, not the first. A stdio server can finish connecting after the first request has already gone, so an early low count is a race rather than a finding.

The mode most likely to surprise you is auto. It reads as the careful setting and it is the one that loads everything up front at ordinary sizes: below the threshold it does not defer, by design, and a line reading Auto tool search disabled: … (threshold: …) is that decision being made. The threshold is a percentage of the context window, so a model with a larger window has a proportionally larger one — pass --context to audit to compare against the window you actually run.

None of this is free, and it is one trivial request. These are Claude Code's own debug lines rather than a documented interface, so they can change; the methodology page is what this project holds to a dated re-read. No other client discovered by audit writes anything comparable, which is why no other row here is a measurement: three of them get their vendor's own record, dated and addressed, and the rest an absence of one.

Set ENABLE_TOOL_SEARCH=false in that shell and the same config reports the opposite — loads every tool definition up front here, naming the variable and the place it was read from. Deferring is also not free: what a deferring client does load at session start — tool names plus the server's instructions — is measured per server and published in the leaderboard's session start column, and for at least one server in the published set it costs more than loading the definitions would.

In CI, make it a gate — the bundlesize move for agents:

npx -y mcp-context-cost audit --config .mcp.json --budget 20000
# exits 1 when the stack exceeds the budget, so a PR adding a 25K-token server fails

The budget is denominated in sessions, because a context window belongs to one session: for claude-code that is the files one session loads together — both its configs, or the managed file alone where one is deployed, minus anything deniedMcpServers removes — and for every other client one file is one session. Per-file totals are still reported as file facts; the gate reads the costliest session. The baseline diff below stays per file, because a baseline pins a file artifact.

The budget is an absolute ceiling. What a reviewer actually wants to know is what this pull request did, so record a baseline and diff against it:

npx -y mcp-context-cost audit --config .mcp.json --json > baseline.json          # on main
npx -y mcp-context-cost audit --config .mcp.json --baseline baseline.json --max-increase 2000
diff vs baseline measured 2026-08-18T01:51:49.555Z (methodology 1.0)

  .mcp.json
    2,378  →  5,201   +2,823

    added             filesystem          — →     2,823  +2,823
    (1 server unchanged)

    This change adds 2,823 tokens to every request in this client — 1.2% → 2.6% of a
    200,000-token context window.

INCREASE FAIL:
  .mcp.json: +2,823 tokens per request, over the 2,000 allowed

Version note. --baseline and --max-increase shipped in 0.4.0 (published 2026-08-18), so the command above gates on npx -y mcp-context-cost@latest. Pinning to 0.3.0 or earlier does not gate, and fails quietly: those builds ignore flags they do not recognise, so the same command produces a plain audit and exit 0 — a passing CI check on a gate that never ran. 0.4.0 rejects unknown flags with exit 2 instead. Pin at or above 0.4.0, or do not pin.

A baseline is just a stored audit --json report, so any artifact store works. Without --max-increase the diff is informational and the exit code is unchanged.

examples/github-actions.yml is the whole thing as a workflow: measure the base branch, measure the PR, fail on the difference.

--max-increase fails on more than the number — it also fails whenever the increase could not be established. A server that measured yesterday and won't start today takes its tokens out of the total in exactly the way uninstalling it would, and reporting that as a saving is the one mistake this tool must not make. So a server that crossed the measured/unmeasured line, a config with no baseline, or a baseline config this run never found each fail the gate and name themselves:

    Not a clean comparison: a server changed measured-ness between the two runs.
    The measured total moved −2,378, but that is not what your config did.

      memory: measured 2,378 in the baseline and could not be measured now — its cost is
              missing from the total, not gone from your config
      → true cost is at least 2,378 higher than the 0 measured now.

INCREASE FAIL:
  .mcp.json: a server changed measured-ness, so the change could not be established exactly

Add --claude to annotate each server with its Anthropic-request cost from the published Claude divergence run — an exact number when the published capture hash matches what you have installed, (silence, not a stale guess) when it doesn't. The run holds 87 rows — the measured servers it covered when it last ran — and results/leaderboard.md prints a claude number for the 86 that still match today and silence for the rest. Most installs will show a mix:

  server               tools  tokens   share   claude
  my-server               18   9,400   79.8%    3,120
  my-fork                  6   2,380   20.2%       —

Add --suggest to place each of your tools in the measured set's tool-shape distribution and get advice only where the data can point at something. What draws a suggestion, what never does, and the percentile that decides are in METHODOLOGY §tool shape:

  suggest — descriptions at or above the 90th percentile of measured tools
  (baseline 2026-09-09: 1,433 tools across 87 measured servers):
    stub · wordy — 345 tokens: description 321 (p92), schema 14
      rewriting the description toward the measured median (27) would recover ≈294 tokens on every request
    1 of 2 tools sit inside the distribution — no advice where nothing is measurably unusual.

A config where nothing is out of distribution is told that in those words, and a baseline that cannot be fetched is a named problem, never a silently skipped check.

Add --changed to ask the other question — did the servers I already have get heavier? Each installed server is identified against the published capture history by its canonical hash, never by its name, because the name in your config is a label you chose and the bytes are not:

  changed — published versions of your servers that have moved since
  (index 2026-09-05, 99 published captures; matched by canonical hash, never by name):
    notes (published as my-server) — you have the capture published 2026-08-19 at 1,132 tokens;
      the current one is 2,062 (+930, 2026-08-26)
    updating all 1 would add 930 tokens to every request in this client.

A server whose bytes match no published capture — a version never measured here, a fork, a pin — is reported as unidentified with nothing claimed about it. Method: capture index.

Flags: --json (full report on stdout, progress on stderr), --budget N, --baseline <report.json>, --max-increase N, --context N (default 200,000), --timeout ms, --concurrency N, --docker, --claude, --suggest, --changed.

Where the numbers come from

The number audit gives you is the same measurement, run across a curated set of public servers — which is how you can tell it is a measurement and not this tool's opinion. It also shows what you are choosing between: across the 87 servers measured, cost spans 1,700× on the wire, from postgres at 32 tokens to github at 54,622 — of which a request carries 10,735, and Claude counts those at 18,728. The table below is a sample of that range, ranked on the wire like every other list here; the full range is in results/leaderboard.md.

server context cost (wire) mapped on Claude tools
github (official) 54,622 tokens 10,735 18,728 44
xcodebuildmcp 26,594 2,676 5,335 24
brave-search 25,487 8,278 13,762 8
notion 17,500 17,163 33,560 24
playwright (4.8M installs/week) 4,024 3,402 6,172 24
filesystem (reference) 2,823 1,665 3,115 14
markitdown 64 64 404 1

(87 of 107 popular servers measured, each row dated by its own most recent sweep — full table in results/leaderboard.md; every failure is listed with its reason. Each measured server also has a detail page showing which tools its tokens are in.)

Because the set is re-measured on a rotation and most entries launch unpinned, the same data answers a question no client asks: what did this server cost last month? results/regressions.md reports each server's most recent movement — dated to when it happened, separated into shipped more tools versus same tools, rewritten, and compared only within one isolation. The ecosystem ratchets upward: of the servers whose cost has moved at all, 15 moved up against 5 that moved down. Method: cost movement.

If you publish a server, the same measurement is available as a badge, so your users can see the cost before they install rather than after:

[context cost | 12,430 tokens]   ← shields.io badge, linked to the methodology

What it costs on Claude

The badge counts every byte a server returns. An Anthropic request carries only name, description, and input_schema — and counts them with a denser tokenizer. Both effects are now measured against a pinned model and published beside the badge, and they do not cancel. The middle column is what a request actually carries, counted with the badge's own tokenizer, so the two effects are separated rather than folded together:

server badge (o200k) mapped Claude (claude-opus-5)
github 54,622 10,735 18,728 78% of the capture is icons metadata Claude never sees
notion 17,500 17,163 33,560 almost no metadata to drop, so the tokenizer difference dominates

So the heaviest server on the badge is not the heaviest server on Claude. Per-server breakdowns are on each detail page; the method is Claude divergence.

Why trust the number?

Every published number is backed by a measurement.json. What it holds, and the five lines that re-derive the number from it, are in METHODOLOGY §reproduce it. Disputes reduce to a byte-level diff, and the CLI does that for you:

npx -y mcp-context-cost verify results/github/measurement.json
# OK github-mcp-server: 54622 tokens (o200k_base, methodology 1.0) — capture, hash, and count all agree

# or point it at a published measurement.json directly, no clone required
npx -y mcp-context-cost verify --remote https://raw.githubusercontent.com/athakur3/mcp-context-cost/main/results/github/measurement.json

Add --json for scripting ({ ok, serverName, rederivedTokens, rederivedSha, problems, badge } on stdout, badge omitted on failure). Exit codes: 0 ok, 1 verification/measurement failed, 2 usage error.

audit runs that same code path on your own machine and reports each server's canonicalSha256 in --json, so you can check that the version you installed is byte-identical to the one that was published — which is exactly what --claude uses to decide whether it is allowed to show you a number.

The number is also cross-checked against the other CLI that measures this, sd2k/mcp-tokens: the leaderboard's mcp-tokens column publishes its count beside ours wherever both tools saw the same tool set — same o200k encoding, differences documented, the divergence published rather than left to be discovered. Method: CLI cross-check.

Full definition: docs/METHODOLOGY.md — what is counted, what the number is not, config policy, failure taxonomy, frozen color bands, known divergences.

What's in the repo

path what
src/core/ the measurement spec, executable — canonical form, tokenizer, bands, badge JSON
src/sweep/ raw-wire MCP stdio client + Dockerized batch sweep + leaderboard/dashboard generators
src/audit/ client-config discovery (10 clients; JSON with comments, TOML, YAML), the remote probe, the per-stack report, and the baseline diff
src/cli.ts audit (measure your own stack), verify (re-derive any published number), measure
spec/fixtures/ golden vectors shared by the TypeScript and bash implementations
tools/ the scripts that call a network API — the Claude divergence run, the registry scan — kept out of the package so the library stays offline
upstream/ badge.sh + bash tests — the self-serve badge recipe, carried here
servers.yaml 107 curated candidates with live install metrics and provenance
results/ · badges/ measurements, leaderboard, history series, shields endpoint JSONs
docs/ methodology, dashboard, and a generated page per measured server

Measure your own server

npm ci
npm run sweep -- --no-persist --name my-server --command "npx -y my-mcp-server"

That prints the number and writes nothing. Published records (results/<name>/measurement.json, badges/<name>.json, the history.csv row) come from CI: a developer machine is a different architecture under different load, and a measurement taken there describes it rather than the server. Which machine a number applies to, and the failed record that made it a rule, are in METHODOLOGY §which machine a number applies to. To get your server into the leaderboard, add an entry to servers.yaml and open a pull request; the check on that PR measures the entry read-only, and the rotation publishes it after merge.

For a badge on your own README, run the published CLI in your server's own CI (the gate below writes results/my-server/measurement.json and badges/my-server.json into that repository), then in your README:

[![context cost](https://img.shields.io/endpoint?url=<raw URL of badges/my-server.json>)](<link target>)

Defend the number, don't just display it

A badge says what your server costs today; it does nothing about the release that adds 1,200 tokens to every user's context next month. Across the servers measured here most costs hold steady from sweep to sweep, but when a cost does move it usually moves up: the movement report has 15 servers ratcheting upward against 5 that got cheaper, and none of those maintainers had a check that would have said so first. measure takes the same gate flags audit does, so your own CI can be that check:

# on your default branch, once — commit the result
npx -y mcp-context-cost measure --name my-server --command "node dist/index.js"
cp results/my-server/measurement.json .context-cost/baseline.json

# on every pull request
npx -y mcp-context-cost measure --name my-server --command "node dist/index.js" \
  --baseline .context-cost/baseline.json --max-increase 100
diff vs baseline .context-cost/baseline.json
  my-server: 61 → 182  +121 tokens  (2 → 3 tools)
    added:   bulk_export (43)
    grew:    search 30 → 108 (+78)

INCREASE FAIL: +121 tokens, over the 100 allowed — this change adds that to every request of every install.

Both sides are single measurements carrying per-tool counts, so an established change is attributed exactly: which tools arrived, which grew, and by how much. And --max-increase fails on more than the number here too: a change that could not be established fails the gate, for the reason the audit gate gives above.

As a GitHub Action that whole workflow is the five lines at the top of this page (full example) — the same inputs, and the same outputs written whether the gate passed or not, for a later step to comment on the pull request or publish as a badge.

Point the link at the measurement behind the number — for servers in this sweep that is https://athakur3.github.io/mcp-context-cost/servers/<name>.html; otherwise the methodology page. A badge nobody can audit is decoration.

Status

Active. Every row carries the date of its own most recent measurement. Two weekly jobs re-measure the set — the memory reference server on Mondays, and a rotating third of the list on Wednesdays, so every row comes round within three weeks. Read each row's date as the date it means, and don't take the cadence on trust — the build history is public, one click each: re-sweep runs and self-badge runs. What changed, and why, is in CHANGELOG.md; contributions welcome, especially new servers.yaml entries — CONTRIBUTING.md says what an entry needs, the order of steps that leaves a pull request green, and where its number comes from.

MIT © 2026

from github.com/athakur3/mcp-context-cost

Install Context Cost in Claude Desktop, Claude Code & Cursor

Recommended · one command, every IDE
unyly install context-cost

Installs into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.

First time? Get the CLI: curl -fsSL https://unyly.org/install | sh

Or configure manually

Run in your terminal:

claude mcp add context-cost -- npx -y mcp-context-cost

Step-by-step: how to install Context Cost

FAQ

Is Context Cost MCP free?

Yes, Context Cost MCP is free — one-click install via Unyly at no cost.

Does Context Cost need an API key?

No, Context Cost runs without API keys or environment variables.

Is Context Cost hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Context Cost in Claude Desktop, Claude Code or Cursor?

Open Context Cost on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Context Cost with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All ai MCPs