mcp-context-cost 0.17.0 → 0.19.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 +99 -126
- package/dist/audit/audit.d.ts +25 -27
- package/dist/audit/audit.js +121 -24
- package/dist/audit/config.d.ts +39 -9
- package/dist/audit/config.js +65 -10
- package/dist/audit/deferral.d.ts +127 -24
- package/dist/audit/deferral.js +366 -34
- package/dist/audit/diff.js +35 -9
- package/dist/audit/remote.d.ts +19 -34
- package/dist/audit/remote.js +109 -16
- package/dist/audit/run.d.ts +18 -18
- package/dist/audit/run.js +30 -7
- package/dist/cli.js +38 -14
- package/dist/core/adoption.d.ts +162 -8
- package/dist/core/adoption.js +278 -25
- package/dist/core/bands.d.ts +4 -3
- package/dist/core/bands.js +4 -3
- package/dist/core/canonical.d.ts +12 -5
- package/dist/core/canonical.js +4 -0
- package/dist/core/capture-index.js +6 -1
- package/dist/core/cross-check.js +11 -3
- package/dist/core/format.d.ts +34 -0
- package/dist/core/format.js +37 -0
- package/dist/core/index.d.ts +39 -7
- package/dist/core/index.js +38 -7
- package/dist/core/protocol.d.ts +116 -0
- package/dist/core/protocol.js +119 -0
- package/dist/core/regression.js +7 -3
- package/dist/core/server-diff.d.ts +2 -2
- package/dist/core/server-diff.js +6 -3
- package/dist/core/session-start.d.ts +7 -50
- package/dist/core/session-start.js +7 -65
- package/dist/core/tool-shape.js +17 -5
- package/dist/core/types.d.ts +63 -16
- package/dist/sweep/client.d.ts +26 -4
- package/dist/sweep/client.js +61 -12
- package/dist/sweep/cross-check.d.ts +1 -2
- package/dist/sweep/cross-check.js +15 -15
- package/dist/sweep/dashboard.js +49 -18
- package/dist/sweep/docker.d.ts +8 -8
- package/dist/sweep/docker.js +1 -1
- package/dist/sweep/harness-guard.d.ts +13 -1
- package/dist/sweep/harness-guard.js +13 -1
- package/dist/sweep/history.js +10 -7
- package/dist/sweep/page-prose.d.ts +25 -0
- package/dist/sweep/page-prose.js +45 -0
- package/dist/sweep/pr-check.d.ts +16 -1
- package/dist/sweep/pr-check.js +18 -3
- package/dist/sweep/published-stats.d.ts +42 -12
- package/dist/sweep/published-stats.js +211 -80
- package/dist/sweep/regen.js +8 -10
- package/dist/sweep/registry-scan.js +28 -6
- package/dist/sweep/regressions.js +7 -8
- package/dist/sweep/report.d.ts +1 -5
- package/dist/sweep/report.js +21 -30
- package/dist/sweep/run.d.ts +57 -14
- package/dist/sweep/run.js +61 -1
- package/dist/sweep/server-pages.d.ts +8 -2
- package/dist/sweep/server-pages.js +51 -14
- package/dist/sweep/servers-schema.d.ts +1 -1
- package/dist/sweep/servers-schema.js +8 -4
- package/dist/sweep/sweep-all.js +2 -3
- package/dist/sweep/tool-shape.js +1 -1
- package/package.json +16 -3
- package/dist/sweep/session-start.d.ts +0 -3
- package/dist/sweep/session-start.js +0 -127
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
[](https://github.com/athakur3/mcp-context-cost/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/marketplace/actions/mcp-context-cost)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
cost than you meant to ship:
|
|
7
|
+
Three entry points: `audit`, for the config you run (below); a GitHub Action, for the server
|
|
8
|
+
you publish; and the measurement itself, as a library, for anything else. The Action is five
|
|
9
|
+
lines in a workflow that fail a pull request adding more context cost than you meant to ship:
|
|
10
10
|
|
|
11
11
|
```yaml
|
|
12
12
|
- uses: athakur3/mcp-context-cost@v1
|
|
@@ -24,12 +24,28 @@ gate passed or not, so a later step can comment the number on the PR or publish
|
|
|
24
24
|
[how the gate decides](#defend-the-number-dont-just-display-it) ·
|
|
25
25
|
[what the number is](docs/METHODOLOGY.md).
|
|
26
26
|
|
|
27
|
+
As a library, if you want the measurement without either wrapper — the same canonical form,
|
|
28
|
+
the same tokenizer, the same badge:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { measureTools, canonicalString, toBadge, METHODOLOGY_VERSION } from "mcp-context-cost";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
It is the definition rather than the harness: measurement and canonical bytes, the color
|
|
35
|
+
bands, the badge JSON, the Claude field-selection mapping, and the session-start load. It
|
|
36
|
+
does not launch servers or read your config — that is what `audit` is for. The surface is
|
|
37
|
+
pinned by a test, so a name is added or removed on purpose and says so in the changelog.
|
|
38
|
+
|
|
27
39
|
**What do the MCP servers in your config cost you before you type anything — and what did
|
|
28
40
|
that last config change add to every session you will ever run?**
|
|
29
41
|
|
|
30
|
-
Every MCP server you wire into an agent
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
Every MCP server you wire into an agent has to put its tool schemas somewhere. For a client
|
|
43
|
+
with no deferral on record that is the model's context, on every single request, whether or
|
|
44
|
+
not the agent ends up using the tools; for a client that defers them — Claude Code by default,
|
|
45
|
+
and Cursor, Codex CLI and VS Code by their vendors' own records — it is a smaller session-start
|
|
46
|
+
cost plus whatever the agent reaches for. Which of the two you are paying is a property of your
|
|
47
|
+
client, not of the server, and no client shows you either number. Point `audit` at your own MCP
|
|
48
|
+
config:
|
|
33
49
|
|
|
34
50
|
```bash
|
|
35
51
|
npx -y mcp-context-cost audit
|
|
@@ -65,16 +81,15 @@ VS Code (`.vscode/mcp.json`), Windsurf, Codex CLI (`~/.codex/config.toml`), Gemi
|
|
|
65
81
|
Goose (`~/.config/goose/config.yaml`) — or pass `--config <path>`. Servers are measured by
|
|
66
82
|
the same path as the published leaderboard (dual `tools/list` capture, `o200k_base` over
|
|
67
83
|
canonical JSON), so a server in both places gets the same number. Nothing is written to your
|
|
68
|
-
project, and
|
|
84
|
+
project, and no **value** an entry carries — an env var, a header — is ever read into the
|
|
85
|
+
output. Only their names are.
|
|
69
86
|
|
|
70
|
-
A remote entry — `url`, or the client's own spelling of it — is
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
reason. Header values an entry carries are sent and never printed — only their names are —
|
|
77
|
-
and nothing here ever opens a browser.
|
|
87
|
+
A remote entry — `url`, or the client's own spelling of it — is probed before anything is
|
|
88
|
+
launched, and reported as measured, **auth-walled** or **unreachable** depending on what it
|
|
89
|
+
answers; an auth-walled row makes the total above it a floor rather than a number. The probe,
|
|
90
|
+
what each verdict quotes, and why it exists at all are in
|
|
91
|
+
[METHODOLOGY §who pays the number](docs/METHODOLOGY.md#who-pays). Nothing here opens a
|
|
92
|
+
browser.
|
|
78
93
|
|
|
79
94
|
Totals are reported per config file, never merged: a context window belongs to one client
|
|
80
95
|
session, so summing Cursor's servers into Claude Desktop's total would describe a session
|
|
@@ -83,33 +98,19 @@ nobody runs.
|
|
|
83
98
|
### Where this cost is paid in full, and where it is deferred away
|
|
84
99
|
|
|
85
100
|
Not every client puts every tool definition in context on every request, so the total above
|
|
86
|
-
is not automatically your bill. Which client reads the config, and
|
|
87
|
-
configured **on this machine
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`<cwd>/.claude/settings.json`, `~/.claude/settings.json`), because a machine that switched
|
|
100
|
-
deferral off in a settings file is not a machine running the default:
|
|
101
|
-
|
|
102
|
-
| setting | what the audit reports |
|
|
103
|
-
|---|---|
|
|
104
|
-
| nothing set (the default) | every definition deferred, at any size — no threshold applies |
|
|
105
|
-
| `ENABLE_TOOL_SEARCH=true` | same: every definition deferred |
|
|
106
|
-
| `ENABLE_TOOL_SEARCH=false` | deferral off — every request carries the full total. In a settings `env` block that is the **string** `"false"`; the JSON boolean `false` is the last row, not this one |
|
|
107
|
-
| `ENABLE_TOOL_SEARCH=auto` / `auto:N` | deferred only once definitions reach 10% / N% of the context window |
|
|
108
|
-
| `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` set | tool search off — read first, because `ENABLE_TOOL_SEARCH` cannot override it |
|
|
109
|
-
| `ANTHROPIC_BASE_URL` off `api.anthropic.com` | falls back to loading up front — consulted only while `ENABLE_TOOL_SEARCH` is unset |
|
|
110
|
-
| anything else in `ENABLE_TOOL_SEARCH` | not a documented value, so nothing is claimed from it |
|
|
111
|
-
| any of the three set, in a settings `env` block, to something that is not a string — a JSON boolean, a number, `null` | it is set there and what it is set to is unknown, so no posture is claimed: the report says whether these tokens are deferred cannot be said from it |
|
|
112
|
-
| a server pinned `"alwaysLoad": true` in its entry | loads at session start whatever the setting says — read from the entry, named with its tokens, and left out of any threshold comparison |
|
|
101
|
+
is not automatically your bill. Which client reads the config decides it, and for Claude Code
|
|
102
|
+
so does how that client is configured **on this machine** — three environment variables and a
|
|
103
|
+
per-server `alwaysLoad` pin, which `audit` reads rather than assumes, from the shell it runs
|
|
104
|
+
in *and* from the `env` block of Claude Code's own settings files. No other client's posture
|
|
105
|
+
is readable from a file this opens, so for those the report gives what the vendor is on record
|
|
106
|
+
with, or says there is nothing on record, and claims neither as a measurement.
|
|
107
|
+
|
|
108
|
+
**The model is written down once, on the methodology page**, because it gets corrected often
|
|
109
|
+
and a second copy here would be a second place to correct:
|
|
110
|
+
[METHODOLOGY §who pays the number](docs/METHODOLOGY.md#who-pays) carries every variable and
|
|
111
|
+
what it resolves to, the four kinds of first-party record the rule admits, the questions the
|
|
112
|
+
report refuses to answer rather than guess, and the address and date behind every claim. What
|
|
113
|
+
follows is what that model looks like when you run it.
|
|
113
114
|
|
|
114
115
|
On a machine where none of them is set, the same stack reads:
|
|
115
116
|
|
|
@@ -130,7 +131,7 @@ On a machine where none of them is set, the same stack reads:
|
|
|
130
131
|
a tool whose _meta carries "anthropic/alwaysLoad": true, which this audit does not read from a capture
|
|
131
132
|
```
|
|
132
133
|
|
|
133
|
-
**Do not take that
|
|
134
|
+
**Do not take that model on trust — your own client will tell you.** All of it is read
|
|
134
135
|
from Anthropic's documentation, and documentation about someone else's product is exactly the
|
|
135
136
|
kind of claim this project refuses to leave unchecked elsewhere. Claude Code writes its own
|
|
136
137
|
decision to a debug log, before it sends anything, so you can check your machine rather than
|
|
@@ -147,7 +148,7 @@ Three line shapes answer three different questions:
|
|
|
147
148
|
|---|---|
|
|
148
149
|
| `[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 |
|
|
149
150
|
| `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 |
|
|
150
|
-
| `[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=… is not a first-party Anthropic host` | the fallback
|
|
151
|
+
| `[ToolSearch:optimistic] disabled: ANTHROPIC_BASE_URL=… is not a first-party Anthropic host` | the base-URL fallback, firing, in the client's own words |
|
|
151
152
|
|
|
152
153
|
Read the *later* requests, not the first. A stdio server can finish connecting after the first
|
|
153
154
|
request has already gone, so an early low count is a race rather than a finding.
|
|
@@ -160,9 +161,10 @@ a proportionally larger one — pass `--context` to `audit` to compare against t
|
|
|
160
161
|
actually run.
|
|
161
162
|
|
|
162
163
|
None of this is free, and it is one trivial request. These are Claude Code's own debug lines
|
|
163
|
-
rather than a documented interface, so they can change; the
|
|
164
|
-
holds to a dated re-read. No other client discovered by `audit` writes anything comparable,
|
|
165
|
-
which is why
|
|
164
|
+
rather than a documented interface, so they can change; the methodology page is what this
|
|
165
|
+
project holds to a dated re-read. No other client discovered by `audit` writes anything comparable,
|
|
166
|
+
which is why no other row here is a measurement: three of them get their vendor's own record,
|
|
167
|
+
dated and addressed, and the rest an absence of one.
|
|
166
168
|
|
|
167
169
|
Set `ENABLE_TOOL_SEARCH=false` in that shell and the same config reports the opposite —
|
|
168
170
|
`loads every tool definition up front here`, naming the variable and the place it was read
|
|
@@ -171,20 +173,6 @@ tool names plus the server's `instructions` — is measured per server and publi
|
|
|
171
173
|
leaderboard's `session start` column, and for at least one server in the published set it
|
|
172
174
|
costs **more** than loading the definitions would.
|
|
173
175
|
|
|
174
|
-
Three things the report will not do: it will not convert between units silently (in
|
|
175
|
-
threshold mode the stack is compared as a range, because the audit counts wire bytes and the
|
|
176
|
-
threshold is counted in what the client sends to the API — measured at 0.19×–1.93× across 86
|
|
177
|
-
servers); it will not claim a posture the machine did not state readably, which is four
|
|
178
|
-
refusals and not one — when two places set the same variable to different values, when a
|
|
179
|
-
settings file exists and cannot be read, when the place that would decide sets the variable
|
|
180
|
-
to something that is not a string, and when `ENABLE_TOOL_SEARCH` holds a value Claude Code
|
|
181
|
-
does not document; and it will not pass an absence of a record off as a measurement. The
|
|
182
|
-
first two print as unanswered questions. The third prints as an answer that names
|
|
183
|
-
itself: for the nine discovered clients with no default on record — `claude-desktop`, `cursor`, `vscode`, `windsurf`, `codex`, `gemini`, `zed`, `kiro`, `goose` — the tokens are counted as
|
|
184
|
-
loaded up front, and the report says so in those words, "an absence of a record about the
|
|
185
|
-
client, not a measurement of it".
|
|
186
|
-
Full model, sources and dates: [METHODOLOGY §who pays the number](docs/METHODOLOGY.md#who-pays).
|
|
187
|
-
|
|
188
176
|
**In CI**, make it a gate — the bundlesize move for agents:
|
|
189
177
|
|
|
190
178
|
```bash
|
|
@@ -257,19 +245,18 @@ still match today and silence for the rest. Most installs will show a mix:
|
|
|
257
245
|
|
|
258
246
|
```
|
|
259
247
|
server tools tokens share claude
|
|
260
|
-
|
|
261
|
-
|
|
248
|
+
my-server 18 9,400 79.8% 3,120
|
|
249
|
+
my-fork 6 2,380 20.2% —
|
|
262
250
|
```
|
|
263
251
|
|
|
264
|
-
Add `--suggest` to place each of your tools in the measured set's tool-shape distribution
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
1,430 measured tools:
|
|
252
|
+
Add `--suggest` to place each of your tools in the measured set's tool-shape distribution and
|
|
253
|
+
get advice only where the data can point at something. What draws a suggestion, what never
|
|
254
|
+
does, and the percentile that decides are in
|
|
255
|
+
[METHODOLOGY §tool shape](docs/METHODOLOGY.md#tool-shape):
|
|
269
256
|
|
|
270
257
|
```
|
|
271
258
|
suggest — descriptions at or above the 90th percentile of measured tools
|
|
272
|
-
(baseline 2026-09-
|
|
259
|
+
(baseline 2026-09-07: 1,430 tools across 87 measured servers):
|
|
273
260
|
stub · wordy — 345 tokens: description 321 (p92), schema 14
|
|
274
261
|
rewriting the description toward the measured median (27) would recover ≈294 tokens on every request
|
|
275
262
|
1 of 2 tools sit inside the distribution — no advice where nothing is measurably unusual.
|
|
@@ -285,8 +272,8 @@ are not:
|
|
|
285
272
|
|
|
286
273
|
```
|
|
287
274
|
changed — published versions of your servers that have moved since
|
|
288
|
-
(index 2026-09-
|
|
289
|
-
notes (published as
|
|
275
|
+
(index 2026-09-05, 99 published captures; matched by canonical hash, never by name):
|
|
276
|
+
notes (published as my-server) — you have the capture published 2026-08-19 at 1,132 tokens;
|
|
290
277
|
the current one is 2,062 (+930, 2026-08-26)
|
|
291
278
|
updating all 1 would add 930 tokens to every request in this client.
|
|
292
279
|
```
|
|
@@ -303,20 +290,21 @@ Flags: `--json` (full report on stdout, progress on stderr), `--budget N`,
|
|
|
303
290
|
|
|
304
291
|
The number `audit` gives you is the same measurement, run across a curated set of public
|
|
305
292
|
servers — which is how you can tell it is a measurement and not this tool's opinion. It also
|
|
306
|
-
shows what you are choosing between: across the 87 servers measured, cost spans **1,700
|
|
307
|
-
from `postgres` at 32 tokens to `github` at 54,622
|
|
308
|
-
|
|
293
|
+
shows what you are choosing between: across the 87 servers measured, cost spans **1,700×** on
|
|
294
|
+
the wire, from `postgres` at 32 tokens to `github` at 54,622 — of which a request carries
|
|
295
|
+
10,735, and Claude counts those at 18,728. The table below is a sample of that range, ranked
|
|
296
|
+
on the wire like every other list here; the full range is in
|
|
309
297
|
[results/leaderboard.md](results/leaderboard.md).
|
|
310
298
|
|
|
311
|
-
| server | context cost | tools |
|
|
312
|
-
|
|
313
|
-
| github (official) | **54,622 tokens** | 44 |
|
|
314
|
-
| xcodebuildmcp | 26,594 | 24 |
|
|
315
|
-
| brave-search | 25,487 | 8 |
|
|
316
|
-
| notion | 17,500 | 24 |
|
|
317
|
-
| playwright *(4.8M installs/week)* | 4,024 | 24 |
|
|
318
|
-
| filesystem (reference) | 2,823 | 14 |
|
|
319
|
-
| markitdown | 64 | 1 |
|
|
299
|
+
| server | context cost (wire) | mapped | on Claude | tools |
|
|
300
|
+
|---|---:|---:|---:|---:|
|
|
301
|
+
| github (official) | **54,622 tokens** | 10,735 | 18,728 | 44 |
|
|
302
|
+
| xcodebuildmcp | 26,594 | 2,676 | 5,335 | 24 |
|
|
303
|
+
| brave-search | 25,487 | 8,278 | 13,762 | 8 |
|
|
304
|
+
| notion | 17,500 | 17,163 | 33,560 | 24 |
|
|
305
|
+
| playwright *(4.8M installs/week)* | 4,024 | 3,402 | 6,172 | 24 |
|
|
306
|
+
| filesystem (reference) | 2,823 | 1,665 | 3,115 | 14 |
|
|
307
|
+
| markitdown | 64 | 64 | 404 | 1 |
|
|
320
308
|
|
|
321
309
|
*(87 of 107 popular servers measured, each row dated by its own most recent sweep — full table in
|
|
322
310
|
[results/leaderboard.md](results/leaderboard.md); every failure is listed with its reason.
|
|
@@ -342,12 +330,14 @@ the cost before they install rather than after:
|
|
|
342
330
|
|
|
343
331
|
The badge counts every byte a server returns. An Anthropic request carries only `name`,
|
|
344
332
|
`description`, and `input_schema` — and counts them with a denser tokenizer. Both effects are
|
|
345
|
-
now measured against a pinned model and published beside the badge, and they do not cancel
|
|
333
|
+
now measured against a pinned model and published beside the badge, and they do not cancel.
|
|
334
|
+
The middle column is what a request actually carries, counted with the badge's own tokenizer,
|
|
335
|
+
so the two effects are separated rather than folded together:
|
|
346
336
|
|
|
347
|
-
| server | badge (o200k) | Claude (`claude-opus-5`) | |
|
|
348
|
-
|
|
349
|
-
| github | 54,622 | **18,728** | 78% of the capture is `icons` metadata Claude never sees |
|
|
350
|
-
| notion | 17,500 | **33,560** | almost no metadata to drop, so the tokenizer difference dominates |
|
|
337
|
+
| server | badge (o200k) | mapped | Claude (`claude-opus-5`) | |
|
|
338
|
+
|---|---:|---:|---:|---|
|
|
339
|
+
| github | 54,622 | 10,735 | **18,728** | 78% of the capture is `icons` metadata Claude never sees |
|
|
340
|
+
| notion | 17,500 | 17,163 | **33,560** | almost no metadata to drop, so the tokenizer difference dominates |
|
|
351
341
|
|
|
352
342
|
So the heaviest server on the badge is not the heaviest server on Claude. Per-server
|
|
353
343
|
breakdowns are on each [detail page](https://athakur3.github.io/mcp-context-cost/servers/);
|
|
@@ -355,9 +345,10 @@ the method is [Claude divergence](docs/METHODOLOGY.md#claude-divergence).
|
|
|
355
345
|
|
|
356
346
|
## Why trust the number?
|
|
357
347
|
|
|
358
|
-
Every published number is backed by a `measurement.json
|
|
359
|
-
|
|
360
|
-
|
|
348
|
+
Every published number is backed by a `measurement.json`. What it holds, and the five lines
|
|
349
|
+
that re-derive the number from it, are in
|
|
350
|
+
[METHODOLOGY §reproduce it](docs/METHODOLOGY.md#reproduce-it). Disputes reduce to a byte-level
|
|
351
|
+
diff, and the CLI does that for you:
|
|
361
352
|
|
|
362
353
|
```bash
|
|
363
354
|
npx -y mcp-context-cost verify results/github/measurement.json
|
|
@@ -395,7 +386,7 @@ number is *not*, config policy, failure taxonomy, frozen color bands, known dive
|
|
|
395
386
|
| `src/cli.ts` | `audit` (measure your own stack), `verify` (re-derive any published number), `measure` |
|
|
396
387
|
| `spec/fixtures/` | golden vectors shared by the TypeScript and bash implementations |
|
|
397
388
|
| `tools/` | the scripts that call a network API — the Claude divergence run, the adoption reading, the registry scan — kept out of the package so the library stays offline |
|
|
398
|
-
| `upstream/` | `badge.sh` +
|
|
389
|
+
| `upstream/` | `badge.sh` + bash tests — the self-serve badge recipe, carried here |
|
|
399
390
|
| `servers.yaml` | 107 curated candidates with live install metrics and provenance |
|
|
400
391
|
| `results/` · `badges/` | measurements, leaderboard, history series, shields endpoint JSONs |
|
|
401
392
|
| `docs/` | methodology, dashboard, and a generated page per measured server |
|
|
@@ -411,14 +402,12 @@ That prints the number and writes nothing. Published records
|
|
|
411
402
|
(`results/<name>/measurement.json`, `badges/<name>.json`, the `history.csv`
|
|
412
403
|
row) come from CI: a developer machine is a different architecture under
|
|
413
404
|
different load, and a measurement taken there describes it rather than the
|
|
414
|
-
server.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
on that PR measures the entry read-only, and the rotation publishes it after
|
|
421
|
-
merge.
|
|
405
|
+
server. Which machine a number applies to, and the failed record that made it
|
|
406
|
+
a rule, are in
|
|
407
|
+
[METHODOLOGY §which machine a number applies to](docs/METHODOLOGY.md#which-machine-a-number-applies-to).
|
|
408
|
+
To get your server into the leaderboard, add an entry to `servers.yaml` and
|
|
409
|
+
open a pull request; the check on that PR measures the entry read-only, and the
|
|
410
|
+
rotation publishes it after merge.
|
|
422
411
|
|
|
423
412
|
For a badge on your own README, run the published CLI in your server's own CI
|
|
424
413
|
(the [gate](#defend-the-number-dont-just-display-it) below writes
|
|
@@ -446,7 +435,7 @@ cp results/my-server/measurement.json .context-cost/baseline.json
|
|
|
446
435
|
|
|
447
436
|
# on every pull request
|
|
448
437
|
npx -y mcp-context-cost measure --name my-server --command "node dist/index.js" \
|
|
449
|
-
--baseline .context-cost/baseline.json --max-increase
|
|
438
|
+
--baseline .context-cost/baseline.json --max-increase 100
|
|
450
439
|
```
|
|
451
440
|
|
|
452
441
|
```
|
|
@@ -460,26 +449,13 @@ INCREASE FAIL: +121 tokens, over the 100 allowed — this change adds that to ev
|
|
|
460
449
|
|
|
461
450
|
Both sides are single measurements carrying per-tool counts, so an established
|
|
462
451
|
change is attributed exactly: which tools arrived, which grew, and by how much.
|
|
463
|
-
And `--max-increase` fails on more than the number
|
|
464
|
-
|
|
465
|
-
improvement is the one mistake a gate like this must not make, so a change that
|
|
466
|
-
could not be established fails too.
|
|
467
|
-
|
|
468
|
-
As a GitHub Action, that whole workflow is five lines
|
|
469
|
-
([full example](examples/server-author-ci.yml)):
|
|
470
|
-
|
|
471
|
-
```yaml
|
|
472
|
-
- uses: athakur3/mcp-context-cost@v1
|
|
473
|
-
with:
|
|
474
|
-
name: my-server
|
|
475
|
-
command: node dist/index.js
|
|
476
|
-
baseline: .context-cost/baseline.json
|
|
477
|
-
max-increase: 500
|
|
478
|
-
```
|
|
452
|
+
And `--max-increase` fails on more than the number here too: a change that could
|
|
453
|
+
not be established fails the gate, for the reason the `audit` gate gives above.
|
|
479
454
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
455
|
+
As a GitHub Action that whole workflow is the five lines at the top of this page
|
|
456
|
+
([full example](examples/server-author-ci.yml)) — the same inputs, and the same
|
|
457
|
+
outputs written whether the gate passed or not, for a later step to comment on
|
|
458
|
+
the pull request or publish as a badge.
|
|
483
459
|
|
|
484
460
|
Point the link at the measurement behind the number — for servers in this sweep that is
|
|
485
461
|
`https://athakur3.github.io/mcp-context-cost/servers/<name>.html`; otherwise the
|
|
@@ -499,17 +475,14 @@ npx tsc --noEmit # typecheck
|
|
|
499
475
|
npm run sweep:all -- --docker # full curated sweep (Docker isolation)
|
|
500
476
|
```
|
|
501
477
|
|
|
502
|
-
Notable engineering choices: the MCP client is a deliberate
|
|
503
|
-
|
|
478
|
+
Notable engineering choices: the MCP client is a deliberate raw-wire implementation
|
|
479
|
+
rather than the SDK (schema-parsing can reorder keys, which would corrupt canonical bytes);
|
|
504
480
|
sweep servers run in credential-free Docker containers with recorded isolation; the badge
|
|
505
481
|
color bands are frozen against the observed distribution of the first full sweep.
|
|
506
482
|
|
|
507
483
|
## Status
|
|
508
484
|
|
|
509
|
-
Active. Every row carries the date of its own most recent measurement
|
|
510
|
-
says as a whole is written up, dated, in
|
|
511
|
-
[The State of MCP Context Cost](https://athakur3.github.io/mcp-context-cost/state-of-mcp-context-cost)
|
|
512
|
-
(September 2026). Two
|
|
485
|
+
Active. Every row carries the date of its own most recent measurement. Two
|
|
513
486
|
weekly jobs re-measure the set — the `memory` reference server on Mondays, and a rotating
|
|
514
487
|
third of the list on Wednesdays, so every row comes round within three weeks. Read each row's
|
|
515
488
|
date as the date it means, and don't take the cadence on trust — the build history is
|
package/dist/audit/audit.d.ts
CHANGED
|
@@ -25,23 +25,23 @@ export interface AuditServerResult {
|
|
|
25
25
|
toolCount: number | null;
|
|
26
26
|
/** Share of this config's measured total, 0–1. */
|
|
27
27
|
share: number | null;
|
|
28
|
-
command?: string;
|
|
29
|
-
url?: string;
|
|
28
|
+
command?: string | undefined;
|
|
29
|
+
url?: string | undefined;
|
|
30
30
|
/** Names only — a server's env values never enter a report. */
|
|
31
31
|
envVarNames: string[];
|
|
32
32
|
/** Names only, and only for a remote entry that carries any — values never enter a report. */
|
|
33
|
-
headerNames?: string[];
|
|
33
|
+
headerNames?: string[] | undefined;
|
|
34
34
|
/** Claude Code's `alwaysLoad: true`, read from the entry: loads at session start whatever the setting. */
|
|
35
|
-
alwaysLoad?: true;
|
|
36
|
-
canonicalSha256?: string | null;
|
|
35
|
+
alwaysLoad?: true | undefined;
|
|
36
|
+
canonicalSha256?: string | null | undefined;
|
|
37
37
|
/**
|
|
38
38
|
* Anthropic-request cost from the published Claude divergence run, only when
|
|
39
39
|
* its captured hash matches this install (`--claude`). `null` means the
|
|
40
40
|
* install doesn't match what was published — silence, not a stale guess.
|
|
41
41
|
* `undefined` means `--claude` wasn't requested at all.
|
|
42
42
|
*/
|
|
43
|
-
claudeTokens?: number | null;
|
|
44
|
-
notes?: string;
|
|
43
|
+
claudeTokens?: number | null | undefined;
|
|
44
|
+
notes?: string | undefined;
|
|
45
45
|
}
|
|
46
46
|
export interface HeaviestTool {
|
|
47
47
|
server: string;
|
|
@@ -96,9 +96,9 @@ export interface AuditConfigResult {
|
|
|
96
96
|
heaviestTools: HeaviestTool[];
|
|
97
97
|
trimAdvice: TrimAdvice | null;
|
|
98
98
|
/** Present only when `--suggest` ran with a usable baseline. */
|
|
99
|
-
suggestions?: ConfigSuggestions;
|
|
99
|
+
suggestions?: ConfigSuggestions | undefined;
|
|
100
100
|
/** Present only when `--changed` ran with a usable capture index. */
|
|
101
|
-
captureVerdicts?: ServerCaptureVerdict[];
|
|
101
|
+
captureVerdicts?: ServerCaptureVerdict[] | undefined;
|
|
102
102
|
/**
|
|
103
103
|
* Whether this client loads the total up front or defers it, and — when the
|
|
104
104
|
* client decides that by a threshold — which side of it this stack is on.
|
|
@@ -171,30 +171,30 @@ export interface AuditReport {
|
|
|
171
171
|
* against the whole stack and the verdict fails rather than passing on a
|
|
172
172
|
* total that understates by an unknown amount.
|
|
173
173
|
*/
|
|
174
|
-
unestablished?: string[];
|
|
174
|
+
unestablished?: string[] | undefined;
|
|
175
175
|
/** Present only when over budget: the arithmetic of getting back under it. */
|
|
176
|
-
fit?: BudgetFit;
|
|
176
|
+
fit?: BudgetFit | undefined;
|
|
177
177
|
};
|
|
178
178
|
/** Present only when a divergence run was supplied (`--claude`). */
|
|
179
179
|
claudeDivergence?: {
|
|
180
180
|
model: string;
|
|
181
181
|
measuredAt: string;
|
|
182
|
-
};
|
|
182
|
+
} | undefined;
|
|
183
183
|
/** Which published tool-shape baseline `--suggest` read its percentiles from. */
|
|
184
184
|
toolShape?: {
|
|
185
185
|
generatedAt: string;
|
|
186
186
|
toolCount: number;
|
|
187
187
|
serverCount: number;
|
|
188
|
-
};
|
|
188
|
+
} | undefined;
|
|
189
189
|
/** Which published capture index `--changed` joined against. */
|
|
190
190
|
captureIndex?: {
|
|
191
191
|
generatedAt: string;
|
|
192
192
|
captureCount: number;
|
|
193
|
-
};
|
|
193
|
+
} | undefined;
|
|
194
194
|
/** Present only when a baseline report was supplied (`--baseline`). */
|
|
195
|
-
diff?: AuditDiff;
|
|
195
|
+
diff?: AuditDiff | undefined;
|
|
196
196
|
/** Present only when `--max-increase` was supplied alongside a baseline. */
|
|
197
|
-
increaseGate?: IncreaseGate;
|
|
197
|
+
increaseGate?: IncreaseGate | undefined;
|
|
198
198
|
problems: string[];
|
|
199
199
|
}
|
|
200
200
|
/** Cache key for measurement reuse: the exact argv two configs would spawn. */
|
|
@@ -218,38 +218,36 @@ export declare function collapsedKeys(configs: LoadedConfig[]): Set<string>;
|
|
|
218
218
|
* launching a single server.
|
|
219
219
|
*/
|
|
220
220
|
export declare function buildReport(configs: LoadedConfig[], measured: Map<string, Measurement>, opts?: {
|
|
221
|
-
contextWindow?: number;
|
|
222
|
-
budget?: number;
|
|
223
|
-
generatedAt?: string;
|
|
221
|
+
contextWindow?: number | undefined;
|
|
222
|
+
budget?: number | undefined;
|
|
223
|
+
generatedAt?: string | undefined;
|
|
224
224
|
/** Published `tools-delta/v1` run to join against (`--claude`); omit to skip the join. */
|
|
225
|
-
divergence?: DivergenceRun | null;
|
|
225
|
+
divergence?: DivergenceRun | null | undefined;
|
|
226
226
|
/** Published `tool-shape/v1` baseline (`--suggest`); omit to skip suggestions. */
|
|
227
|
-
toolShape?: ToolShapeBaseline | null;
|
|
227
|
+
toolShape?: ToolShapeBaseline | null | undefined;
|
|
228
228
|
/** Published `capture-index/v1` (`--changed`); omit to skip the version join. */
|
|
229
|
-
captureIndex?: CaptureIndex | null;
|
|
229
|
+
captureIndex?: CaptureIndex | null | undefined;
|
|
230
230
|
/**
|
|
231
231
|
* The audited machine's SHELL tool-search variables. Passed in rather than
|
|
232
232
|
* read here so this stays pure and a report is reproducible from its
|
|
233
233
|
* inputs; `runAudit` supplies the real environment. Omitted means the shell
|
|
234
234
|
* set nothing.
|
|
235
235
|
*/
|
|
236
|
-
env?: ToolSearchEnv;
|
|
236
|
+
env?: ToolSearchEnv | undefined;
|
|
237
237
|
/**
|
|
238
238
|
* The other place those variables come from: Claude Code's own settings
|
|
239
239
|
* files, highest precedence first, as `loadSettingsSources` read them.
|
|
240
240
|
* `runAudit` supplies these. Omitted means they were not read here — which
|
|
241
241
|
* the report says, rather than reporting a default it did not establish.
|
|
242
242
|
*/
|
|
243
|
-
settings?: ToolSearchSource[];
|
|
243
|
+
settings?: ToolSearchSource[] | undefined;
|
|
244
244
|
/**
|
|
245
245
|
* What each remote endpoint said to an unauthenticated `initialize`, keyed
|
|
246
246
|
* by `serverKey`. `runAudit` supplies it from `probeRemotes`; omitted, a
|
|
247
247
|
* remote entry is reported as not probed rather than as anything else.
|
|
248
248
|
*/
|
|
249
|
-
remotes?: Map<string, RemoteProbe
|
|
249
|
+
remotes?: Map<string, RemoteProbe> | undefined;
|
|
250
250
|
}): AuditReport;
|
|
251
251
|
/** Human output. JSON output is the report object itself. */
|
|
252
252
|
export declare function formatReport(report: AuditReport): string;
|
|
253
|
-
/** Top-level tool list across every config — used by nothing yet, handy for --json consumers. */
|
|
254
|
-
export declare function allHeaviestTools(report: AuditReport, limit?: number): HeaviestTool[];
|
|
255
253
|
export type { ToolMeasurement };
|