Problem
You've identified the tool — maybe [fs-mcp] read_file, maybe your own Read tool
on a directory of screenshots — and you know it's averaging thousands of tokens per call. Now what?
"Make it return less" isn't an action you can type into a config file. Different tools need different
fixes: a search tool needs a narrower query, a file reader needs field filtering, a directory full of
PNGs needs an exclude pattern — and Token Meter never saw the arguments those calls were made with, so
it can't just tell you "add limit: 20 here." You need to know which fix applies to this
tool's shape, and then a way to confirm the average actually dropped a week later, not just a
feeling that it should have.
Cause
Every tool call's response size is estimated at ingest (response_tokens_est, stored per row
in tool_events). computeTrimSuggestions()'s LARGE_RESPONSE query
(src/trim-suggestions.ts) groups those rows by (tool_name, mcp_server) and flags
any pair averaging ≥5,000 tokens/call over ≥5 calls in the window. The
oversized_tool_response detector (D2 of 6 in token-meter audit,
src/audit/detectors/oversized-tool-response.ts) doesn't re-run that query — it calls the same
function and turns its large_response-kind results into ranked findings, so "large" means the
identical thing in both the free CLI audit and the Pro dashboard's trim-suggestions panel.
src/audit/config.ts also pre-declares OVERSIZED_RESPONSE_PERCENTILE (0.95) next
to the absolute floor, apparently anticipating a second, relative check — "large for what this specific
project usually sees," not just "large in absolute terms." That check does not exist yet:
computeTrimSuggestions() computes no percentile at all, so the detector only ever applies the
flat 5,000-token/5-call bar. A tool that's unusually large for your project but still under 5,000
tokens on average won't be flagged — see Limitations.
The reason the fixes below are "shaped like the tool's name" rather than "here's the exact argument to
change" is a deliberate privacy invariant: tool_events never stores the arguments a call was
made with (see ToolEvent in src/types.ts) — only tool_name,
mcp_server, response size/latency, and (for path-like arguments only) file_ext.
Token Meter can prove a tool runs large; it cannot see the query/limit/filter arguments that produced that
size, so it infers a fix from the tool's name instead of an inspected schema.
Command
1. Rank the candidates — token-meter audit
Flags verified against src/cli.ts. --days defaults to 7 and is clamped to your
tier's history cap; --limit defaults to 5 in terminal mode, 20 with --json:
token-meter audit --days 7 --source all --limit 10 # machine-readable, keeps every finding's metrics field for scripting token-meter audit --days 7 --source all --limit 10 --json
A pair reaches high confidence only at ≥20 calls in the window; below that it's capped at
medium, because a couple of unusually large calls can still swing a small-sample average.
2. See the binary/asset-file variant the CLI doesn't surface — Pro dashboard
computeTrimSuggestions() actually returns three suggestion kinds —
large_response, repeated_binary, high_latency — but D2 filters to
only large_response (.filter((s) => s.kind === 'large_response') in
oversized-tool-response.ts). The repeated_binary kind — reading
.png/.jpg/.pdf/etc. files as text repeatedly — never reaches the
free CLI audit at all. It's only reachable through the Pro dashboard's endpoint (local machine, no cloud
call, gated by getEntitlement()):
curl -s "http://localhost:8765/api/trim-suggestions?days=7"
Requires token-meter serve running and a Pro/Pro+ license activated — Free returns HTTP 402.
3. Verify the fix, a week later — same command, compare the numbers
Re-run the exact audit command from step 1 after you apply a fix. Match findings by
toolName + mcpServer in the two reports, not by
id — computeFindingId() (src/audit/finding-id.ts) hashes
periodStart/periodEnd into every id, so the same tool audited over two different
7-day windows always produces two different ids even when nothing about the tool changed. Compare
metrics.meanTokens across the two runs instead — that's the number the fix should have moved.
Synthetic output
Fabricated example numbers below — not real user data — to show the shape of each command's output.
token-meter audit --days 7 (terminal, 2 of 4 findings shown)
TOKEN METER AUDIT — LAST 7 DAYS Sources claude-code: available (2104 records analyzed, 100% of window covered) codex: unavailable — no data ingested yet Confirmed usage Tokens: 540.2k total (in 195.0k, out 76.0k, cache 269.2k) Estimated cost: $8.1200 Efficiency signals Findings: 4 Cost associated: $0.9325 Confidence: medium 1. [fs-mcp] read_file responses averaging 8k tokens Source: claude-code Project: /home/you/projects/docs-bot Session: - Tool: read_file Evidence: [fs-mcp] read_file returns ~8,000 tokens/call on average (62 calls in 7d window; max 21,000). Configure `fields` or `limit` on [fs-mcp] read_file to reduce response size (est. 198,400 tokens/week saved). Confidence: high Suggested action: Pre-filter [fs-mcp] read_file's output to only the fields/lines actually needed (e.g. request specific fields, or pipe through `grep`/`head`) instead of returning the full payload. 2. [internal-search] search_docs responses averaging 6.2k tokens Source: claude-code Project: /home/you/projects/docs-bot Session: - Tool: search_docs Evidence: [internal-search] search_docs returns ~6,200 tokens/call on average (14 calls in 7d window; max 9,800). Configure `fields` or `limit` on [internal-search] search_docs to reduce response size (est. 34,720 tokens/week saved). Confidence: medium Suggested action: Narrow [internal-search] search_docs's query/filter scope instead of searching or listing broadly.
token-meter audit --days 7 --json (one finding, full shape)
{
"id": "3f7a9c21b6d40e18",
"rank": 1,
"type": "oversized_tool_response",
"title": "[fs-mcp] read_file responses averaging 8k tokens",
"description": "[fs-mcp] read_file returns ~8,000 tokens/call on average (62 calls in 7d window; max 21,000). Configure `fields` or `limit` on [fs-mcp] read_file to reduce response size (est. 198,400 tokens/week saved).",
"source": "claude-code",
"project": "/home/you/projects/docs-bot",
"sessionId": null,
"toolName": "read_file",
"metrics": {
"toolName": "read_file",
"mcpServer": "fs-mcp",
"callCount": 62,
"meanTokens": 8000,
"maxTokens": 21000
},
"estimatedCostUsd": 0.7936,
"costLabel": "cost_associated",
"confidence": "high",
"evidence": [
"[fs-mcp] read_file returns ~8,000 tokens/call on average (62 calls in 7d window; max 21,000).",
"Configure `fields` or `limit` on [fs-mcp] read_file to reduce response size (est. 198,400 tokens/week saved)."
],
"recommendations": [
"Pre-filter [fs-mcp] read_file's output to only the fields/lines actually needed (e.g. request specific fields, or pipe through `grep`/`head`) instead of returning the full payload.",
"Summarize or post-process [fs-mcp] read_file's response before it enters the model context, if the full payload isn't needed downstream.",
"Check whether repeated [fs-mcp] read_file calls return duplicate or already-seen context that could be deduplicated or cached."
],
"costEventIds": ["tool:read_file:fs-mcp"]
}
curl "http://localhost:8765/api/trim-suggestions?days=7" (Pro, repeated_binary kind — not in CLI audit)
{
"days": 7,
"count": 3,
"suggestions": [
{
"kind": "repeated_binary",
"tool_name": "Read",
"mcp_server": null,
"evidence": "Read read .jpg, .png files 34 times combined in 7d (avg 3,100 tokens/call). Binary/asset files rarely need to be read as text — consider an exclude pattern.",
"savings_tokens_per_week": 52700,
"savings_usd_per_week": 0.2108,
"action_text": "Add an exclude glob pattern like `**/*.{jpg,png}` to Read in your Claude Code settings to avoid reading .jpg, .png files.",
"calls": 34,
"avg_tokens": 3100
}
]
}
Interpretation
-
metrics.meanTokens/maxTokens/callCount— these are the exact three fieldscomputeTrimSuggestions()already computed for its ownevidencestring; D2 doesn't re-derive them, it just exposes them as separate numbers so you (or a script) can compare them run-over-run without parsing prose. -
Terminal output only ever shows
recommendations[0].formatTerminal()(src/audit/reporters/terminal.ts) prints a single "Suggested action" line from the first entry in therecommendationsarray — butbuildRecommendations()can return two to four entries (a summarize/post-process reminder and a dedup check are appended after the shape-specific fix). If you only ever read the terminal output, you're missing part of the advice — run--jsonand read the fullrecommendationsarray, or open the finding on the (Pro) dashboard. -
estimatedCostUsdundercostLabel: 'cost_associated'is a projected weekly savings estimate, not money already spent — it assumes a flat 40% of the average response is reducible (savingsTok = callsPerWeek * avg_tokens * 0.4), and the per-token USD rate it's multiplied by (avgTokenCostPerEvent()) is a single blended average —AVG(usd_estimate) / AVG(input+output tokens)across your entire window, every model mixed together — not the specific model this tool's caller happened to be using. Treat the dollar figure as "roughly this order of magnitude," not an invoice line. -
The
repeated_binarycurl example has nomax_tokenskey. Only theLARGE_RESPONSEquery computesMAX(response_tokens_est); theREPEATED_BINARY/HIGH_LATENCYbranches leave itundefined, whichJSON.stringifysimply omits — that's not a redaction, the query never computed a max for that suggestion kind.
Actions
buildRecommendations() (src/audit/detectors/oversized-tool-response.ts) picks a
fix set from the tool's name alone — matched against two regexes — because that's the only shape
hint available without an argument schema. Check which bucket your flagged tool falls into first:
-
Search/list-shaped — name matches
/search|grep|find|query|list/: narrow the query/filter scope instead of searching broadly; use alimit/max_results/top_kargument if the tool exposes one; paginate across multiple calls instead of requesting everything at once. -
Read/fetch/exec-shaped — name matches
/read|get|fetch|cat|show|view|download|export|bash|exec|run|shell|command/: request specific fields instead of the full payload, or pipe throughgrep/headwhere the tool supports it. - Neither pattern matches (an opaque or custom-named MCP tool) — Token Meter doesn't guess at arguments it can't confirm exist. The only recommendation you'll get is "review why this returns a large payload and whether the caller can request less of it." That's the honest limit of a name-only heuristic, not a missing feature.
- Always, regardless of shape — once a tool is confirmed to run large, ask whether the full response is even used downstream. If it's read once and mostly discarded, a summarization or post-processing pass before the payload enters context is worth more than tuning the call itself.
-
If the tool matched either shape above, also check whether repeated calls in the same
session return duplicate or already-seen context — that's the
repeated_similar_tool_calls(D4) signal. Read its argument-approximation caveat in Limitations before treating a D4 finding as proof of an identical call. -
Binary/asset files (
REPEATED_BINARY, step 2 above) — oncefile_extconfirms the extensions (png/jpg/pdf/zip/mp4/font files/etc., the full list isBINARY_EXTENSIONSintrim-suggestions.ts), add the exact exclude glob the suggestion'saction_textgives you (e.g.**/*.{jpg,png}) to your Claude Code settings. Iffile_extisNULLon the flagged rows (older data ingested before that column existed, or a call with no path-like argument to extract an extension from), the same detector falls back to a plain "called ≥10 times" heuristic on read-named tools (HIGH_FREQ_READ_THRESHOLD) instead — re-runtoken-meter ingest --forceto backfillfile_extand get the real extension list rather than a guess. -
Verify it worked. Re-run
token-meter auditnext week. Success looks like either the(toolName, mcpServer)pair dropping out of the report entirely (it fell below the 5-call or 5,000-token bar) ormetrics.meanTokensbeing materially lower than the run before. Match by tool + server, not by findingid— see Command, step 3.
Limitations
-
Recommendations are name-shape guesses, not verified capabilities. Token Meter has no
visibility into a tool's actual argument schema (that's the privacy invariant in the Cause section) —
"use a
limitargument" is inferred from the tool's name looking read/list-like, not confirmed by inspecting what the tool accepts. Check the tool's own documentation before assuming it supports the parameter the recommendation names. -
Only the absolute floor is enforced.
OVERSIZED_RESPONSE_PERCENTILE(0.95) is declared insrc/audit/config.tsbut never wired into any query —computeTrimSuggestions()computes no percentile at all. A tool that's unusually large relative to your other tools but still under the flat 5,000-token/5-call bar will not appear in either the CLI audit or the Pro dashboard's trim suggestions. - The 40% "reducible" assumption and the blended USD rate are heuristics, not measurements. Neither number is derived from what actually happened after you applied a fix — they're a flat projection computed once, at query time, from the tool's current average.
-
repeated_binarysuggestions don't reach the free CLI audit. D2 filterscomputeTrimSuggestions()'s output down to thelarge_responsekind only; the binary/asset exclude-glob suggestions currently require the Pro dashboard endpoint (Command, step 2) — there's no free-tier equivalent tocurl .../api/trim-suggestionstoday. -
--source/--projectscoping on D2 is best-effort. The underlying(tool_name, mcp_server)aggregate has no source/project columns of its own, so a finding's reportedsource/projectechoes what you asked for rather than guaranteeing the aggregate itself is confined to it — a tool called from both Claude Code and Codex, or from two different projects, can be silently mixed into one finding. -
"Repeated calls" (D4) is an approximation, by design. There are no stored arguments to
compare, so
repeated_similar_tool_callsclusters calls by same tool + timing proximity (within 5 minutes) + response-size similarity (within 10%) as a proxy for "probably did something similar repeatedly." It is not proof of identical calls; confidence is capped atmediumfor exactly this reason. -
Codex tool calls never carry MCP-server attribution.
codex-parser.tshardcodesmcp_server: nullfor every Codex tool event, so a--source codexaudit can still flag an individual tool as oversized, but never scoped to a specific MCP server the way a Claude Code finding is — everything Codex-side collapses to "this tool name," MCP or built-in indistinguishable. -
Finding
ids are not stable across two audits taken at different times.computeFindingId()hashesperiodStart/periodEndinto the id, so re-running the exact same command a week later produces a new id for the same tool even if nothing changed. Diff bytoolName+metrics.mcpServer, not byid.
Related guides
- Find your most expensive Claude Code sessions
- Which MCP server is eating your Claude Code tokens?
- Find the MCP tools slowing your sessions down
- Track Codex token cost alongside Claude Code
← Token Meter home · Source on GitHub · Full MCP server docs