Skip to content

feat(context-budget): surface unused MCP server overhead per project#79

Open
lfl1337 wants to merge 8 commits intogetagentseal:mainfrom
lfl1337:feat/mcp-token-overhead
Open

feat(context-budget): surface unused MCP server overhead per project#79
lfl1337 wants to merge 8 commits intogetagentseal:mainfrom
lfl1337:feat/mcp-token-overhead

Conversation

@lfl1337
Copy link
Copy Markdown
Contributor

@lfl1337 lfl1337 commented Apr 18, 2026

Summary

Closes #2. The context budget now distinguishes between MCP servers declared in config and MCP servers actually called during the scanned period, and surfaces the waste as both a dashboard signal and enhanced wording in codeburn optimize.

Changes

  • ContextBudget.mcpTools extended with declared, used, unused, unusedTokens
  • estimateContextBudget(cwd, modelContext, calledServers?) - third parameter optional, backward compatible
  • dashboard.tsx loadBudgets aggregates mcpBreakdown keys per project and passes them to the estimator
  • Project-breakdown overhead column turns orange when unused servers are detected
  • detectUnusedMcp explanation now shows per-session token cost in addition to the total
  • countMcpTools also reads ~/.claude.json - both the top-level mcpServers (user scope) and projects[cwd].mcpServers (project scope). This is where claude mcp add <name> writes config, and the previous settings.json-only discovery missed it.

Scope

Currently Claude Code only. Codex (~/.codex/config.toml TOML), Cursor (~/.cursor/mcp.json), and OpenCode (~/.config/opencode/opencode.json) each use their own MCP config format. Multi-provider MCP config reading is tracked as a follow-up PR because each format needs its own reader and the --provider flag scoping deserves its own design discussion.

Before / After

Dashboard overhead column stays blue when all declared MCP servers are in use, orange when at least one is declared but never called.

codeburn optimize finding:

Never called in this period: filesystem, github.
Estimated overhead: ~4.0k tokens/session (320k tokens total across 80 sessions).

Test plan

  • npm test green (240/240, +9 new tests covering calledServers breakdown and ~/.claude.json discovery)
  • Existing detectUnusedMcp behavior unchanged when no MCPs are configured
  • plugin:foo:bar server names correctly normalize to plugin_foo_bar for comparison with session data
  • ~/.claude.json top-level mcpServers picked up
  • ~/.claude.json projects[cwd].mcpServers picked up
  • Forward-slash key lookup works on Windows paths
  • .mcp.json + ~/.claude.json merged into union set
  • Visual confirmation on real session dir

lfl1337 added 7 commits April 18, 2026 15:48
Claude Code CLI (`claude mcp add ...`) writes MCP server configs to
~/.claude.json, not to settings.json. The existing settings.json-only
discovery missed this common config path, making the unused-server
detection effectively invisible for users who configure MCPs via the CLI.

Reads both the top-level mcpServers (user scope) and the per-project
projects[cwd].mcpServers block (local scope). Falls back to a slash-
normalized key lookup to match Windows path variants.

Scope: Claude Code only. Codex/Cursor/OpenCode MCP configs use different
file formats (TOML, custom JSON) and need their own reader; tracked as
follow-up.
Mirrors the countMcpTools fix (previous commit) in the sibling
loadMcpConfigs used by detectUnusedMcp. Without this, the optimize
view never flagged unused MCP servers for users who configure MCPs
via claude mcp add (which writes to ~/.claude.json, not settings.json).

Reads top-level mcpServers for user scope and projects[cwd].mcpServers
for local scope, with slash-normalized lookup for Windows paths.

Also adds a file-level beforeEach that cleans stale ~/.claude.json
from the mocked home directory between tests to prevent state bleed
across test suites.
@lfl1337
Copy link
Copy Markdown
Contributor Author

lfl1337 commented Apr 18, 2026

Closing this pending further review - the feature doesn't surface MCP servers as expected in their setup. Will reopen after investigating the actual rendering path.

@lfl1337 lfl1337 closed this Apr 18, 2026
@lfl1337
Copy link
Copy Markdown
Contributor Author

lfl1337 commented Apr 18, 2026

image

@lfl1337 lfl1337 reopened this Apr 18, 2026
@github-actions
Copy link
Copy Markdown

firstlook

Signal Detail
Account Created 5y 1mo ago
Repos 8 public
Profile None provided ⚠️
History 3 merged, 0 rejected elsewhere
Merge quality 1 unique mergers, 1 repos with 100+ stars
Activity 2/12 months active ⚠️
Followers 1 ⚠️
Signed No ⚠️

⚠️ Dormant Reactivation -- Active 2/12 months despite 5y account

Review Suggested (score: 39/100) -- Limited history or new account. Review with extra care.

Details
  • Self-merged: 3 | Externally merged: 5
  • Contributed to 1 repos with 100+ stars
  • This repo: 5 merged, 2 closed

firstlook

@Qodo-Free-For-OSS
Copy link
Copy Markdown

Hi, In src/context-budget.ts, countMcpTools treats an empty calledServers Set the same as calledServers being undefined and returns unused=[], so projects that made zero MCP calls will never report unused declared servers or unusedTokens.

Severity: action required | Category: correctness

How to fix: Differentiate undefined vs empty

Agent prompt to fix - you can give this to your LLM of choice:

Issue description

countMcpTools() currently returns { unused: [], unusedTokens: 0 } when calledServers is either undefined or an empty Set. This prevents surfacing unused MCP servers for projects that had sessions scanned but made zero MCP calls in the period.

Issue Context

The dashboard passes calledServers as a Set aggregated from session.mcpBreakdown keys; when there were no MCP calls, this Set is empty but still represents known usage data.

Fix Focus Areas

  • src/context-budget.ts[97-113]
    • Change the guard from !calledServers || calledServers.size === 0 to only treat calledServers === undefined (or null, if you choose) as “no usage data”.
    • When calledServers is provided (even empty), compute unused as all declared serverNames not in calledServers and set unusedTokens accordingly.
  • tests/optimize-fs.test.ts[500-511]
    • Update/add a test for calledServers = new Set() with declared servers to ensure it reports all as unused.
  • src/dashboard.tsx[621-627]
    • No change required if semantics are updated; ensure behavior matches the PR intent (0 MCP calls => all declared are unused).

Found by Qodo code review. FYI, Qodo is free for open-source.

…empty set

An empty Set means zero MCP calls were made in the period - all
declared servers are unused. Previously this case was collapsed with
calledServers=undefined (no usage data), so countMcpTools returned
unused=[] instead of the full server list.

Adds a test covering the empty-Set path.
@lfl1337
Copy link
Copy Markdown
Contributor Author

lfl1337 commented Apr 28, 2026

Fixed in 3357df4.

The root cause: countMcpTools collapsed calledServers === undefined (no usage data available) and calledServers.size === 0 (data present, zero calls made) into the same early return, so projects with zero MCP calls never reported any unused servers. Split into two distinct branches -- undefined returns empty unused list (backward compat), empty Set returns all declared servers as unused.

Added a test covering the empty-Set case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: show token overhead from unused MCP tool definitions

2 participants