mcp-context-cost 0.15.0 → 0.16.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 +4 -4
- package/dist/audit/audit.js +39 -4
- package/dist/audit/deferral.d.ts +35 -0
- package/dist/audit/deferral.js +45 -0
- package/dist/sweep/published-stats.js +16 -4
- package/dist/sweep/shard.d.ts +1 -1
- package/dist/sweep/shard.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -294,7 +294,7 @@ answers a question no client asks: **what did this server cost last month?**
|
|
|
294
294
|
[results/regressions.md](results/regressions.md) reports each server's most recent movement —
|
|
295
295
|
dated to when it happened, separated into *shipped more tools* versus *same tools, rewritten*,
|
|
296
296
|
and compared only within one isolation. The ecosystem ratchets upward: of the servers whose
|
|
297
|
-
cost has moved at all,
|
|
297
|
+
cost has moved at all, 13 moved up against 5 that moved down. Method:
|
|
298
298
|
[cost movement](docs/METHODOLOGY.md#cost-movement).
|
|
299
299
|
|
|
300
300
|
If you publish a server, the same measurement is available as a badge, so your users can see
|
|
@@ -400,8 +400,8 @@ repository), then in your README:
|
|
|
400
400
|
A badge says what your server costs today; it does nothing about the release
|
|
401
401
|
that adds 1,200 tokens to every user's context next month. Across the servers
|
|
402
402
|
measured here most costs hold steady from sweep to sweep, but when a cost does
|
|
403
|
-
move it usually moves up: the [movement report](results/regressions.md) has
|
|
404
|
-
servers ratcheting upward against
|
|
403
|
+
move it usually moves up: the [movement report](results/regressions.md) has 13
|
|
404
|
+
servers ratcheting upward against 5 that got cheaper, and none of those
|
|
405
405
|
maintainers had a check that would have said so first. `measure` takes the same
|
|
406
406
|
gate flags `audit` does, so your own CI can be that check:
|
|
407
407
|
|
|
@@ -477,7 +477,7 @@ says as a whole is written up, dated, in
|
|
|
477
477
|
[The State of MCP Context Cost](https://athakur3.github.io/mcp-context-cost/state-of-mcp-context-cost)
|
|
478
478
|
(September 2026). Two
|
|
479
479
|
weekly jobs re-measure the set — the `memory` reference server on Mondays, and a rotating
|
|
480
|
-
|
|
480
|
+
third of the list on Wednesdays, so every row comes round within three weeks. Read each row's
|
|
481
481
|
date as the date it means, and don't take the cadence on trust — the build history is
|
|
482
482
|
public, one click each:
|
|
483
483
|
[re-sweep runs](https://github.com/athakur3/mcp-context-cost/actions/workflows/resweep.yml)
|
package/dist/audit/audit.js
CHANGED
|
@@ -16,7 +16,7 @@ import { METHODOLOGY_VERSION } from '../core/canonical.js';
|
|
|
16
16
|
import { isCurrent } from '../core/divergence.js';
|
|
17
17
|
import { SUGGEST_DESCRIPTION_PERCENTILE, suggestFor, } from '../core/tool-shape.js';
|
|
18
18
|
import { identify } from '../core/capture-index.js';
|
|
19
|
-
import { evaluateDeferral, PUBLISHED_WIRE_TO_CLIENT_RATIO, SHELL_SOURCE, } from './deferral.js';
|
|
19
|
+
import { evaluateDeferral, BAND_PRECISION, PUBLISHED_WIRE_TO_CLIENT_RATIO, SHELL_SOURCE, } from './deferral.js';
|
|
20
20
|
import { formatDiff, formatGate } from './diff.js';
|
|
21
21
|
export const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
22
22
|
const TRIM_TOOL_COUNT = 3;
|
|
@@ -493,6 +493,40 @@ function sharedMeasurementLines(d, skippedNames, consequence) {
|
|
|
493
493
|
}
|
|
494
494
|
return lines;
|
|
495
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* What the threshold below is a share *of*, said where the threshold is said.
|
|
498
|
+
*
|
|
499
|
+
* The setting is a percentage of the context window, so the token figure it
|
|
500
|
+
* resolves to is only as right as the window this audit assumed. That window is
|
|
501
|
+
* a property of the model the client runs, which no config file states and this
|
|
502
|
+
* audit therefore cannot read: it uses 200,000 unless `--context` overrides it.
|
|
503
|
+
* A model with a larger window puts the threshold proportionally higher, and a
|
|
504
|
+
* stack that clears a 20,000-token threshold does not clear a 100,000-token one.
|
|
505
|
+
*
|
|
506
|
+
* This is stated rather than silently assumed because the error has a direction.
|
|
507
|
+
* A window assumed too small makes the threshold too small, which pushes the
|
|
508
|
+
* verdict toward "at or above" — toward telling a reader their tool definitions
|
|
509
|
+
* are deferred, and therefore not charged, when the client's own larger
|
|
510
|
+
* threshold would have loaded every one of them up front. Of the two ways to be
|
|
511
|
+
* wrong here, that is the one that costs somebody tokens they were told they
|
|
512
|
+
* would not pay, so it does not get to be a footnote.
|
|
513
|
+
*
|
|
514
|
+
* Only threshold mode reaches this. The default defers everything at any size,
|
|
515
|
+
* where no window and no arithmetic enter the answer at all.
|
|
516
|
+
*/
|
|
517
|
+
function thresholdAssumptionLines(d) {
|
|
518
|
+
const share = d.thresholdShare ?? 0;
|
|
519
|
+
if (!(share > 0) || d.thresholdTokens === null || d.thresholdTokens === undefined)
|
|
520
|
+
return [];
|
|
521
|
+
const window = Math.round(d.thresholdTokens / share);
|
|
522
|
+
return [
|
|
523
|
+
` That token figure assumes a ${n(window)}-token context window, which is a`,
|
|
524
|
+
' property of the model in use and not of your config, so this audit cannot',
|
|
525
|
+
' read it — pass --context to set it. A model with a larger window has a',
|
|
526
|
+
' proportionally larger threshold, and the same stack can fall on either',
|
|
527
|
+
' side of it depending on which model the client runs.',
|
|
528
|
+
];
|
|
529
|
+
}
|
|
496
530
|
/** Where a threshold is in play, the unknown size is the whole verdict. */
|
|
497
531
|
const SIDE_UNKNOWN = [
|
|
498
532
|
' either direction — which side of the threshold this stack falls on cannot be',
|
|
@@ -608,6 +642,7 @@ function deferralLines(d, skippedNames) {
|
|
|
608
642
|
lines.push(` ${d.client} defers tool definitions above a threshold here (${d.mechanism}):`);
|
|
609
643
|
lines.push(` ${settingPhrase(d)}, so deferral activates once the`);
|
|
610
644
|
lines.push(` definitions reach ${n(t)} tokens — ${pct(d.thresholdShare ?? 0)} of the context window.`);
|
|
645
|
+
lines.push(...thresholdAssumptionLines(d));
|
|
611
646
|
lines.push(...postureSourceLines(d));
|
|
612
647
|
const c = d.clientTokens;
|
|
613
648
|
if (!c) {
|
|
@@ -662,7 +697,7 @@ function deferralLines(d, skippedNames) {
|
|
|
662
697
|
/** "0.20×–1.92×", from whichever band the verdict was actually computed against. */
|
|
663
698
|
function ratioBand(d) {
|
|
664
699
|
const r = d.ratio ?? PUBLISHED_WIRE_TO_CLIENT_RATIO;
|
|
665
|
-
return `${r.low.toFixed(
|
|
700
|
+
return `${r.low.toFixed(BAND_PRECISION)}×–${r.high.toFixed(BAND_PRECISION)}×`;
|
|
666
701
|
}
|
|
667
702
|
/** Human output. JSON output is the report object itself. */
|
|
668
703
|
export function formatReport(report) {
|
|
@@ -861,8 +896,8 @@ export function formatReport(report) {
|
|
|
861
896
|
}
|
|
862
897
|
lines.push('');
|
|
863
898
|
lines.push('These are wire tokens — what the server puts on the wire, counted with o200k_base. What your model is billed');
|
|
864
|
-
lines.push(`differs per provider: measured ratios run ${PUBLISHED_WIRE_TO_CLIENT_RATIO.low.toFixed(
|
|
865
|
-
`${PUBLISHED_WIRE_TO_CLIENT_RATIO.high.toFixed(
|
|
899
|
+
lines.push(`differs per provider: measured ratios run ${PUBLISHED_WIRE_TO_CLIENT_RATIO.low.toFixed(BAND_PRECISION)}×–` +
|
|
900
|
+
`${PUBLISHED_WIRE_TO_CLIENT_RATIO.high.toFixed(BAND_PRECISION)}× on Anthropic requests. See docs/METHODOLOGY.md §claude-divergence.`);
|
|
866
901
|
return lines.map((l) => l.replace(/\s+$/, '')).join('\n');
|
|
867
902
|
}
|
|
868
903
|
/** Top-level tool list across every config — used by nothing yet, handy for --json consumers. */
|
package/dist/audit/deferral.d.ts
CHANGED
|
@@ -286,6 +286,41 @@ export interface WireToClientRatio {
|
|
|
286
286
|
* level down. The fields are the record; `source` dates them.
|
|
287
287
|
*/
|
|
288
288
|
export declare const PUBLISHED_WIRE_TO_CLIENT_RATIO: WireToClientRatio;
|
|
289
|
+
/**
|
|
290
|
+
* The decimal places the band is published at.
|
|
291
|
+
*
|
|
292
|
+
* Not a display choice: it is the precision a snapshot of the band has to stay
|
|
293
|
+
* accurate to, because two numbers that round the same way decide the same
|
|
294
|
+
* above/below verdict against a client threshold, and two that do not, do not.
|
|
295
|
+
* The pages print the band to this many places (`published-stats.ts`) and
|
|
296
|
+
* `bandSnapshotProblem` judges a snapshot at the same width, so "still right"
|
|
297
|
+
* means the same thing in both places.
|
|
298
|
+
*/
|
|
299
|
+
export declare const BAND_PRECISION = 2;
|
|
300
|
+
/**
|
|
301
|
+
* Is a snapshot of the band still an honest description of `derived`? The
|
|
302
|
+
* problem in words, or `null` when the snapshot may lag but does not mislead.
|
|
303
|
+
*
|
|
304
|
+
* Two callers, one rule. The suite asks it of `PUBLISHED_WIRE_TO_CLIENT_RATIO`
|
|
305
|
+
* against the run committed beside it. The release readiness gate asks it of
|
|
306
|
+
* the constant the *last release* shipped against the run on trunk — that band
|
|
307
|
+
* is what an installed package states offline, and nothing except cutting a
|
|
308
|
+
* release can move it, so a release being due is exactly what a finding there
|
|
309
|
+
* means.
|
|
310
|
+
*
|
|
311
|
+
* Deliberately not an equality check, and the first version of this rule was
|
|
312
|
+
* one: it demanded agreement and went red on the next bot commit, which had
|
|
313
|
+
* done nothing but measure a server for the first time. The run grows whenever
|
|
314
|
+
* a sweep reaches a new server, and the bot that commits it cannot edit a
|
|
315
|
+
* TypeScript constant. A snapshot is allowed to lag. It is not allowed to be
|
|
316
|
+
* wrong:
|
|
317
|
+
*
|
|
318
|
+
* - the **band** must still be right to the precision it is published at,
|
|
319
|
+
* because it decides an above/below verdict against a client threshold;
|
|
320
|
+
* - the **count** must never exceed the run, because a snapshot claiming more
|
|
321
|
+
* servers than were measured is a fabricated number rather than an old one.
|
|
322
|
+
*/
|
|
323
|
+
export declare function bandSnapshotProblem(snapshot: Pick<WireToClientRatio, 'low' | 'high' | 'servers'>, derived: Pick<WireToClientRatio, 'low' | 'high' | 'servers'>): string | null;
|
|
289
324
|
/** Derive the band from a supplied divergence run, falling back to the published one. */
|
|
290
325
|
export declare function wireToClientRatio(run?: DivergenceRun | null): WireToClientRatio;
|
|
291
326
|
/** One measured server, as the deferral arithmetic needs it. */
|
package/dist/audit/deferral.js
CHANGED
|
@@ -258,6 +258,51 @@ export const PUBLISHED_WIRE_TO_CLIENT_RATIO = {
|
|
|
258
258
|
servers: 86,
|
|
259
259
|
source: 'the published claude-opus-5 divergence run',
|
|
260
260
|
};
|
|
261
|
+
/**
|
|
262
|
+
* The decimal places the band is published at.
|
|
263
|
+
*
|
|
264
|
+
* Not a display choice: it is the precision a snapshot of the band has to stay
|
|
265
|
+
* accurate to, because two numbers that round the same way decide the same
|
|
266
|
+
* above/below verdict against a client threshold, and two that do not, do not.
|
|
267
|
+
* The pages print the band to this many places (`published-stats.ts`) and
|
|
268
|
+
* `bandSnapshotProblem` judges a snapshot at the same width, so "still right"
|
|
269
|
+
* means the same thing in both places.
|
|
270
|
+
*/
|
|
271
|
+
export const BAND_PRECISION = 2;
|
|
272
|
+
/**
|
|
273
|
+
* Is a snapshot of the band still an honest description of `derived`? The
|
|
274
|
+
* problem in words, or `null` when the snapshot may lag but does not mislead.
|
|
275
|
+
*
|
|
276
|
+
* Two callers, one rule. The suite asks it of `PUBLISHED_WIRE_TO_CLIENT_RATIO`
|
|
277
|
+
* against the run committed beside it. The release readiness gate asks it of
|
|
278
|
+
* the constant the *last release* shipped against the run on trunk — that band
|
|
279
|
+
* is what an installed package states offline, and nothing except cutting a
|
|
280
|
+
* release can move it, so a release being due is exactly what a finding there
|
|
281
|
+
* means.
|
|
282
|
+
*
|
|
283
|
+
* Deliberately not an equality check, and the first version of this rule was
|
|
284
|
+
* one: it demanded agreement and went red on the next bot commit, which had
|
|
285
|
+
* done nothing but measure a server for the first time. The run grows whenever
|
|
286
|
+
* a sweep reaches a new server, and the bot that commits it cannot edit a
|
|
287
|
+
* TypeScript constant. A snapshot is allowed to lag. It is not allowed to be
|
|
288
|
+
* wrong:
|
|
289
|
+
*
|
|
290
|
+
* - the **band** must still be right to the precision it is published at,
|
|
291
|
+
* because it decides an above/below verdict against a client threshold;
|
|
292
|
+
* - the **count** must never exceed the run, because a snapshot claiming more
|
|
293
|
+
* servers than were measured is a fabricated number rather than an old one.
|
|
294
|
+
*/
|
|
295
|
+
export function bandSnapshotProblem(snapshot, derived) {
|
|
296
|
+
const at = (n) => n.toFixed(BAND_PRECISION);
|
|
297
|
+
if (snapshot.servers > derived.servers) {
|
|
298
|
+
return `it is measured across ${snapshot.servers} servers where the run holds ${derived.servers}`;
|
|
299
|
+
}
|
|
300
|
+
if (at(snapshot.low) !== at(derived.low) || at(snapshot.high) !== at(derived.high)) {
|
|
301
|
+
return (`it states ${at(snapshot.low)}×–${at(snapshot.high)}× where the run derives ` +
|
|
302
|
+
`${at(derived.low)}×–${at(derived.high)}×`);
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
261
306
|
/** Derive the band from a supplied divergence run, falling back to the published one. */
|
|
262
307
|
export function wireToClientRatio(run) {
|
|
263
308
|
if (!run)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
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
|
-
import { wireToClientRatio } from '../audit/deferral.js';
|
|
31
|
+
import { BAND_PRECISION, wireToClientRatio } from '../audit/deferral.js';
|
|
32
32
|
import { fieldSelectionShare, isCurrent } from '../core/divergence.js';
|
|
33
33
|
import { sessionStartLoad } from '../core/session-start.js';
|
|
34
34
|
import { isGood } from './harness-guard.js';
|
|
@@ -240,7 +240,11 @@ export const PAGE_CLAIMS = [
|
|
|
240
240
|
// page-number guard: three numbers written by hand beside the two
|
|
241
241
|
// sentences regen already kept true.
|
|
242
242
|
template: 'measured at {f}×–{f}× across {n} servers)',
|
|
243
|
-
values: (s) => [
|
|
243
|
+
values: (s) => [
|
|
244
|
+
s.claude.ratioMin.toFixed(BAND_PRECISION),
|
|
245
|
+
s.claude.ratioMax.toFixed(BAND_PRECISION),
|
|
246
|
+
fmt(s.claude.ratioServers),
|
|
247
|
+
],
|
|
244
248
|
},
|
|
245
249
|
{
|
|
246
250
|
file: 'README.md',
|
|
@@ -358,13 +362,21 @@ export const PAGE_CLAIMS = [
|
|
|
358
362
|
// different sources for one number is the drift this file exists to end, so
|
|
359
363
|
// both pages state the run and the constant is guarded separately.
|
|
360
364
|
template: 'band ({f}×–{f}×\nacross {n} servers)',
|
|
361
|
-
values: (s) => [
|
|
365
|
+
values: (s) => [
|
|
366
|
+
s.claude.ratioMin.toFixed(BAND_PRECISION),
|
|
367
|
+
s.claude.ratioMax.toFixed(BAND_PRECISION),
|
|
368
|
+
fmt(s.claude.ratioServers),
|
|
369
|
+
],
|
|
362
370
|
},
|
|
363
371
|
{
|
|
364
372
|
file: 'docs/METHODOLOGY.md',
|
|
365
373
|
id: 'divergence:ratio-range',
|
|
366
374
|
template: 'it ranged from {f}× to {f}× across the {n} servers in the run,',
|
|
367
|
-
values: (s) => [
|
|
375
|
+
values: (s) => [
|
|
376
|
+
s.claude.ratioMin.toFixed(BAND_PRECISION),
|
|
377
|
+
s.claude.ratioMax.toFixed(BAND_PRECISION),
|
|
378
|
+
fmt(s.claude.ratioServers),
|
|
379
|
+
],
|
|
368
380
|
},
|
|
369
381
|
{
|
|
370
382
|
file: 'docs/METHODOLOGY.md',
|
package/dist/sweep/shard.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `history.csv` only grows for servers that get measured again, and the only
|
|
5
5
|
* scheduled measurement this project runs is the weekly self-badge job — one
|
|
6
6
|
* server, `memory`. Every other row's trend line therefore stops at whatever
|
|
7
|
-
* date a maintenance run last swept by hand. Re-measuring
|
|
7
|
+
* date a maintenance run last swept by hand. Re-measuring every one of them on a schedule
|
|
8
8
|
* is not the fix: a cold runner pays a full image pull and package install per
|
|
9
9
|
* server with no cache volume to carry over, so the whole set does not fit in
|
|
10
10
|
* one job's budget comfortably or cheaply.
|
package/dist/sweep/shard.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `history.csv` only grows for servers that get measured again, and the only
|
|
5
5
|
* scheduled measurement this project runs is the weekly self-badge job — one
|
|
6
6
|
* server, `memory`. Every other row's trend line therefore stops at whatever
|
|
7
|
-
* date a maintenance run last swept by hand. Re-measuring
|
|
7
|
+
* date a maintenance run last swept by hand. Re-measuring every one of them on a schedule
|
|
8
8
|
* is not the fix: a cold runner pays a full image pull and package install per
|
|
9
9
|
* server with no cache volume to carry over, so the whole set does not fit in
|
|
10
10
|
* one job's budget comfortably or cheaply.
|
package/package.json
CHANGED