mcp-context-cost 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -33
- package/dist/audit/audit.d.ts +48 -0
- package/dist/audit/audit.js +392 -7
- package/dist/audit/config.d.ts +46 -0
- package/dist/audit/config.js +86 -2
- package/dist/audit/deferral.d.ts +366 -0
- package/dist/audit/deferral.js +403 -0
- package/dist/audit/run.d.ts +22 -0
- package/dist/audit/run.js +17 -1
- package/dist/cli.js +11 -0
- package/dist/core/adoption.d.ts +226 -0
- package/dist/core/adoption.js +432 -0
- package/dist/core/canonical.d.ts +6 -0
- package/dist/core/canonical.js +3 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/session-start.d.ts +102 -0
- package/dist/core/session-start.js +186 -0
- package/dist/core/types.d.ts +8 -0
- package/dist/sweep/client.d.ts +6 -1
- package/dist/sweep/client.js +1 -0
- package/dist/sweep/dashboard.d.ts +10 -0
- package/dist/sweep/dashboard.js +32 -16
- package/dist/sweep/docker.d.ts +23 -0
- package/dist/sweep/docker.js +16 -12
- package/dist/sweep/harness-guard.d.ts +57 -0
- package/dist/sweep/harness-guard.js +144 -0
- package/dist/sweep/history.d.ts +33 -1
- package/dist/sweep/history.js +60 -5
- package/dist/sweep/regen.js +6 -1
- package/dist/sweep/report.d.ts +16 -0
- package/dist/sweep/report.js +79 -5
- package/dist/sweep/run.d.ts +41 -0
- package/dist/sweep/run.js +129 -36
- package/dist/sweep/server-pages.js +31 -6
- package/dist/sweep/session-start.d.ts +3 -0
- package/dist/sweep/session-start.js +103 -0
- package/dist/sweep/shard.d.ts +41 -0
- package/dist/sweep/shard.js +58 -0
- package/dist/sweep/sweep-all.js +56 -2
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -24,8 +24,10 @@ claude-desktop ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
|
24
24
|
────────────────────────────────────────────
|
|
25
25
|
total 37 7,901
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
7,901 tokens of tool schemas — 4.0% of a 200,000-token context window.
|
|
28
|
+
No default deferral is on record for claude-desktop, so every request
|
|
29
|
+
carries these tokens before you type anything — an absence of a record
|
|
30
|
+
about the client, not a measurement of it.
|
|
29
31
|
|
|
30
32
|
heaviest tools
|
|
31
33
|
sequential-thinking · sequentialthinking 990
|
|
@@ -46,11 +48,71 @@ Totals are reported per config file, never merged: a context window belongs to o
|
|
|
46
48
|
session, so summing Cursor's servers into Claude Desktop's total would describe a session
|
|
47
49
|
nobody runs.
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
### Where this cost is paid in full, and where it is deferred away
|
|
52
|
+
|
|
53
|
+
Not every client puts every tool definition in context on every request, so the total above
|
|
54
|
+
is not automatically your bill. Which client reads the config, and how that client is
|
|
55
|
+
configured **on this machine**, decides it — and `audit` reads that rather than assuming it.
|
|
56
|
+
|
|
57
|
+
**Clients with no default deferral on record** — Claude Desktop, Cursor, VS Code, Windsurf.
|
|
58
|
+
The total is what every request carries, as in the example above. That sentence is an
|
|
59
|
+
absence of a record about those clients, not a measurement of them, and the report says so
|
|
60
|
+
in those words.
|
|
61
|
+
|
|
62
|
+
**Claude Code defers MCP tool definitions by default** (its **tool search**): they are not
|
|
63
|
+
in context at session start, and load when the model reaches for one. Three variables move
|
|
64
|
+
that, and `audit` reads all three — from the shell it runs in *and* from the `env` block of
|
|
65
|
+
Claude Code's own settings files (managed, `<cwd>/.claude/settings.local.json`,
|
|
66
|
+
`<cwd>/.claude/settings.json`, `~/.claude/settings.json`), because a machine that switched
|
|
67
|
+
deferral off in a settings file is not a machine running the default:
|
|
68
|
+
|
|
69
|
+
| setting | what the audit reports |
|
|
70
|
+
|---|---|
|
|
71
|
+
| nothing set (the default) | every definition deferred, at any size — no threshold applies |
|
|
72
|
+
| `ENABLE_TOOL_SEARCH=true` | same: every definition deferred |
|
|
73
|
+
| `ENABLE_TOOL_SEARCH=false` | deferral off — every request carries the full total |
|
|
74
|
+
| `ENABLE_TOOL_SEARCH=auto` / `auto:N` | deferred only once definitions reach 10% / N% of the context window |
|
|
75
|
+
| `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` set | tool search off — read first, because `ENABLE_TOOL_SEARCH` cannot override it |
|
|
76
|
+
| `ANTHROPIC_BASE_URL` off `api.anthropic.com` | falls back to loading up front — consulted only while `ENABLE_TOOL_SEARCH` is unset |
|
|
77
|
+
| anything else in `ENABLE_TOOL_SEARCH` | not a documented value, so nothing is claimed from it |
|
|
78
|
+
|
|
79
|
+
On a machine where none of them is set, the same stack reads:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
7,901 tokens of tool schemas — 4.0% of a 200,000-token context window.
|
|
83
|
+
claude-code defers every MCP tool definition (tool search), with no threshold —
|
|
84
|
+
ENABLE_TOOL_SEARCH is unset here, which is the documented default. These tokens are NOT loaded
|
|
85
|
+
up front at any size; they load when the model reaches for a tool. Size
|
|
86
|
+
decides nothing here, so none of the arithmetic above changes the answer.
|
|
87
|
+
Where this was read — Claude Code takes these variables from the shell it
|
|
88
|
+
starts in and from the env block of its own settings files:
|
|
89
|
+
this shell — sets none of them
|
|
90
|
+
4 other settings file(s) it reads are not on this machine
|
|
91
|
+
The full number is paid where deferral does not apply:
|
|
92
|
+
a Microsoft Foundry deployment hosted on Azure, which rejects tool search server-side
|
|
93
|
+
Google Cloud's Agent Platform on a model earlier than the Claude 4.5 generation
|
|
94
|
+
a model without support for tool_reference blocks (before Sonnet 4.5 / Haiku 4.5 / Opus 4.5)
|
|
95
|
+
a server pinned with "alwaysLoad": true, whose tools load at session start regardless
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Set `ENABLE_TOOL_SEARCH=false` in that shell and the same config reports the opposite —
|
|
99
|
+
`loads every tool definition up front here`, naming the variable and the place it was read
|
|
100
|
+
from. Deferring is also not free: what a deferring client *does* load at session start —
|
|
101
|
+
tool names plus the server's `instructions` — is measured per server and published in the
|
|
102
|
+
leaderboard's `session start` column, and for at least one server in the published set it
|
|
103
|
+
costs **more** than loading the definitions would.
|
|
104
|
+
|
|
105
|
+
Three things the report will not do: it will not convert between units silently (in
|
|
106
|
+
threshold mode the stack is compared as a range, because the audit counts wire bytes and the
|
|
107
|
+
threshold is counted in what the client sends to the API — measured at 0.20×–1.92× across 20
|
|
108
|
+
servers); it will not pick a winner when two places on the machine set the same variable to
|
|
109
|
+
different values, or when a settings file exists and cannot be read; and it will not pass an
|
|
110
|
+
absence of a record off as a measurement. The first two print as unanswered questions. The
|
|
111
|
+
third prints as an answer that names itself: for the four discovered clients with no default
|
|
112
|
+
on record — `claude-desktop`, `cursor`, `vscode`, `windsurf` — the tokens are counted as
|
|
113
|
+
loaded up front, and the report says so in those words, "an absence of a record about the
|
|
114
|
+
client, not a measurement of it".
|
|
115
|
+
Full model, sources and dates: [METHODOLOGY §who pays the number](docs/METHODOLOGY.md#who-pays).
|
|
54
116
|
|
|
55
117
|
**In CI**, make it a gate — the bundlesize move for agents:
|
|
56
118
|
|
|
@@ -83,12 +145,12 @@ INCREASE FAIL:
|
|
|
83
145
|
.mcp.json: +2,823 tokens per request, over the 2,000 allowed
|
|
84
146
|
```
|
|
85
147
|
|
|
86
|
-
> **Version note.** `--baseline` and `--max-increase`
|
|
87
|
-
>
|
|
88
|
-
>
|
|
89
|
-
>
|
|
90
|
-
>
|
|
91
|
-
>
|
|
148
|
+
> **Version note.** `--baseline` and `--max-increase` shipped in **0.4.0** (published
|
|
149
|
+
> 2026-08-18), so the command above gates on
|
|
150
|
+
> `npx -y mcp-context-cost@latest`. Pinning to **0.3.0 or earlier** does not gate, and fails
|
|
151
|
+
> quietly: those builds ignore flags they do not recognise, so the same command produces a
|
|
152
|
+
> plain audit and **exit 0** — a passing CI check on a gate that never ran. 0.4.0 rejects
|
|
153
|
+
> unknown flags with exit 2 instead. Pin at or above 0.4.0, or do not pin.
|
|
92
154
|
|
|
93
155
|
A baseline is just a stored `audit --json` report, so any artifact store works. Without
|
|
94
156
|
`--max-increase` the diff is informational and the exit code is unchanged.
|
|
@@ -118,8 +180,10 @@ INCREASE FAIL:
|
|
|
118
180
|
Add `--claude` to annotate each server with its Anthropic-request cost from the published
|
|
119
181
|
[Claude divergence](docs/METHODOLOGY.md#claude-divergence) run — an exact number when the
|
|
120
182
|
published capture hash matches what you have installed, `—` (silence, not a stale guess)
|
|
121
|
-
when it doesn't
|
|
122
|
-
|
|
183
|
+
when it doesn't. The run holds 20 rows — the top 20 measured servers by tokens; 19 of them
|
|
184
|
+
still match the capture on disk here, which is why
|
|
185
|
+
[results/leaderboard.md](results/leaderboard.md) prints a claude number for 19 and leaves the
|
|
186
|
+
twentieth blank rather than stale. Most installs will show a mix:
|
|
123
187
|
|
|
124
188
|
```
|
|
125
189
|
server tools tokens share claude
|
|
@@ -135,21 +199,22 @@ Flags: `--json` (full report on stdout, progress on stderr), `--budget N`,
|
|
|
135
199
|
|
|
136
200
|
The number `audit` gives you is the same measurement, run across a curated set of public
|
|
137
201
|
servers — which is how you can tell it is a measurement and not this tool's opinion. It also
|
|
138
|
-
shows what you are choosing between: across the
|
|
139
|
-
from the 32-token `postgres` reference server to github's 54,422. The table below
|
|
140
|
-
|
|
202
|
+
shows what you are choosing between: across the 69 servers measured, cost spans **1,700×**,
|
|
203
|
+
from the 32-token `postgres` reference server to github's 54,422. The table below is a
|
|
204
|
+
sample of that range; the full range is in
|
|
141
205
|
[results/leaderboard.md](results/leaderboard.md).
|
|
142
206
|
|
|
143
207
|
| server | context cost | tools |
|
|
144
208
|
|---|---:|---:|
|
|
145
209
|
| github (official) | **54,422 tokens** | 44 |
|
|
210
|
+
| xcodebuildmcp | 26,594 | 24 |
|
|
146
211
|
| brave-search | 25,456 | 8 |
|
|
147
212
|
| notion | 17,500 | 24 |
|
|
148
213
|
| playwright *(4.8M installs/week)* | 4,024 | 24 |
|
|
149
214
|
| filesystem (reference) | 2,823 | 14 |
|
|
150
215
|
| markitdown | 64 | 1 |
|
|
151
216
|
|
|
152
|
-
*(
|
|
217
|
+
*(69 of 82 popular servers measured, sweeps of 2026-08-18 and 2026-08-19 — full table in
|
|
153
218
|
[results/leaderboard.md](results/leaderboard.md); every failure is listed with its reason.
|
|
154
219
|
Each measured server also has a [detail page](https://athakur3.github.io/mcp-context-cost/servers/)
|
|
155
220
|
showing which tools its tokens are in.)*
|
|
@@ -212,7 +277,7 @@ number is *not*, config policy, failure taxonomy, frozen color bands, known dive
|
|
|
212
277
|
| `src/cli.ts` | `audit` (measure your own stack), `verify` (re-derive any published number), `measure` |
|
|
213
278
|
| `spec/fixtures/` | golden vectors shared by the TypeScript and bash implementations |
|
|
214
279
|
| `tools/` | the one script that calls a network API (Claude divergence); kept out of the package so the library stays offline |
|
|
215
|
-
| `upstream/` |
|
|
280
|
+
| `upstream/` | `badge.sh` + composite-action patch + bash tests — the self-serve badge recipe, carried here |
|
|
216
281
|
| `servers.yaml` | 82 curated candidates with live install metrics and provenance |
|
|
217
282
|
| `results/` · `badges/` | measurements, leaderboard, history series, shields endpoint JSONs |
|
|
218
283
|
| `docs/` | methodology, dashboard, and a generated page per measured server |
|
|
@@ -235,32 +300,35 @@ Point the link at the measurement behind the number — for servers in this swee
|
|
|
235
300
|
`https://athakur3.github.io/mcp-context-cost/servers/<name>.html`; otherwise the
|
|
236
301
|
methodology page. A badge nobody can audit is decoration.
|
|
237
302
|
|
|
238
|
-
|
|
239
|
-
[
|
|
303
|
+
How many projects outside this repository actually display it is a dated reading rather
|
|
304
|
+
than a guess — [docs/adoption.md](docs/adoption.md), regenerated by `npm run adoption`,
|
|
305
|
+
which publishes the queries it ran and every file it examined. A zero there means the
|
|
306
|
+
search ran and found none; if it could not run, it says that instead of publishing a zero.
|
|
240
307
|
|
|
241
308
|
## Development
|
|
242
309
|
|
|
243
310
|
```bash
|
|
244
|
-
npm test #
|
|
311
|
+
npm test # TS suite incl. golden fixtures + dispute drills
|
|
245
312
|
npx tsc --noEmit # typecheck
|
|
246
|
-
./upstream/tests/badge-test.sh #
|
|
313
|
+
./upstream/tests/badge-test.sh # bash suite — byte-identical to the TS reference
|
|
247
314
|
npm run sweep:all -- --docker # full curated sweep (Docker isolation)
|
|
248
315
|
```
|
|
249
316
|
|
|
250
|
-
Notable engineering choices: the MCP client is a deliberate ~
|
|
317
|
+
Notable engineering choices: the MCP client is a deliberate ~220-line raw-wire
|
|
251
318
|
implementation (SDK schema-parsing can reorder keys, which would corrupt canonical bytes);
|
|
252
319
|
sweep servers run in credential-free Docker containers with recorded isolation; the badge
|
|
253
320
|
color bands are frozen against the observed distribution of the first full sweep.
|
|
254
321
|
|
|
255
322
|
## Status
|
|
256
323
|
|
|
257
|
-
Active.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
the
|
|
261
|
-
|
|
262
|
-
[
|
|
263
|
-
self-
|
|
264
|
-
contributions welcome, especially new
|
|
324
|
+
Active. 46 of the 69 numbers come from the sweep of 2026-08-19 and 23 from 2026-08-18. Two
|
|
325
|
+
weekly jobs re-measure the set — the `memory` reference server on Mondays, and a rotating
|
|
326
|
+
sixth of the list on Wednesdays, so every row comes round within six weeks. Read each row's
|
|
327
|
+
date as the date it means, and don't take the cadence on trust — the build history is
|
|
328
|
+
public, one click each:
|
|
329
|
+
[re-sweep runs](https://github.com/athakur3/mcp-context-cost/actions/workflows/resweep.yml)
|
|
330
|
+
and [self-badge runs](https://github.com/athakur3/mcp-context-cost/actions/workflows/self-badge.yml).
|
|
331
|
+
See [ROADMAP.md](ROADMAP.md) for what's next — contributions welcome, especially new
|
|
332
|
+
`servers.yaml` entries.
|
|
265
333
|
|
|
266
334
|
MIT © 2026
|
package/dist/audit/audit.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type DivergenceRun } from '../core/divergence.js';
|
|
2
2
|
import type { Measurement, MeasurementStatus, ToolMeasurement } from '../core/types.js';
|
|
3
3
|
import type { ConfiguredServer, LoadedConfig } from './config.js';
|
|
4
|
+
import { type DeferralVerdict, type ToolSearchEnv, type ToolSearchSource } from './deferral.js';
|
|
4
5
|
import { type AuditDiff, type IncreaseGate } from './diff.js';
|
|
5
6
|
export declare const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
6
7
|
export type AuditStatus = MeasurementStatus | 'remote-not-measurable';
|
|
@@ -53,6 +54,16 @@ export interface AuditConfigResult {
|
|
|
53
54
|
skipped: AuditServerResult[];
|
|
54
55
|
heaviestTools: HeaviestTool[];
|
|
55
56
|
trimAdvice: TrimAdvice | null;
|
|
57
|
+
/**
|
|
58
|
+
* Whether this client loads the total up front or defers it, and — when the
|
|
59
|
+
* client decides that by a threshold — which side of it this stack is on.
|
|
60
|
+
* Every config carries one: the answer "no deferral is on record for this
|
|
61
|
+
* client" is a reading, not a gap.
|
|
62
|
+
*
|
|
63
|
+
* Configs that one session loads together share a single verdict object, so
|
|
64
|
+
* `deferral.sources` can name more files than this config's own `source`.
|
|
65
|
+
*/
|
|
66
|
+
deferral: DeferralVerdict;
|
|
56
67
|
}
|
|
57
68
|
export interface BudgetFitStep {
|
|
58
69
|
name: string;
|
|
@@ -89,6 +100,16 @@ export interface AuditReport {
|
|
|
89
100
|
generatedAt: string;
|
|
90
101
|
contextWindow: number;
|
|
91
102
|
configs: AuditConfigResult[];
|
|
103
|
+
/**
|
|
104
|
+
* Client configs that were read and parsed but declare no servers. They get
|
|
105
|
+
* no report line — there is nothing to total — but they are the record that a
|
|
106
|
+
* client is installed here, which is not the same machine as one with no
|
|
107
|
+
* client at all.
|
|
108
|
+
*/
|
|
109
|
+
emptyConfigs: {
|
|
110
|
+
client: string;
|
|
111
|
+
source: string;
|
|
112
|
+
}[];
|
|
92
113
|
budget?: {
|
|
93
114
|
limit: number;
|
|
94
115
|
worstTotal: number;
|
|
@@ -110,6 +131,19 @@ export interface AuditReport {
|
|
|
110
131
|
}
|
|
111
132
|
/** Cache key for measurement reuse: the exact argv two configs would spawn. */
|
|
112
133
|
export declare function serverKey(s: ConfiguredServer): string;
|
|
134
|
+
/**
|
|
135
|
+
* The measurement keys that stand for more than one distinct server.
|
|
136
|
+
*
|
|
137
|
+
* `serverKey` is the argv alone, so two entries running the same command under
|
|
138
|
+
* different environments are measured once and both are given that one number.
|
|
139
|
+
* Environment decides what a server serves — `GITHUB_TOOLSETS` on
|
|
140
|
+
* `github-mcp-server` selects which toolsets it lists — so for entries under one
|
|
141
|
+
* of these keys, the number reported is one entry's, not each one's.
|
|
142
|
+
*
|
|
143
|
+
* Same argv AND same environment is not collapsed: two clients pointing at an
|
|
144
|
+
* identical server are one measurement, which is the reuse this key is for.
|
|
145
|
+
*/
|
|
146
|
+
export declare function collapsedKeys(configs: LoadedConfig[]): Set<string>;
|
|
113
147
|
/**
|
|
114
148
|
* Assemble the report from configs + measurements. Pure: `runAudit` does the
|
|
115
149
|
* spawning, this does the arithmetic, so totals and shares are testable without
|
|
@@ -121,6 +155,20 @@ export declare function buildReport(configs: LoadedConfig[], measured: Map<strin
|
|
|
121
155
|
generatedAt?: string;
|
|
122
156
|
/** Published `tools-delta/v1` run to join against (`--claude`); omit to skip the join. */
|
|
123
157
|
divergence?: DivergenceRun | null;
|
|
158
|
+
/**
|
|
159
|
+
* The audited machine's SHELL tool-search variables. Passed in rather than
|
|
160
|
+
* read here so this stays pure and a report is reproducible from its
|
|
161
|
+
* inputs; `runAudit` supplies the real environment. Omitted means the shell
|
|
162
|
+
* set nothing.
|
|
163
|
+
*/
|
|
164
|
+
env?: ToolSearchEnv;
|
|
165
|
+
/**
|
|
166
|
+
* The other place those variables come from: Claude Code's own settings
|
|
167
|
+
* files, highest precedence first, as `loadSettingsSources` read them.
|
|
168
|
+
* `runAudit` supplies these. Omitted means they were not read here — which
|
|
169
|
+
* the report says, rather than reporting a default it did not establish.
|
|
170
|
+
*/
|
|
171
|
+
settings?: ToolSearchSource[];
|
|
124
172
|
}): AuditReport;
|
|
125
173
|
/** Human output. JSON output is the report object itself. */
|
|
126
174
|
export declare function formatReport(report: AuditReport): string;
|