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 23
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 23 rows — the measured servers it covered when it last ran —
208
- and [results/leaderboard.md](results/leaderboard.md) prints a claude number for the 15 that
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,150 measured tools:
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-03: 1,150 tools across 81 measured servers):
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 86 servers measured, cost spans **1,700×**,
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
- *(86 of 106 popular servers measured, each row dated by its own most recent sweep — full table in
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.)*
@@ -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
- * (claude-opus-5, 2026-08-19, 20 servers). Used when no divergence run was
239
- * supplied; `--claude` recomputes it from the run it fetched.
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. */
@@ -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
- * (claude-opus-5, 2026-08-19, 20 servers). Used when no divergence run was
227
- * supplied; `--claude` recomputes it from the run it fetched.
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: new Date().toISOString().slice(0, 10),
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))),
@@ -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
- const baseline = buildToolShapeBaseline(tools, { serverCount, methodologyVersion: METHODOLOGY_VERSION });
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.0",
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"