The low-coverage and project-scope findings only see servers whose tools were
deferred, yet charged 400 tokens per tool and counted every call as both a
cache write and a cache read. A deferred tool is one name line (~9 tokens),
and each call now pays it as a read, or as a write only when it read no cache.
Extra High on #1049: keep the shipped Claude Code empty-state and
TUI header strings; resolve agent nouns from Provider.displayName;
cover all five cross-provider detector labels.
The prefix alone could not tell a claude.ai connector from a local MCP
server that happens to be named claude_ai_*, so the latter lost its
removal command and its apply plan. A namespace is now a connector only
when no readable local config claims that exact name; localMcpServerNames
supplies the set from the same files the remove plan edits (settings.json,
.mcp.json, and ~/.claude.json top-level plus per-project mcpServers).
A prefixed name that local config does own stays local: normal removal
command, apply plan, class fix, full savings attribution. Because the
transcript still cannot rule out a same-name connector, the finding adds a
manual note about it instead of asserting the server is one. Config that
cannot be read contributes no names, which leaves every prefixed namespace
on the conservative connector path.
The rewritten cost pass charges each session only for the schemas that
session loaded, which changes local-only estimates too (on a real corpus
the mcp-low-coverage estimate roughly halves). Pin it so the change is
deliberate rather than a side effect of the connector split.
A mixed local + claude.ai connector finding is class `fix`, but `--apply`
only mutates the local servers. classTotals now credits the `fix` group
with `applyTokensSaved` when present, so the "Fix now (apply-able)"
subtotal, the "apply-able: ~$X" headline and `summary.byClass.fix` (CLI,
TUI and desktop all read these) describe what apply can actually recover.
The finding keeps the whole opportunity in its own `tokensSaved`.
Also fixes the desktop connector fixture, which predated the class/basis
fields, and adds class-level coverage: connector-only findings resolve to
`nudge` (no apply payload), a local server named like a connector stays
manual-only, and local-only findings keep their full subtotal.
Flags MCP servers useful in 1-2 projects but loaded into cold projects
where they are never invoked. Recommends project-scoping to preserve
hot workflows while reducing schema overhead elsewhere.
- Use 1.25x multiplier for cache-write tokens to match Anthropic's
actual pricing (was incorrectly using 1x)
- Shell-quote server names in `claude mcp remove` fix text to prevent
issues with unusual server names
Adds a per-tool optimizer finding for MCP servers whose schema is loaded
on every turn but rarely invoked. Builds on the existing server-level
`detectUnusedMcp` (zero invocations) by reporting partial-use cases:
"loaded 54 tools, called 0" or "loaded 26 tools, called 2 (8% coverage)".
Inventory comes from Claude Code's JSONL `attachment.deferred_tools_delta`
entries: `addedNames` lists the exact tools available at that turn,
including every fully-qualified `mcp__<server>__<tool>` name. We union
across all delta entries in a session (not just the first) because tool
availability can change mid-session when the user reloads MCP config or
a subagent inherits a different tool set. Names that don't match the
`mcp__<server>__<tool>` shape with both segments non-empty are rejected
at extraction so downstream `split('__')` consumers can't be poisoned.
Token-savings estimates are cache-aware. MCP tool schemas live in the
cached prefix of the system prompt: a session pays the full input price
on each cache-creation turn (rebuilds happen every ~5 minutes of
inactivity) and the cache-read discount on subsequent turns. Each call's
contribution is capped at its observed `cacheCreationInputTokens` /
`cacheReadInputTokens` so we never claim more MCP overhead than the
call's own cache buckets could contain.
When multiple servers are flagged, costing happens in a single combined
pass: the per-call cap applies to the total unused-schema budget across
all flagged servers, not per server. Two flagged servers cannot both
independently claim the same call's cache bucket, which would otherwise
overstate `tokensSaved` and misclassify findings as high impact.
A session counts toward `loadedSessions` (and toward the cost estimate)
only if its observed inventory included the server. Pure invocation-only
sessions, where the server appears in `mcpBreakdown` or `call.mcpTools`
without any matching `deferred_tools_delta`, do not satisfy the
`>= 2 sessions` threshold on their own. The same invariant applies in
`estimateMcpSchemaCost` so the two passes agree.
Coverage is computed against the inventory only: invocations of names
not present in any observed inventory (older config, hallucinated tool,
typo) do not inflate `toolsInvoked` and cannot drive `unusedCount`
negative. `toolsInvoked` is derived as `inventory.size - unusedTools.length`
to keep both numbers consistent.
`detectUnusedMcp` and the new detector are explicitly disjoint:
`detectUnusedMcp` skips servers that the coverage detector will report,
not every server that happens to be in any inventory, so a small
inventoried-but-uninvoked server below the coverage thresholds still
gets flagged as "configured but never called."
Thresholds for the coverage finding:
- > 10 tools available (small servers are noise)
- < 20% coverage
- >= 2 sessions with observed inventory
- High impact when total effective tokens >= 200_000 or >= 3 servers flagged
Smoke-tested on a real account: 7 servers flagged across 93 sessions
(`office-word-mcp` 0/54, `notebooklm-mcp` 0/38, `office-ppt-mcp` 0/37,
`excel-mcp-server` 0/25, `github-mcp-server` 2/26, `peekaboo` 3/22, plus
`claude_ai_Asana`). Combined-cap costing keeps `tokensSaved` honest.
Changes:
- src/types.ts: optional `mcpInventory: string[]` on `SessionSummary`.
Provider-agnostic field; currently populated only by the Claude parser.
- src/parser.ts: `extractMcpInventory` walks all entries, validates
fully-qualified names, returns sorted unique list. `buildSessionSummary`
passes it through; field is omitted when empty so JSON exports stay
clean.
- src/optimize.ts: `aggregateMcpCoverage`, `estimateMcpSchemaCost`
(single- and multi-server signatures), `detectMcpToolCoverage`. Wired
into `scanAndDetect`. `detectUnusedMcp` updated to disjoint with the
new detector.
- tests/mcp-coverage.test.ts: 23 cases covering aggregation, costing,
combined-cap behaviour, threshold gates, invocation-only-session
filtering, foreign-tool invocations, cache rebuild events, write+read
on the same call, multi-server pluralisation.
- tests/parser-mcp-inventory.test.ts: 12 cases for the JSONL extractor
including malformed name rejection and tolerant attachment parsing.
- CHANGELOG.md: entry under Unreleased / Added (CLI).
Closes#2