mcp-context-cost 0.8.0 → 0.9.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 +15 -10
- package/dist/cli.js +27 -12
- package/dist/core/cross-check.d.ts +93 -0
- package/dist/core/cross-check.js +176 -0
- package/dist/sweep/cross-check.d.ts +31 -0
- package/dist/sweep/cross-check.js +342 -0
- package/dist/sweep/docker.d.ts +55 -0
- package/dist/sweep/docker.js +94 -1
- package/dist/sweep/published-stats.d.ts +114 -0
- package/dist/sweep/published-stats.js +383 -0
- package/dist/sweep/regen.js +13 -0
- package/dist/sweep/report.d.ts +9 -0
- package/dist/sweep/report.js +50 -4
- package/dist/sweep/run.js +35 -6
- package/dist/sweep/sweep-all.js +41 -9
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -183,10 +183,9 @@ INCREASE FAIL:
|
|
|
183
183
|
Add `--claude` to annotate each server with its Anthropic-request cost from the published
|
|
184
184
|
[Claude divergence](docs/METHODOLOGY.md#claude-divergence) run — an exact number when the
|
|
185
185
|
published capture hash matches what you have installed, `—` (silence, not a stale guess)
|
|
186
|
-
when it doesn't. The run holds 20 rows — the top 20 measured servers by tokens
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
twentieth blank rather than stale. Most installs will show a mix:
|
|
186
|
+
when it doesn't. The run holds 20 rows — the top 20 measured servers by tokens when it ran —
|
|
187
|
+
and [results/leaderboard.md](results/leaderboard.md) prints a claude number for the 16 that
|
|
188
|
+
still match today and silence for the rest. Most installs will show a mix:
|
|
190
189
|
|
|
191
190
|
```
|
|
192
191
|
server tools tokens share claude
|
|
@@ -202,14 +201,14 @@ Flags: `--json` (full report on stdout, progress on stderr), `--budget N`,
|
|
|
202
201
|
|
|
203
202
|
The number `audit` gives you is the same measurement, run across a curated set of public
|
|
204
203
|
servers — which is how you can tell it is a measurement and not this tool's opinion. It also
|
|
205
|
-
shows what you are choosing between: across the
|
|
206
|
-
from
|
|
204
|
+
shows what you are choosing between: across the 81 servers measured, cost spans **1,700×**,
|
|
205
|
+
from `postgres` at 32 tokens to `github` at 54,622. The table below is a
|
|
207
206
|
sample of that range; the full range is in
|
|
208
207
|
[results/leaderboard.md](results/leaderboard.md).
|
|
209
208
|
|
|
210
209
|
| server | context cost | tools |
|
|
211
210
|
|---|---:|---:|
|
|
212
|
-
| github (official) | **54,
|
|
211
|
+
| github (official) | **54,622 tokens** | 44 |
|
|
213
212
|
| xcodebuildmcp | 26,594 | 24 |
|
|
214
213
|
| brave-search | 25,456 | 8 |
|
|
215
214
|
| notion | 17,500 | 24 |
|
|
@@ -217,7 +216,7 @@ sample of that range; the full range is in
|
|
|
217
216
|
| filesystem (reference) | 2,823 | 14 |
|
|
218
217
|
| markitdown | 64 | 1 |
|
|
219
218
|
|
|
220
|
-
*(
|
|
219
|
+
*(81 of 106 popular servers measured, each row dated by its own most recent sweep — full table in
|
|
221
220
|
[results/leaderboard.md](results/leaderboard.md); every failure is listed with its reason.
|
|
222
221
|
Each measured server also has a [detail page](https://athakur3.github.io/mcp-context-cost/servers/)
|
|
223
222
|
showing which tools its tokens are in.)*
|
|
@@ -252,7 +251,7 @@ exact launch command. Disputes reduce to a byte-level diff:
|
|
|
252
251
|
|
|
253
252
|
```bash
|
|
254
253
|
npx -y mcp-context-cost verify results/github/measurement.json
|
|
255
|
-
# OK github-mcp-server:
|
|
254
|
+
# OK github-mcp-server: 54622 tokens (o200k_base, methodology 1.0) — capture, hash, and count all agree
|
|
256
255
|
|
|
257
256
|
# or point it at a published measurement.json directly, no clone required
|
|
258
257
|
npx -y mcp-context-cost verify --remote https://raw.githubusercontent.com/athakur3/mcp-context-cost/main/results/github/measurement.json
|
|
@@ -267,6 +266,12 @@ failed, `2` usage error.
|
|
|
267
266
|
to the one that was published — which is exactly what `--claude` uses to decide whether it is
|
|
268
267
|
allowed to show you a number.
|
|
269
268
|
|
|
269
|
+
The number is also cross-checked against the other CLI that measures this,
|
|
270
|
+
[`sd2k/mcp-tokens`](https://github.com/sd2k/mcp-tokens): the leaderboard's **mcp-tokens**
|
|
271
|
+
column publishes its count beside ours wherever both tools saw the same tool set — same
|
|
272
|
+
o200k encoding, differences documented, the divergence published rather than left to be
|
|
273
|
+
discovered. Method: [CLI cross-check](docs/METHODOLOGY.md#cli-cross-check).
|
|
274
|
+
|
|
270
275
|
Full definition: [docs/METHODOLOGY.md](docs/METHODOLOGY.md) — what is counted, what the
|
|
271
276
|
number is *not*, config policy, failure taxonomy, frozen color bands, known divergences.
|
|
272
277
|
|
|
@@ -324,7 +329,7 @@ color bands are frozen against the observed distribution of the first full sweep
|
|
|
324
329
|
|
|
325
330
|
## Status
|
|
326
331
|
|
|
327
|
-
Active.
|
|
332
|
+
Active. Every row carries the date of its own most recent measurement. Two
|
|
328
333
|
weekly jobs re-measure the set — the `memory` reference server on Mondays, and a rotating
|
|
329
334
|
sixth of the list on Wednesdays, so every row comes round within six weeks. Read each row's
|
|
330
335
|
date as the date it means, and don't take the cadence on trust — the build history is
|
package/dist/cli.js
CHANGED
|
@@ -159,18 +159,33 @@ if (cmd === 'audit') {
|
|
|
159
159
|
}
|
|
160
160
|
const { runAudit } = await import('./audit/run.js');
|
|
161
161
|
const { formatReport } = await import('./audit/audit.js');
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
162
|
+
const { DockerHarnessFault } = await import('./sweep/docker.js');
|
|
163
|
+
let report;
|
|
164
|
+
try {
|
|
165
|
+
report = await runAudit({
|
|
166
|
+
configPaths: all('config'),
|
|
167
|
+
budget,
|
|
168
|
+
contextWindow: numeric('context'),
|
|
169
|
+
timeoutMs: numeric('timeout'),
|
|
170
|
+
concurrency: numeric('concurrency'),
|
|
171
|
+
docker: rest.includes('--docker'),
|
|
172
|
+
claude: rest.includes('--claude'),
|
|
173
|
+
divergenceUrl: argOf('divergence-url'),
|
|
174
|
+
// Progress goes to stderr so `--json` stdout stays a single parseable object.
|
|
175
|
+
onProgress: json ? undefined : (name, done, total) => process.stderr.write(` [${done}/${total}] ${name}\n`),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
// Docker failing as docker means every measurement through it would be a
|
|
180
|
+
// statement about this machine, so the audit refuses whole rather than
|
|
181
|
+
// reporting each server as broken.
|
|
182
|
+
if (e instanceof DockerHarnessFault) {
|
|
183
|
+
console.error(`audit --docker cannot answer for this machine: ${e.message}`);
|
|
184
|
+
console.error('Fix Docker here, or run without --docker.');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
174
189
|
if (report.configs.length === 0) {
|
|
175
190
|
const where = report.problems.length ? `\n${report.problems.map((p) => ` ${p}`).join('\n')}` : '';
|
|
176
191
|
const empty = report.emptyConfigs ?? [];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { Measurement } from './types.js';
|
|
2
|
+
/** Method identifier, versioned independently of METHODOLOGY_VERSION. */
|
|
3
|
+
export declare const CROSS_CHECK_METHOD = "cli-cross-check/v1";
|
|
4
|
+
/** The CLI being cross-checked against, and the release this run pins. */
|
|
5
|
+
export declare const CROSS_CHECK_CLI = "sd2k/mcp-tokens";
|
|
6
|
+
export declare const CROSS_CHECK_CLI_VERSION = "v0.2.5";
|
|
7
|
+
/**
|
|
8
|
+
* The exact analyze invocation, recorded in every run. `--model gpt-4o` is
|
|
9
|
+
* load-bearing: tiktoken-rs maps it to o200k_base, and without a model the CLI
|
|
10
|
+
* falls back to cl100k_base — a systematic difference that would swamp the one
|
|
11
|
+
* being measured (spec/upstream-notes.md, finding 1).
|
|
12
|
+
*/
|
|
13
|
+
export declare const CROSS_CHECK_CLI_ARGS: readonly ['analyze', '--provider', 'tiktoken', '--model', 'gpt-4o', '--format', 'json'];
|
|
14
|
+
export interface CrossCheckRow {
|
|
15
|
+
/** o200k count of our fresh canonical capture from this run — the headline. */
|
|
16
|
+
ourTokens: number;
|
|
17
|
+
/**
|
|
18
|
+
* o200k count of the same capture's name/description/input_schema
|
|
19
|
+
* projection — what the CLI's structs actually model, and therefore the
|
|
20
|
+
* number its count is compared against.
|
|
21
|
+
*/
|
|
22
|
+
ourMappedTokens: number;
|
|
23
|
+
/** The CLI's `.tools.total` for the same server, launched by the CLI itself. */
|
|
24
|
+
cliTokens: number;
|
|
25
|
+
ourToolCount: number;
|
|
26
|
+
cliToolCount: number;
|
|
27
|
+
/** Both tools saw the same tool names (order-insensitive) — the comparison is between like and like. */
|
|
28
|
+
toolSetMatches: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Our fresh measurement listed dynamically (tools/list differed between its
|
|
31
|
+
* own two captures). The CLI's launch is a third capture, so even with
|
|
32
|
+
* matching names its residual mixes content drift with counter disagreement
|
|
33
|
+
* — recorded, never printed.
|
|
34
|
+
*/
|
|
35
|
+
dynamic: boolean;
|
|
36
|
+
/** canonicalSha256 of our capture in this run; null when our measurement failed. */
|
|
37
|
+
capturedSha256: string | null;
|
|
38
|
+
/** Set when either side could not produce a number; nothing is published from the row. */
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface CrossCheckRun {
|
|
42
|
+
method: string;
|
|
43
|
+
cli: string;
|
|
44
|
+
cliVersion: string;
|
|
45
|
+
/** The analyze invocation, verbatim, so the encoding choice is auditable. */
|
|
46
|
+
cliArgs: string[];
|
|
47
|
+
/** UTC day the run was taken (YYYY-MM-DD). */
|
|
48
|
+
measuredAt: string;
|
|
49
|
+
isolation: string;
|
|
50
|
+
servers: Record<string, CrossCheckRow>;
|
|
51
|
+
}
|
|
52
|
+
export declare function parseCrossCheck(text: string): CrossCheckRun | null;
|
|
53
|
+
/** What the CLI's JSON report contributes to a row. */
|
|
54
|
+
export interface CliReport {
|
|
55
|
+
total: number;
|
|
56
|
+
count: number;
|
|
57
|
+
names: string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read the CLI's `--format json` report. The report is that CLI's contract, not
|
|
61
|
+
* ours, so this is deliberately narrow: `.tools.total` (the number the CLI's
|
|
62
|
+
* own action publishes), `.tools.count`, and the per-tool names — and a report
|
|
63
|
+
* that does not carry a usable total is a named problem, never a zero.
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseCliReport(text: string): {
|
|
66
|
+
report?: CliReport;
|
|
67
|
+
problem?: string;
|
|
68
|
+
};
|
|
69
|
+
/** Order-insensitive equality of the two tool-name lists, repeats included. */
|
|
70
|
+
export declare function sameToolSet(ours: string[], theirs: string[]): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Build a row from our fresh measurement and the CLI's report. The caller
|
|
73
|
+
* decides whether to run the CLI at all; a failed side arrives here as an
|
|
74
|
+
* `error`, and the row keeps whatever the other side established.
|
|
75
|
+
*/
|
|
76
|
+
export declare function toCrossCheckRow(m: Measurement, cli: {
|
|
77
|
+
report?: CliReport;
|
|
78
|
+
problem?: string;
|
|
79
|
+
}): CrossCheckRow;
|
|
80
|
+
/**
|
|
81
|
+
* A row is only printable while it compares like with like: no error on either
|
|
82
|
+
* side, the same tool names seen by both tools, and our capture still the one
|
|
83
|
+
* published — the exact staleness rule the claude column follows.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isComparable(row: CrossCheckRow | undefined, canonicalSha256: string | null): row is CrossCheckRow;
|
|
86
|
+
/**
|
|
87
|
+
* Signed divergence of the CLI's count from our count of the projection it
|
|
88
|
+
* models, in percent. Against the mapped number and not the headline: the gap
|
|
89
|
+
* to the headline is field selection, published separately per server, and
|
|
90
|
+
* folding it in here would bury the number this column exists to check —
|
|
91
|
+
* whether two independent counters agree on the fields both count.
|
|
92
|
+
*/
|
|
93
|
+
export declare function divergencePct(row: CrossCheckRow): number | null;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI cross-check — the other tool's number, published beside ours.
|
|
3
|
+
*
|
|
4
|
+
* `sd2k/mcp-tokens` measures the same thing this project measures, differently
|
|
5
|
+
* in two documented ways (spec/upstream-notes.md): its tiktoken provider picks
|
|
6
|
+
* the encoding from a `--model` argument with a cl100k_base fallback, and it
|
|
7
|
+
* counts a `serde_json` re-serialization of deserialized tool structs rather
|
|
8
|
+
* than the wire bytes. Measured (2026-09-03, addendum in the notes), the
|
|
9
|
+
* second difference is nearly all field modeling: the CLI's count lands within
|
|
10
|
+
* a fraction of a percent of our o200k count of the three-field
|
|
11
|
+
* name/description/input_schema projection — the same projection the Claude
|
|
12
|
+
* divergence starts from — not of the full capture. So the published
|
|
13
|
+
* comparison is against `mappedTokens` of the same fresh capture: like counted
|
|
14
|
+
* against like, with the field-selection gap already published separately as
|
|
15
|
+
* each server's field-selection share. The honest move is the one the notes
|
|
16
|
+
* chose on day one: publish the divergence ourselves rather than leave it to
|
|
17
|
+
* be discovered by critics.
|
|
18
|
+
*
|
|
19
|
+
* Every row is filed under the `canonicalSha256` of a capture taken by OUR
|
|
20
|
+
* client in the same run, minutes from the CLI's own launch — the session-start
|
|
21
|
+
* discipline: a re-sweep that moves the published capture marks the row stale
|
|
22
|
+
* instead of leaving a comparison against bytes that no longer exist. And a
|
|
23
|
+
* comparison is only printed when both tools counted the same tool set: the CLI
|
|
24
|
+
* launches the server itself, so on a server that changed between the two
|
|
25
|
+
* launches (or lists dynamically) the two numbers describe different schemas,
|
|
26
|
+
* and publishing their difference as "divergence" would be a category error.
|
|
27
|
+
* Those rows keep their data in the run file and print silence.
|
|
28
|
+
*
|
|
29
|
+
* Versioned independently of the o200k methodology, like `tools-delta/v1` and
|
|
30
|
+
* `deferred-load/v1`: a new published number, no change to the definition of
|
|
31
|
+
* the first. No `totalTokens` and no canonical hash moves.
|
|
32
|
+
*/
|
|
33
|
+
import { mappedTokens } from './divergence.js';
|
|
34
|
+
import { toolNames } from './session-start.js';
|
|
35
|
+
/** Method identifier, versioned independently of METHODOLOGY_VERSION. */
|
|
36
|
+
export const CROSS_CHECK_METHOD = 'cli-cross-check/v1';
|
|
37
|
+
/** The CLI being cross-checked against, and the release this run pins. */
|
|
38
|
+
export const CROSS_CHECK_CLI = 'sd2k/mcp-tokens';
|
|
39
|
+
export const CROSS_CHECK_CLI_VERSION = 'v0.2.5';
|
|
40
|
+
/**
|
|
41
|
+
* The exact analyze invocation, recorded in every run. `--model gpt-4o` is
|
|
42
|
+
* load-bearing: tiktoken-rs maps it to o200k_base, and without a model the CLI
|
|
43
|
+
* falls back to cl100k_base — a systematic difference that would swamp the one
|
|
44
|
+
* being measured (spec/upstream-notes.md, finding 1).
|
|
45
|
+
*/
|
|
46
|
+
export const CROSS_CHECK_CLI_ARGS = ['analyze', '--provider', 'tiktoken', '--model', 'gpt-4o', '--format', 'json'];
|
|
47
|
+
export function parseCrossCheck(text) {
|
|
48
|
+
let run;
|
|
49
|
+
try {
|
|
50
|
+
run = JSON.parse(text);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const r = run;
|
|
56
|
+
if (!r || typeof r.cliVersion !== 'string' || typeof r.measuredAt !== 'string')
|
|
57
|
+
return null;
|
|
58
|
+
if (!r.servers || typeof r.servers !== 'object')
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
method: typeof r.method === 'string' ? r.method : CROSS_CHECK_METHOD,
|
|
62
|
+
cli: typeof r.cli === 'string' ? r.cli : CROSS_CHECK_CLI,
|
|
63
|
+
cliVersion: r.cliVersion,
|
|
64
|
+
cliArgs: Array.isArray(r.cliArgs) ? r.cliArgs.map(String) : [...CROSS_CHECK_CLI_ARGS],
|
|
65
|
+
measuredAt: r.measuredAt,
|
|
66
|
+
isolation: typeof r.isolation === 'string' ? r.isolation : 'not recorded',
|
|
67
|
+
servers: r.servers,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the CLI's `--format json` report. The report is that CLI's contract, not
|
|
72
|
+
* ours, so this is deliberately narrow: `.tools.total` (the number the CLI's
|
|
73
|
+
* own action publishes), `.tools.count`, and the per-tool names — and a report
|
|
74
|
+
* that does not carry a usable total is a named problem, never a zero.
|
|
75
|
+
*/
|
|
76
|
+
export function parseCliReport(text) {
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(text);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// `--format json` puts the report on stdout, but a launcher inside the same
|
|
83
|
+
// pipe can precede it with noise; the report is the outermost JSON object.
|
|
84
|
+
const start = text.indexOf('{');
|
|
85
|
+
const end = text.lastIndexOf('}');
|
|
86
|
+
if (start < 0 || end <= start)
|
|
87
|
+
return { problem: 'CLI stdout carried no JSON report' };
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(text.slice(start, end + 1));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { problem: 'CLI stdout carried no parseable JSON report' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const tools = parsed.tools;
|
|
96
|
+
if (!tools || typeof tools !== 'object')
|
|
97
|
+
return { problem: 'CLI report has no .tools object' };
|
|
98
|
+
if (typeof tools.total !== 'number' || !Number.isFinite(tools.total) || tools.total < 0) {
|
|
99
|
+
return { problem: 'CLI report has no usable .tools.total' };
|
|
100
|
+
}
|
|
101
|
+
const items = Array.isArray(tools.items) ? tools.items : [];
|
|
102
|
+
const names = [];
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
const name = (item ?? {}).name;
|
|
105
|
+
if (typeof name === 'string' && name !== '')
|
|
106
|
+
names.push(name);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
report: {
|
|
110
|
+
total: tools.total,
|
|
111
|
+
count: typeof tools.count === 'number' ? tools.count : items.length,
|
|
112
|
+
names,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Order-insensitive equality of the two tool-name lists, repeats included. */
|
|
117
|
+
export function sameToolSet(ours, theirs) {
|
|
118
|
+
if (ours.length !== theirs.length)
|
|
119
|
+
return false;
|
|
120
|
+
const a = [...ours].sort();
|
|
121
|
+
const b = [...theirs].sort();
|
|
122
|
+
return a.every((name, i) => name === b[i]);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build a row from our fresh measurement and the CLI's report. The caller
|
|
126
|
+
* decides whether to run the CLI at all; a failed side arrives here as an
|
|
127
|
+
* `error`, and the row keeps whatever the other side established.
|
|
128
|
+
*/
|
|
129
|
+
export function toCrossCheckRow(m, cli) {
|
|
130
|
+
const measured = m.status === 'measured' || m.status === 'dynamic';
|
|
131
|
+
const raw = measured && Array.isArray(m.rawToolsCapture) ? m.rawToolsCapture : [];
|
|
132
|
+
const ours = toolNames(raw);
|
|
133
|
+
const row = {
|
|
134
|
+
ourTokens: measured && typeof m.totalTokens === 'number' ? m.totalTokens : 0,
|
|
135
|
+
ourMappedTokens: raw.length > 0 ? mappedTokens(raw) : 0,
|
|
136
|
+
cliTokens: cli.report?.total ?? 0,
|
|
137
|
+
ourToolCount: measured && typeof m.toolCount === 'number' ? m.toolCount : 0,
|
|
138
|
+
cliToolCount: cli.report?.count ?? 0,
|
|
139
|
+
toolSetMatches: cli.report !== undefined && measured ? sameToolSet(ours, cli.report.names) : false,
|
|
140
|
+
dynamic: m.status === 'dynamic',
|
|
141
|
+
capturedSha256: measured ? m.canonicalSha256 : null,
|
|
142
|
+
};
|
|
143
|
+
if (!measured)
|
|
144
|
+
row.error = `our measurement: ${m.status}: ${(m.notes ?? '').slice(0, 200)}`;
|
|
145
|
+
else if (cli.problem)
|
|
146
|
+
row.error = `cli: ${cli.problem.slice(0, 200)}`;
|
|
147
|
+
return row;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A row is only printable while it compares like with like: no error on either
|
|
151
|
+
* side, the same tool names seen by both tools, and our capture still the one
|
|
152
|
+
* published — the exact staleness rule the claude column follows.
|
|
153
|
+
*/
|
|
154
|
+
export function isComparable(row, canonicalSha256) {
|
|
155
|
+
return (row !== undefined &&
|
|
156
|
+
row.error === undefined &&
|
|
157
|
+
row.toolSetMatches &&
|
|
158
|
+
!row.dynamic &&
|
|
159
|
+
row.capturedSha256 !== null &&
|
|
160
|
+
canonicalSha256 !== null &&
|
|
161
|
+
row.capturedSha256 === canonicalSha256 &&
|
|
162
|
+
row.ourTokens > 0 &&
|
|
163
|
+
row.ourMappedTokens > 0);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Signed divergence of the CLI's count from our count of the projection it
|
|
167
|
+
* models, in percent. Against the mapped number and not the headline: the gap
|
|
168
|
+
* to the headline is field selection, published separately per server, and
|
|
169
|
+
* folding it in here would bury the number this column exists to check —
|
|
170
|
+
* whether two independent counters agree on the fields both count.
|
|
171
|
+
*/
|
|
172
|
+
export function divergencePct(row) {
|
|
173
|
+
if (row.ourMappedTokens <= 0)
|
|
174
|
+
return null;
|
|
175
|
+
return ((row.cliTokens - row.ourMappedTokens) / row.ourMappedTokens) * 100;
|
|
176
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ServerEntry } from './report.js';
|
|
2
|
+
import { type CrossCheckRun } from '../core/cross-check.js';
|
|
3
|
+
export declare function loadCrossCheck(root?: string): CrossCheckRun | null;
|
|
4
|
+
export declare function writeCrossCheck(run: CrossCheckRun, root?: string): void;
|
|
5
|
+
/** Release-asset triple for where the CLI will actually run. */
|
|
6
|
+
export declare function cliTriple(docker: boolean, platform?: NodeJS.Platform, arch?: NodeJS.Architecture): string;
|
|
7
|
+
/**
|
|
8
|
+
* Fetch the pinned CLI release for `triple` into a host cache, verifying the
|
|
9
|
+
* archive against the release's own `.sha256` before anything is extracted or
|
|
10
|
+
* executed. `MCP_TOKENS_BIN` overrides the whole dance — that is how the tests
|
|
11
|
+
* substitute a shim, and how an airgapped machine supplies its own copy.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureCliBinary(triple: string): Promise<string>;
|
|
14
|
+
interface CliOutcome {
|
|
15
|
+
code: number | null;
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
timedOut: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run the CLI against one server, host or containerized. In docker mode the
|
|
22
|
+
* binary is bind-mounted read-only and the CLI launches the server inside the
|
|
23
|
+
* container — same image, limits, dummy env and shared package caches as the
|
|
24
|
+
* measurement that just ran, so the npx install it pays is already warm.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runCli(binPath: string, entry: ServerEntry, opts: {
|
|
27
|
+
docker: boolean;
|
|
28
|
+
timeoutMs: number;
|
|
29
|
+
graceMs?: number;
|
|
30
|
+
}): Promise<CliOutcome>;
|
|
31
|
+
export {};
|