mcp-context-cost 0.4.0 → 0.5.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.
Files changed (40) hide show
  1. package/README.md +91 -25
  2. package/dist/audit/audit.d.ts +38 -0
  3. package/dist/audit/audit.js +372 -7
  4. package/dist/audit/config.d.ts +38 -0
  5. package/dist/audit/config.js +64 -0
  6. package/dist/audit/deferral.d.ts +346 -0
  7. package/dist/audit/deferral.js +376 -0
  8. package/dist/audit/run.d.ts +22 -0
  9. package/dist/audit/run.js +17 -1
  10. package/dist/core/adoption.d.ts +226 -0
  11. package/dist/core/adoption.js +432 -0
  12. package/dist/core/canonical.d.ts +6 -0
  13. package/dist/core/canonical.js +3 -0
  14. package/dist/core/index.d.ts +1 -0
  15. package/dist/core/index.js +1 -0
  16. package/dist/core/session-start.d.ts +102 -0
  17. package/dist/core/session-start.js +186 -0
  18. package/dist/core/types.d.ts +8 -0
  19. package/dist/sweep/client.d.ts +6 -1
  20. package/dist/sweep/client.js +1 -0
  21. package/dist/sweep/dashboard.d.ts +10 -0
  22. package/dist/sweep/dashboard.js +32 -16
  23. package/dist/sweep/docker.d.ts +23 -0
  24. package/dist/sweep/docker.js +16 -12
  25. package/dist/sweep/harness-guard.d.ts +57 -0
  26. package/dist/sweep/harness-guard.js +144 -0
  27. package/dist/sweep/history.d.ts +33 -1
  28. package/dist/sweep/history.js +60 -5
  29. package/dist/sweep/regen.js +6 -1
  30. package/dist/sweep/report.d.ts +16 -0
  31. package/dist/sweep/report.js +79 -5
  32. package/dist/sweep/run.d.ts +41 -0
  33. package/dist/sweep/run.js +129 -36
  34. package/dist/sweep/server-pages.js +31 -6
  35. package/dist/sweep/session-start.d.ts +3 -0
  36. package/dist/sweep/session-start.js +103 -0
  37. package/dist/sweep/shard.d.ts +41 -0
  38. package/dist/sweep/shard.js +58 -0
  39. package/dist/sweep/sweep-all.js +56 -2
  40. 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
- Every request in this client carries 7,901 tokens of tool schemas — 4.0% of a
28
- 200,000-token context window, before you type anything.
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
- One nuance: Claude Code's tool search (default-on in recent versions) defers full MCP
50
- schemas until used, loading only tool names at session start. Audit totals are the weight
51
- of the schema surface itself what loads upfront in clients without deferral (Claude
52
- Desktop, Cursor, VS Code, Windsurf today), and what Claude Code's documented fallback
53
- modes still load. Deferral-aware reporting is on the roadmap.
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, `.claude/settings.local.json`,
66
+ `.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` are **not in the published 0.3.0**
87
- > they are on `main` and ship in the next release. This matters more than a normal
88
- > unreleased-feature note: 0.3.0 ignores flags it does not recognise, so running the command
89
- > above against it produces a plain audit and **exit 0** a passing CI check on a gate that
90
- > never ran. Builds after 0.3.0 reject unknown flags with exit 2 instead. Until the next
91
- > release, pin the gate to a version that has it, or it is not gating anything.
148
+ > **Version note.** `--baseline` and `--max-increase` shipped in **0.4.0** (published
149
+ > 2026-08-18, and the current `latest`), 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,7 +180,7 @@ 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 (today the run covers the top 15 measured servers, so most installs will
183
+ when it doesn't (today the run covers the top 20 measured servers, so most installs will
122
184
  show a mix):
123
185
 
124
186
  ```
@@ -135,7 +197,7 @@ Flags: `--json` (full report on stdout, progress on stderr), `--budget N`,
135
197
 
136
198
  The number `audit` gives you is the same measurement, run across a curated set of public
137
199
  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 65 servers measured, cost spans **1,700×**,
200
+ shows what you are choosing between: across the 69 servers measured, cost spans **1,700×**,
139
201
  from the 32-token `postgres` reference server to github's 54,422. The table below starts at
140
202
  markitdown's 64 tokens, an 850× spread; the full range is in
141
203
  [results/leaderboard.md](results/leaderboard.md).
@@ -149,7 +211,7 @@ markitdown's 64 tokens, an 850× spread; the full range is in
149
211
  | filesystem (reference) | 2,823 | 14 |
150
212
  | markitdown | 64 | 1 |
151
213
 
152
- *(65 of 82 popular servers measured, 2026-08-18 sweep — full table in
214
+ *(69 of 82 popular servers measured, sweeps of 2026-08-18 and 2026-08-19 — full table in
153
215
  [results/leaderboard.md](results/leaderboard.md); every failure is listed with its reason.
154
216
  Each measured server also has a [detail page](https://athakur3.github.io/mcp-context-cost/servers/)
155
217
  showing which tools its tokens are in.)*
@@ -235,32 +297,36 @@ Point the link at the measurement behind the number — for servers in this swee
235
297
  `https://athakur3.github.io/mcp-context-cost/servers/<name>.html`; otherwise the
236
298
  methodology page. A badge nobody can audit is decoration.
237
299
 
300
+ How many projects outside this repository actually display it is a dated reading rather
301
+ than a guess — [docs/adoption.md](docs/adoption.md), regenerated by `npm run adoption`,
302
+ which publishes the queries it ran and every file it examined. A zero there means the
303
+ search ran and found none; if it could not run, it says that instead of publishing a zero.
304
+
238
305
  Or self-serve from CI via the (staged) mcp-tokens-action badge inputs — see
239
306
  [upstream/action-patch.md](upstream/action-patch.md).
240
307
 
241
308
  ## Development
242
309
 
243
310
  ```bash
244
- npm test # 158 TS tests incl. golden fixtures + dispute drills
311
+ npm test # 403 TS tests incl. golden fixtures + dispute drills
245
312
  npx tsc --noEmit # typecheck
246
313
  ./upstream/tests/badge-test.sh # 21 bash tests — 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 ~150-line raw-wire
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. 56 of the 65 numbers come from a single sweep on 2026-08-16, 3 from 2026-08-17, and
258
- 6 from 2026-08-18 (an upstream `mcp` package bump broke the old low-level-`Server` API these
259
- six relied on; pinning `mcp<2` in their launch commands fixed startup, not this project's code);
260
- the weekly job currently re-measures one server (`memory`), so treat the leaderboard as a
261
- dated snapshot rather than a live feed. Badge PRs are open across the ecosystem and
262
- [sd2k/mcp-tokens-action#5](https://github.com/sd2k/mcp-tokens-action/pull/5) proposes the
263
- self-serve badge path upstream. See [ROADMAP.md](ROADMAP.md) for what's next —
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 one reference server (`memory`) every Monday, and a rotating
326
+ sixth of the list every Wednesday, so the full set turns over in six weeks — but treat the
327
+ leaderboard as a dated snapshot rather than a live feed. Badge PRs are open across the
328
+ ecosystem and [sd2k/mcp-tokens-action#5](https://github.com/sd2k/mcp-tokens-action/pull/5)
329
+ proposes the self-serve badge path upstream. See [ROADMAP.md](ROADMAP.md) for what's next —
264
330
  contributions welcome, especially new `servers.yaml` entries.
265
331
 
266
332
  MIT © 2026
@@ -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;
@@ -110,6 +121,19 @@ export interface AuditReport {
110
121
  }
111
122
  /** Cache key for measurement reuse: the exact argv two configs would spawn. */
112
123
  export declare function serverKey(s: ConfiguredServer): string;
124
+ /**
125
+ * The measurement keys that stand for more than one distinct server.
126
+ *
127
+ * `serverKey` is the argv alone, so two entries running the same command under
128
+ * different environments are measured once and both are given that one number.
129
+ * Environment decides what a server serves — `GITHUB_TOOLSETS` on
130
+ * `github-mcp-server` selects which toolsets it lists — so for entries under one
131
+ * of these keys, the number reported is one entry's, not each one's.
132
+ *
133
+ * Same argv AND same environment is not collapsed: two clients pointing at an
134
+ * identical server are one measurement, which is the reuse this key is for.
135
+ */
136
+ export declare function collapsedKeys(configs: LoadedConfig[]): Set<string>;
113
137
  /**
114
138
  * Assemble the report from configs + measurements. Pure: `runAudit` does the
115
139
  * spawning, this does the arithmetic, so totals and shares are testable without
@@ -121,6 +145,20 @@ export declare function buildReport(configs: LoadedConfig[], measured: Map<strin
121
145
  generatedAt?: string;
122
146
  /** Published `tools-delta/v1` run to join against (`--claude`); omit to skip the join. */
123
147
  divergence?: DivergenceRun | null;
148
+ /**
149
+ * The audited machine's SHELL tool-search variables. Passed in rather than
150
+ * read here so this stays pure and a report is reproducible from its
151
+ * inputs; `runAudit` supplies the real environment. Omitted means the shell
152
+ * set nothing.
153
+ */
154
+ env?: ToolSearchEnv;
155
+ /**
156
+ * The other place those variables come from: Claude Code's own settings
157
+ * files, highest precedence first, as `loadSettingsSources` read them.
158
+ * `runAudit` supplies these. Omitted means they were not read here — which
159
+ * the report says, rather than reporting a default it did not establish.
160
+ */
161
+ settings?: ToolSearchSource[];
124
162
  }): AuditReport;
125
163
  /** Human output. JSON output is the report object itself. */
126
164
  export declare function formatReport(report: AuditReport): string;