mcp-context-cost 0.13.0 → 0.13.1
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
CHANGED
|
@@ -126,7 +126,7 @@ costs **more** than loading the definitions would.
|
|
|
126
126
|
|
|
127
127
|
Three things the report will not do: it will not convert between units silently (in
|
|
128
128
|
threshold mode the stack is compared as a range, because the audit counts wire bytes and the
|
|
129
|
-
threshold is counted in what the client sends to the API — measured at 0.20×–1.92× across
|
|
129
|
+
threshold is counted in what the client sends to the API — measured at 0.20×–1.92× across 24
|
|
130
130
|
servers); it will not claim a posture the machine did not state readably, which is four
|
|
131
131
|
refusals and not one — when two places set the same variable to different values, when a
|
|
132
132
|
settings file exists and cannot be read, when the place that would decide sets the variable
|
|
@@ -204,8 +204,8 @@ INCREASE FAIL:
|
|
|
204
204
|
Add `--claude` to annotate each server with its Anthropic-request cost from the published
|
|
205
205
|
[Claude divergence](docs/METHODOLOGY.md#claude-divergence) run — an exact number when the
|
|
206
206
|
published capture hash matches what you have installed, `—` (silence, not a stale guess)
|
|
207
|
-
when it doesn't. The run holds
|
|
208
|
-
and [results/leaderboard.md](results/leaderboard.md) prints a claude number for the
|
|
207
|
+
when it doesn't. The run holds 24 rows — the measured servers it covered when it last ran —
|
|
208
|
+
and [results/leaderboard.md](results/leaderboard.md) prints a claude number for the 16 that
|
|
209
209
|
still match today and silence for the rest. Most installs will show a mix:
|
|
210
210
|
|
|
211
211
|
```
|
|
@@ -218,11 +218,11 @@ Add `--suggest` to place each of your tools in the measured set's tool-shape dis
|
|
|
218
218
|
([method](docs/METHODOLOGY.md#tool-shape)) and get advice only where the data can point at
|
|
219
219
|
something. Only descriptions draw advice — schemas are functional surface; descriptions are
|
|
220
220
|
prose every request carries — and only descriptions at or above the 90th percentile of the
|
|
221
|
-
1,
|
|
221
|
+
1,430 measured tools:
|
|
222
222
|
|
|
223
223
|
```
|
|
224
224
|
suggest — descriptions at or above the 90th percentile of measured tools
|
|
225
|
-
(baseline 2026-09-
|
|
225
|
+
(baseline 2026-09-05: 1,430 tools across 87 measured servers):
|
|
226
226
|
stub · wordy — 345 tokens: description 321 (p92), schema 14
|
|
227
227
|
rewriting the description toward the measured median (27) would recover ≈294 tokens on every request
|
|
228
228
|
1 of 2 tools sit inside the distribution — no advice where nothing is measurably unusual.
|
|
@@ -256,7 +256,7 @@ Flags: `--json` (full report on stdout, progress on stderr), `--budget N`,
|
|
|
256
256
|
|
|
257
257
|
The number `audit` gives you is the same measurement, run across a curated set of public
|
|
258
258
|
servers — which is how you can tell it is a measurement and not this tool's opinion. It also
|
|
259
|
-
shows what you are choosing between: across the
|
|
259
|
+
shows what you are choosing between: across the 87 servers measured, cost spans **1,700×**,
|
|
260
260
|
from `postgres` at 32 tokens to `github` at 54,622. The table below is a
|
|
261
261
|
sample of that range; the full range is in
|
|
262
262
|
[results/leaderboard.md](results/leaderboard.md).
|
|
@@ -271,7 +271,7 @@ sample of that range; the full range is in
|
|
|
271
271
|
| filesystem (reference) | 2,823 | 14 |
|
|
272
272
|
| markitdown | 64 | 1 |
|
|
273
273
|
|
|
274
|
-
*(
|
|
274
|
+
*(87 of 106 popular servers measured, each row dated by its own most recent sweep — full table in
|
|
275
275
|
[results/leaderboard.md](results/leaderboard.md); every failure is listed with its reason.
|
|
276
276
|
Each measured server also has a [detail page](https://athakur3.github.io/mcp-context-cost/servers/)
|
|
277
277
|
showing which tools its tokens are in.)*
|
package/dist/audit/deferral.d.ts
CHANGED
|
@@ -234,9 +234,15 @@ export interface WireToClientRatio {
|
|
|
234
234
|
source: string;
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
|
-
* The band as published in this repository's own `results/divergence.json
|
|
238
|
-
*
|
|
239
|
-
*
|
|
237
|
+
* The band as published in this repository's own `results/divergence.json`.
|
|
238
|
+
* Used when no divergence run was supplied; `--claude` recomputes it from the
|
|
239
|
+
* run it fetched.
|
|
240
|
+
*
|
|
241
|
+
* The model, the date and the count deliberately are not restated here. This
|
|
242
|
+
* docblock said `(claude-opus-5, 2026-08-19, 20 servers)` while the field below
|
|
243
|
+
* read 23 and the run on disk held 24 — prose beside a number, drifting from
|
|
244
|
+
* it, which is the same failure this constant's own guard exists to catch one
|
|
245
|
+
* level down. The fields are the record; `source` dates them.
|
|
240
246
|
*/
|
|
241
247
|
export declare const PUBLISHED_WIRE_TO_CLIENT_RATIO: WireToClientRatio;
|
|
242
248
|
/** Derive the band from a supplied divergence run, falling back to the published one. */
|
package/dist/audit/deferral.js
CHANGED
|
@@ -222,9 +222,15 @@ export function resolveToolSearchSources(sources) {
|
|
|
222
222
|
return { ...resolved, source: resolved.readFromMachine ? (base?.source ?? null) : null };
|
|
223
223
|
}
|
|
224
224
|
/**
|
|
225
|
-
* The band as published in this repository's own `results/divergence.json
|
|
226
|
-
*
|
|
227
|
-
*
|
|
225
|
+
* The band as published in this repository's own `results/divergence.json`.
|
|
226
|
+
* Used when no divergence run was supplied; `--claude` recomputes it from the
|
|
227
|
+
* run it fetched.
|
|
228
|
+
*
|
|
229
|
+
* The model, the date and the count deliberately are not restated here. This
|
|
230
|
+
* docblock said `(claude-opus-5, 2026-08-19, 20 servers)` while the field below
|
|
231
|
+
* read 23 and the run on disk held 24 — prose beside a number, drifting from
|
|
232
|
+
* it, which is the same failure this constant's own guard exists to catch one
|
|
233
|
+
* level down. The fields are the record; `source` dates them.
|
|
228
234
|
*/
|
|
229
235
|
export const PUBLISHED_WIRE_TO_CLIENT_RATIO = {
|
|
230
236
|
low: 0.2,
|
|
@@ -50,6 +50,18 @@ export interface PublishedStats {
|
|
|
50
50
|
ratioMin: number;
|
|
51
51
|
ratioMax: number;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* The published tool-shape baseline, which README quotes twice — once in
|
|
55
|
+
* prose and once inside the `--suggest` sample output. Both were hand-written
|
|
56
|
+
* and `STATIC_COUNTS` excused them as regen-maintained, which was not true of
|
|
57
|
+
* either: the file said 1,430 tools across 87 servers while the page said
|
|
58
|
+
* 1,150 across 81, and every test passed.
|
|
59
|
+
*/
|
|
60
|
+
toolShape: {
|
|
61
|
+
toolCount: number;
|
|
62
|
+
serverCount: number;
|
|
63
|
+
generatedAt: string;
|
|
64
|
+
};
|
|
53
65
|
deferralCostlierCount: number;
|
|
54
66
|
/** Servers whose most recent cost movement went up / down (cost-regression/v1). */
|
|
55
67
|
movement: {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* never a silent skip), and only the slots are ever rewritten — spliced in
|
|
26
26
|
* place, so the page's own line wrapping survives.
|
|
27
27
|
*/
|
|
28
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
28
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
29
29
|
import { join } from 'node:path';
|
|
30
30
|
import { DEFAULT_CONTEXT_WINDOW } from '../audit/audit.js';
|
|
31
31
|
import { fieldSelectionShare, isCurrent } from '../core/divergence.js';
|
|
@@ -128,7 +128,15 @@ export function computePublishedStats(entries, root = process.cwd()) {
|
|
|
128
128
|
if (!githubRow?.m || typeof githubRow.m.totalTokens !== 'number') {
|
|
129
129
|
throw new Error('README quotes `verify` on results/github, which has no current measurement');
|
|
130
130
|
}
|
|
131
|
+
const shape = (() => {
|
|
132
|
+
const p = join(root, 'results', 'tool-shape.json');
|
|
133
|
+
if (!existsSync(p))
|
|
134
|
+
throw new Error('results/tool-shape.json is missing — README states its numbers');
|
|
135
|
+
const j = JSON.parse(readFileSync(p, 'utf8'));
|
|
136
|
+
return { toolCount: j.toolCount, serverCount: j.serverCount, generatedAt: j.generatedAt };
|
|
137
|
+
})();
|
|
131
138
|
return {
|
|
139
|
+
toolShape: shape,
|
|
132
140
|
candidateTotal: rows.length,
|
|
133
141
|
measuredCount: measured.length,
|
|
134
142
|
max,
|
|
@@ -225,6 +233,20 @@ export const PAGE_CLAIMS = [
|
|
|
225
233
|
template: 'measured at {f}×–{f}× across {n} servers)',
|
|
226
234
|
values: (s) => [s.claude.ratioMin.toFixed(2), s.claude.ratioMax.toFixed(2), fmt(s.claude.runSize)],
|
|
227
235
|
},
|
|
236
|
+
{
|
|
237
|
+
file: 'README.md',
|
|
238
|
+
id: 'tool-shape:prose',
|
|
239
|
+
template: 'only descriptions at or above the 90th percentile of the {n} measured tools:',
|
|
240
|
+
values: (s) => [fmt(s.toolShape.toolCount)],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
file: 'README.md',
|
|
244
|
+
id: 'tool-shape:sample-header',
|
|
245
|
+
// Inside the `--suggest` sample block. A reader compares their own output
|
|
246
|
+
// against it, so a stale baseline line there is read as a current one.
|
|
247
|
+
template: '(baseline {w}: {n} tools across {n} measured servers):',
|
|
248
|
+
values: (s) => [s.toolShape.generatedAt, fmt(s.toolShape.toolCount), fmt(s.toolShape.serverCount)],
|
|
249
|
+
},
|
|
228
250
|
{
|
|
229
251
|
file: 'README.md',
|
|
230
252
|
id: 'repo-map:candidates',
|
|
@@ -317,6 +339,18 @@ export const PAGE_CLAIMS = [
|
|
|
317
339
|
fmt(s.claude.widest.mapped),
|
|
318
340
|
],
|
|
319
341
|
},
|
|
342
|
+
{
|
|
343
|
+
file: 'docs/METHODOLOGY.md',
|
|
344
|
+
id: 'divergence:band-parenthetical',
|
|
345
|
+
// The deferral section's own copy of the band. It was hand-written and held
|
|
346
|
+
// to `PUBLISHED_WIRE_TO_CLIENT_RATIO` by a test, which worked only while the
|
|
347
|
+
// constant tracked the run exactly — and the constant is a release-time
|
|
348
|
+
// snapshot that is deliberately allowed to lag. Two pages quoting two
|
|
349
|
+
// different sources for one number is the drift this file exists to end, so
|
|
350
|
+
// both pages state the run and the constant is guarded separately.
|
|
351
|
+
template: 'band ({f}×–{f}×\nacross {n} servers)',
|
|
352
|
+
values: (s) => [s.claude.ratioMin.toFixed(2), s.claude.ratioMax.toFixed(2), fmt(s.claude.runSize)],
|
|
353
|
+
},
|
|
320
354
|
{
|
|
321
355
|
file: 'docs/METHODOLOGY.md',
|
|
322
356
|
id: 'divergence:ratio-range',
|
|
@@ -103,9 +103,19 @@ export function writeCaptureIndex(entries, root = process.cwd()) {
|
|
|
103
103
|
}
|
|
104
104
|
for (const sha of ambiguous)
|
|
105
105
|
delete captures[sha];
|
|
106
|
+
// Dated by the newest capture it indexes, not by the moment it was written —
|
|
107
|
+
// the same rule the dashboard was moved to. A clock stamp makes the file
|
|
108
|
+
// claim to be as fresh as the run that regenerated it, when what a reader
|
|
109
|
+
// needs to know is how far the data behind it reaches. It also means
|
|
110
|
+
// regenerating over unchanged vectors produces no diff, which is what lets a
|
|
111
|
+
// release gate ask "is anything derived here stale?" and get a real answer.
|
|
112
|
+
const newest = Object.values(captures)
|
|
113
|
+
.map((c) => c.date)
|
|
114
|
+
.sort()
|
|
115
|
+
.pop();
|
|
106
116
|
const index = {
|
|
107
117
|
method: CAPTURE_INDEX_METHOD,
|
|
108
|
-
generatedAt:
|
|
118
|
+
generatedAt: newest ?? '',
|
|
109
119
|
// Key order sorted so a re-run over unchanged vectors produces no diff noise.
|
|
110
120
|
captures: Object.fromEntries(Object.entries(captures).sort(([a], [b]) => a.localeCompare(b))),
|
|
111
121
|
current: Object.fromEntries(Object.entries(current).sort(([a], [b]) => a.localeCompare(b))),
|
package/dist/sweep/tool-shape.js
CHANGED
|
@@ -11,6 +11,7 @@ import { buildToolShapeBaseline } from '../core/tool-shape.js';
|
|
|
11
11
|
import { loadRows } from './report.js';
|
|
12
12
|
export function writeToolShapeBaseline(entries, root = process.cwd()) {
|
|
13
13
|
const tools = [];
|
|
14
|
+
const dates = [];
|
|
14
15
|
let serverCount = 0;
|
|
15
16
|
for (const r of loadRows(entries, root)) {
|
|
16
17
|
if (!r.m || (r.m.status !== 'measured' && r.m.status !== 'dynamic'))
|
|
@@ -19,8 +20,20 @@ export function writeToolShapeBaseline(entries, root = process.cwd()) {
|
|
|
19
20
|
continue;
|
|
20
21
|
serverCount++;
|
|
21
22
|
tools.push(...r.m.tools);
|
|
23
|
+
const day = String(r.m.measuredAt ?? '').slice(0, 10);
|
|
24
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(day))
|
|
25
|
+
dates.push(day);
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
// Dated by the newest measurement it summarises rather than by the clock, so
|
|
28
|
+
// the file says how far its data reaches and a re-derivation over unchanged
|
|
29
|
+
// measurements produces no diff. `buildToolShapeBaseline` still defaults to
|
|
30
|
+
// today when nobody supplies one, for callers with no measurement dates.
|
|
31
|
+
const generatedAt = dates.sort().pop();
|
|
32
|
+
const baseline = buildToolShapeBaseline(tools, {
|
|
33
|
+
serverCount,
|
|
34
|
+
methodologyVersion: METHODOLOGY_VERSION,
|
|
35
|
+
...(generatedAt ? { generatedAt } : {}),
|
|
36
|
+
});
|
|
24
37
|
writeFileSync(join(root, 'results', 'tool-shape.json'), JSON.stringify(baseline, null, 2) + '\n');
|
|
25
38
|
return baseline;
|
|
26
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-context-cost",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Measure what your MCP servers cost in context tokens — audit your own config, or badge the server you publish",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
"session-start": "tsx src/sweep/session-start.ts",
|
|
44
44
|
"sweep": "tsx src/sweep/run.ts",
|
|
45
45
|
"sweep:all": "tsx src/sweep/sweep-all.ts",
|
|
46
|
-
"verify": "tsx src/cli.ts verify"
|
|
46
|
+
"verify": "tsx src/cli.ts verify",
|
|
47
|
+
"check:release": "tsx tools/release-readiness.ts",
|
|
48
|
+
"cut": "tsx tools/cut-changelog.ts"
|
|
47
49
|
},
|
|
48
50
|
"engines": {
|
|
49
51
|
"node": ">=20"
|