- measured: a tokenizer run over the exact string the tool returned.
- extrapolated: computed from a measured per-row cost, because no seed held enough rows.
- derived: computed from a measured per-item rule, stated in prose below rather than in the table.
The ladder
How it was measured
The baseline seed is a server run with-no-auth and a long shape retention, holding the spec’s
conformance trace (17 steps, root_status: error) ingested 60 times with fresh trace ids, read
back through the tools themselves. Seven rows needed a seed of their own, each named in the last
column: the logs row ingested the spec’s three-entry logs fixture, the three-caller fan-out ran
against a purpose-built fixture of five synthetic traces naming one function as root once and as
a child four times under two parents, the three 100-row rows needed 101 freshly dated traces
because 60 cannot fill a 100-row page, and the two related-trace rows with a matched window
needed traces young enough to fall inside 24 hours. A difference between two rows is a
difference in cost only when they share a seed.
Bytes count the one-line tool result text, which is what enters an AI agent’s context, not the
HTTP body it was parsed from, and not the jq . renderings the other pages show. Text and body
agree within a byte except where the process adds something of its own:
the empty-state rewrite on vexlo_get_function_examples, about 12 bytes an example, and
vexlo_find_related_traces’s staleness note, which the two related rows above put at 321
bytes including its key.
Tokens come from a tokenizer run over that same string, never from its length divided by a
constant. The tokenizer is @anthropic-ai/tokenizer 0.0.4, a published byte-pair encoding from
the Claude 2 generation, because no current model’s tokenizer is published and counting with a
current one needs credentials this measurement did not have. Their error against a current
model is unmeasured in size and direction, so read any threshold here to the nearest ten
tokens, and two rows within a few percent of each other as needing a fresh measurement.
The startup handshake is excluded from every row: it is 672 bytes and 224 tokens on the server
these pages were captured against, paid once per process launch and never inside a tool result.
What the numbers decided
A constant divisor would not do. The ratio runs from 2.5 bytes per token onvexlo_get_function’s dense nested objects to 4.3 on a logs slot of English prose. On the
densest row, 991 bytes and 399 tokens, dividing bytes by four gives 248: 62% of the real count,
so the estimate falls 38% short in the one direction a claim about cheapness must not be wrong
in. The two columns do not even rank the calls alike. Read down the table, which ascends by
tokens, and the byte figure falls five times; across every pair of rows, nine pairs invert.
vexlo_search_traces defaults to 10 traces rather than the API’s 50 because of two rows here:
10 rows cost 1,473 tokens and 50 cost 7,127, which is more than one whole trace’s shape for a
page an agent has not decided to read yet.
vexlo_get_trace and vexlo_search_traces are the expensive members by construction, one
returning a whole call tree and the other enumerating traces. Pushed to their ceilings, the two
widest pages cost over 14,000 tokens, four times one trace’s shape. One ceiling is wider still
and no row measures it: vexlo_list_functions accepts limit: 200, and at the 87 tokens a row
its 17-row page gives, 200 rows is roughly 17,400 tokens, derived. Size a context window against
that figure, not against the 50-row row. vexlo_get_function scales with distinct caller and
callee edges rather than call count. Its two measured fan-outs, five edges at 991 bytes and
seven at 1,297, put each further edge at about 153 bytes and 60 tokens; they sit on different
seeds, so treat that as an estimate rather than a measurement. Thirty edges is then roughly
4.8 KB and 1,900 tokens, derived.
The discovery tools are cheap enough to call freely before trusting an empty result:
vexlo_list_tags, vexlo_check_coverage, and an empty window-matched
vexlo_find_related_traces all sit under 70 tokens, and vexlo_check_health under 115.
vexlo_check_trace_status is the one to watch, at 180 tokens for a single id, because what it
returns is a full trace-summary row.
The same calls on another seed
Fourteen of these calls were re-measured on the bearer server the rest of these pages were captured against, which holds four demo traces and one fixture trace rather than 60 clones. Six came back byte-for-byte and token-for-token identical:vexlo_list_tags at one tag,
vexlo_check_health at one service, vexlo_get_values at one path and at eight leading paths,
and both empty vexlo_find_related_traces answers. The other eight moved. Where another MCP page
quotes one of these calls, it quotes the right-hand column:
Only the last two rows moved because the software changed; the other six moved because the data
did. Both
vexlo_get_trace orders now return 17 rows for the 17-step trace, so slowest_first
is a reordering rather than a longer response.