Profile local MCP server context footprint and usage.
Project description
mcp-top
mcp-top profiles your local MCP context footprint: what servers advertise, what may load into context, and which tools your agents actually use.
License: MIT · Dependencies: none (Python stdlib, 3.11+)
Install
The PyPI package is available as of v0.4.0:
uvx mcp-top
pipx install mcp-top
pip install mcp-top
Clone-and-run still works:
python bin/mcp-top
You can also run the package module directly when src is on PYTHONPATH:
PYTHONPATH=src python -m mcp_top
The Problem
MCP servers accumulate. Their advertised tools can be large, but modern clients do not all load those definitions the same way. Ordinary cost dashboards show spend rather than context composition. mcp-top shows the join that is usually missing: advertised MCP footprint, possible upfront context footprint, and actual local usage.
The Tool Search Era
Default 2026 Claude Code uses Tool Search, so fixed definition-tax numbers are the wrong model: tool names and server instructions may load upfront while full tool schemas load only when needed. Cursor has Dynamic Context Discovery outside a stable local contract. Codex has its own upstream tool-search/deferred-exposure mechanism, but whether it is active for a given server is not observable from Codex's locally readable config, so mcp-top reports Codex servers as unknown rather than guessing.
v0.4 reports a range instead of a fixed tax:
advertised_max_tokens: estimated maximum if every advertised tool definition is loaded.upfront_floor_tokens: estimated lower bound that may load upfront, including tool names, server instructions, and always-loaded definitions when observable.loading_regime:deferred,upfront, orunknown.regime_evidence: the local file/key or documented client rule that produced the regime.
unknown is a valid answer. If the local files do not prove a loading regime, mcp-top says so rather than guessing from client versions or provider defaults. Token counts remain chars/4 estimates (~), and Claude Code's documented 2KB truncation of tool descriptions and server instructions is modeled before counting.
The v0.4 measurement semantics are beta. If your client or config shape produces surprising ranges or regimes, compatibility fixtures are welcome before the v0.5 attribution work hardens the behavior.
Project-Scope Trust
A project's .mcp.json (Claude Code) or .cursor/mcp.json (Cursor) ships with the repository, not with your machine -- it is untrusted input, the same way a cloned repo's build scripts are. Claude Code itself gates project-scope servers behind workspace trust before ever launching them, so mcp-top mirrors that: project-scope servers are always listed, but never launched, by default. Coverage and the token range explain why ("project scope not queried by default; pass --query-project in trusted repos"). Pass --query-project to also query them once you trust the repository. User/home-scope servers are queried as before, with no change.
Quickstart
Example output from this machine, captured with python bin/mcp-top --timeout 5:
=== claude-code ===
Coverage: 199 transcripts found, 199 parsed, 0 skipped; 30 in window; 1698 tool calls (0 MCP); server queries: 0 ok, 3 failed, 0 not queried
server query failed context7: timeout after 5 seconds
server query failed playwright: timeout after 5 seconds
server query failed serena: timeout after 5 seconds
SERVER SCOPE TOOLS TOKEN RANGE REGIME CALLS(window) VERDICT
---------- ----- ----- ----------- ------- ------------- -------
context7 user ? ? unknown 0 review
playwright user ? ? unknown 0 review
serena user ? ? unknown 0 review
context7: definitions unavailable -- timeout after 5 seconds
playwright: definitions unavailable -- timeout after 5 seconds
serena: definitions unavailable -- timeout after 5 seconds
=== codex ===
Coverage: 149 transcripts found, 149 parsed, 0 skipped; 30 in window; 1228 tool calls (4 MCP); server queries: 1 ok, 3 failed, 0 not queried
server query failed context7: timeout after 5 seconds
server query failed playwright: timeout after 5 seconds
server query failed serena: timeout after 5 seconds
SERVER SCOPE TOOLS TOKEN RANGE REGIME CALLS(window) VERDICT
---------- ----- ----- -------------- ------- ------------- ----------------------------------------------------------------
node_repl user 3 >=~291..~1,757 unknown 0 prune -> removes advertised up to ~1,757 / upfront at least ~291
context7 user ? ? unknown 0 review
playwright user ? ? unknown 0 review
serena user ? ? unknown 4 keep
context7: definitions unavailable -- timeout after 5 seconds
playwright: definitions unavailable -- timeout after 5 seconds
serena: definitions unavailable -- timeout after 5 seconds
serena called tools:
- initial_instructions: 4
=== cursor ===
Coverage: 0 transcripts found, 0 parsed, 0 skipped; usage: unknown (no transcript adapter); server queries: 0 ok, 0 failed, 0 not queried
usage: Cursor stores chats in undocumented SQLite; no transcript adapter -- usage unknown
config warning: C:\Users\User\.cursor\mcp.json: exists but is empty -- no servers read
SERVER SCOPE TOOLS TOKEN RANGE REGIME CALLS(window) VERDICT
------ ----- ----- ----------- ------ ------------- -------
Definition token counts use the chars/4 heuristic; ~ means estimate.
Note Codex's node_repl above: unknown regime with an open >=floor..max range, not a collapsed upfront value -- Codex's own tool-search/deferred-exposure state is not observable from local config, so mcp-top no longer guesses upfront for it (see The Tool Search Era).
Prune Suggestions
Add --prune to append a suggested-removal block. It is never applied: mcp-top is read-only and only tells you which file or command to review yourself.
Captured with python bin/mcp-top --timeout 5 --prune:
Suggested removals (never applied; mcp-top is read-only -- edit configs yourself):
- [codex] node_repl (user) in C:\Users\User\.codex\config.toml -> removes advertised up to ~1,757 / upfront at least ~291
edit: Edit C:\Users\User\.codex\config.toml (user scope): set enabled = false under [mcp_servers.node_repl]. For tool-level pruning, add the unused tool name to disabled_tools in that same table.
(~ marks a chars/4 estimate, rough error +/-25%)
Prune candidates -- review before removing (actual saving unknown):
- [cursor] usage unavailable (no transcript adapter) -- no prune analysis
Definition token counts use the chars/4 heuristic; ~ means estimate.
--prune splits removals into two honest tiers:
- Suggested removals -- a global (user-scope) server with an observed advertised maximum, an observed upfront floor (both estimated tokens), zero recent calls, and no lower-precedence server that would reactivate. For Claude Code, clean suggestions get a copy-pasteable
claude mcp remove --scope ...command. For Codex, clean suggestions get structured guidance only -- the exact file,[mcp_servers.<name>]table, andenabled = falseline to add -- never a command, because a text-manipulation edit of a live TOML file can corrupt multiline strings or comments. The tool still never edits configs. - Prune candidates -- everything else that scored
prunebut cannot be recommended cleanly: a project-scoped server, a server whose removal would reactivate a lower-precedence entry of the same name, an unparsed config layer, or unsupported usage. Candidates get structured edit guidance only, not commands.
Reserved Claude Code server names (workspace, claude-in-chrome, computer-use, Claude Preview, Claude Browser) are never queried and never given a recipe: Claude Code itself skips them at load time regardless of config, so mcp-top reports them explicitly as unsupported rather than showing a phantom cost or removal suggestion.
CLIs without a usage adapter, including Cursor, are reported as "usage unavailable" rather than an empty block.
JSON
--json emits machine-readable JSON schema mcp-top/v3. Captured with python bin/mcp-top --timeout 5 --json | head -c 1800:
{"clis": [{"cli": "claude-code", "coverage": {"bad_lines_in_parsed": 0, "config_warnings": [], "duplicate_tool_use": 0, "future_sessions": 0, "in_window": 30, "mcp_tool_calls": 0, "parsed_without_timestamp": 0, "servers_queried_ok": 0, "servers_query_failed": [["context7", "timeout after 5 seconds"], ["playwright", "timeout after 5 seconds"], ["serena", "timeout after 5 seconds"]], "servers_unsupported": 0, "total_tool_calls": 1711, "transcripts_found": 199, "transcripts_parsed": 199, "transcripts_skipped": [], "unattributed_mcp_calls": 0, "unmatched_enabled_tools": [], "usage_note": null}, "servers": [{"advertised_max_tokens": null, "called_tools": {}, "calls": 0, "def_error": "timeout after 5 seconds", "def_status": "error", "filtered_tools": 0, "loading_regime": "unknown", "regime_evidence": [], "scope": "user", "server": "context7", "tool_count": null, "transport": "stdio", "upfront_floor_tokens": null, "usage_status": "measured", "verdict": "review"}, {"advertised_max_tokens": null, "called_tools": {}, "calls": 0, "def_error": "timeout after 5 seconds", "def_status": "error", "filtered_tools": 0, "loading_regime": "unknown", "regime_evidence": [], "scope": "user", "server": "playwright", "tool_count": null, "transport": "stdio", "upfront_floor_tokens": null, "usage_status": "measured", "verdict": "review"}, {"advertised_max_tokens": null, "called_tools": {}, "calls": 0, "def_error": "timeout after 5 seconds", "def_status": "error", "filtered_tools": 0, "loading_regime": "unknown", "regime_evidence": [], "scope": "user", "server": "serena", "tool_count": null, "transport": "stdio", "upfront_floor_tokens": null, "usage_status": "measured", "verdict": "review"}], "window": {"days": 30, "sessions": 30, "sessions_considered": 30}}, {"cli": "codex", "coverage": {"bad_lin
JSON v3 is a hard break from v2. Rows use advertised_max_tokens, upfront_floor_tokens, loading_regime, and regime_evidence. With --prune, each CLI entry gains a suggested_removals array with removes_advertised_max_tokens, removes_upfront_floor_tokens, recipe, reactivates, and reasons. The raw ServerConfig is never serialized, because config args and env can contain secrets.
How It Works
- Read supported CLI MCP configs from local files.
- Query each configured stdio server read-only with MCP
initializeandtools/listto fetch its actual tool inventory. - Model the server's advertised maximum and upfront floor using locally observable loading-regime evidence.
- Parse local session transcripts through a versioned adapter and count tool calls in a recent window, defaulting to the last 30 sessions and 30 days.
- Join context footprint to recent usage, rank servers, and print
keep,review,prune, orunknownverdicts.
Naive timestamps are treated as UTC. Subagent transcripts are included and grouped under their parent session for windowing.
Querying definitions launches the configured server commands. mcp-top sends read-only protocol calls and kills the processes afterward. Use --no-query to skip launching servers; definition weights are then unknown.
CLI Reference
--home PATH: home directory to inspect. Default: ~.
--project PATH: project directory to inspect for project-scoped MCP config. Default: current working directory.
--sessions N: maximum recent parsed sessions to include. Default: 30.
--days N: maximum transcript age for the usage window. Default: 30.
--timeout SECONDS: per-server stdio query timeout. Default: 20.0.
--cli NAME: CLI source to inspect: claude-code, codex, cursor, or all. Default: all. With all, only CLIs detected from local config or transcript paths are included.
--no-query: do not launch configured MCP servers. Default: off.
--query-project: also launch project-scope MCP servers (Claude project .mcp.json, Cursor project .cursor/mcp.json). Off by default because a project's config is untrusted input; pass this only for repositories you trust. Default: off.
--prune: append a suggested-removal block (human output) or a suggested_removals array per CLI (--json). Never applied. Default: off.
--json: emit machine-readable JSON schema mcp-top/v3 instead of the human table.
--version: print the installed version and exit.
Exit code 0 means the report completed. Exit code 2 means invalid usage or an unexpected internal error. Skipped transcripts are reported in coverage and are not fatal.
Honesty Rules
Coverage is always reported at the top of every output: found, parsed, skipped, skip reasons, usage-window count, tool calls, and server query status.
Token counts are labeled. ~ means an estimate from the chars/4 heuristic, with rough error bars around +/-25%. Exact and estimated counts are never silently mixed.
Ranges obey upfront_floor_tokens <= advertised_max_tokens. If definitions cannot be observed, the range is ? in human output and null in JSON.
Loading regimes carry evidence. Evidence is canonicalized to file/key/category style and never includes raw env values, command args, or secrets.
mcp-top is read-only. It never edits configs, uploads data, or sends telemetry.
Unknown Claude Code transcript format versions are reported and skipped. Codex transcript admission is structural because Codex ships frequently and records the CLI version in session metadata.
A zero-call verdict is prune only when definition cost was actually observed (estimated from the queried tool definitions). With unobserved definition cost, zero calls is review. When a CLI has no measurable usage, calls are unknown and the verdict is unknown.
--prune only presents a clean removal for a global (user-scope) server whose deletion reactivates nothing. Anything else that scored prune -- a project-scoped server, a server whose removal would reactivate a lower-precedence entry, or a CLI with an unparsed config layer -- is a review candidate with explicitly unknown actual savings, never a recommendation.
enabled_tools names that were not returned by a server's tools/list snapshot are surfaced in coverage (phrased as "not returned by this snapshot", not an absolute claim). A malformed enabled_tools/disabled_tools value that is not a list is warned about and applies no filter, rather than silently hiding every tool.
Verdicts
| Verdict | Default |
|---|---|
prune |
0 calls with an observed advertised maximum (estimated tokens) |
review |
1-3 calls, or 0 calls with unobserved definition cost |
keep |
More than 3 calls |
unknown |
Usage is unsupported for this CLI/server |
Supported CLIs
| CLI | Config inventory | Usage adapter |
|---|---|---|
| Claude Code | Full: user, user-project/local, and project scopes. User-project/local outranks project. Project scope is inventory-only by default (see Project-Scope Trust); reserved server names are inventory-only, never queried. | Full: JSONL transcript format 2.x |
| Codex | User scope ~/.codex/config.toml; project-layer .codex/config.toml is read and its servers listed as a conditional inventory -- not queried and not merged (see below) |
Rollout JSONL sessions under ~/.codex/sessions/**/rollout-*.jsonl |
| Cursor | User ~/.cursor/mcp.json and project .cursor/mcp.json inventory (project scope is inventory-only by default), plus a pointer to Cursor's native context panel for actual usage/context inspection |
Not available; Cursor stores chats in undocumented per-workspace SQLite, so usage is unknown |
A Codex project-layer .codex/config.toml is reported but deliberately not queried or merged: Codex applies project layers only to trusted projects (a state mcp-top cannot observe), reads a cascade of files from the project root down to the working directory, and field-merges same-name tables, so its per-server precedence cannot be reproduced faithfully from the published spec. See docs/v0.3-provenance-and-prune.md.
Claude Code reserves workspace, claude-in-chrome, computer-use, Claude Preview, and Claude Browser: if your configuration defines a server with one of these names, Claude Code skips it at load time. mcp-top treats them the same way -- unsupported and non-prunable, with no query and no recipe.
Roadmap
v0.5: per-project attribution, recorded-result footprint, and snapshots/diff.
v1.0: Gemini inventory, opt-in Streamable HTTP, offline CI thresholds, and schema freeze.
Non-Goals
mcp-top is not a cost dashboard; ccusage exists for that. It is not an auto-pruner and never rewrites configs. It is not an MCP security scanner. There is no hosted service.
Running Tests
bash tests/run.sh
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_top-0.4.0.tar.gz.
File metadata
- Download URL: mcp_top-0.4.0.tar.gz
- Upload date:
- Size: 80.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99b678a062f7ddd290460f98bfd8f68acc2a07c50d1dd5dd5ff5956d4bb389db
|
|
| MD5 |
28ca1a32aac1386bef22c0a4e1e18daa
|
|
| BLAKE2b-256 |
2edbb57856f98c52d8887b968f2b16a2821af776af248f76247c85f96482571b
|
Provenance
The following attestation bundles were made for mcp_top-0.4.0.tar.gz:
Publisher:
publish.yml on mohamedzhioua/mcp-top
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_top-0.4.0.tar.gz -
Subject digest:
99b678a062f7ddd290460f98bfd8f68acc2a07c50d1dd5dd5ff5956d4bb389db - Sigstore transparency entry: 2194326870
- Sigstore integration time:
-
Permalink:
mohamedzhioua/mcp-top@8fdc3ab6478d3b8224bf2d115f20be6b771b805e -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/mohamedzhioua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8fdc3ab6478d3b8224bf2d115f20be6b771b805e -
Trigger Event:
push
-
Statement type:
File details
Details for the file mcp_top-0.4.0-py3-none-any.whl.
File metadata
- Download URL: mcp_top-0.4.0-py3-none-any.whl
- Upload date:
- Size: 44.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
04bd69626c54fc73984edeaa40fac0d156722f54fe8e0b652913f67383c62b16
|
|
| MD5 |
5fab2cc64a94f3ca2091266ea01fe3b5
|
|
| BLAKE2b-256 |
e1c77de6d5eb7ea21290fc25f405d073160c5270830e91db257e1400fc731f99
|
Provenance
The following attestation bundles were made for mcp_top-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on mohamedzhioua/mcp-top
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_top-0.4.0-py3-none-any.whl -
Subject digest:
04bd69626c54fc73984edeaa40fac0d156722f54fe8e0b652913f67383c62b16 - Sigstore transparency entry: 2194326875
- Sigstore integration time:
-
Permalink:
mohamedzhioua/mcp-top@8fdc3ab6478d3b8224bf2d115f20be6b771b805e -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/mohamedzhioua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8fdc3ab6478d3b8224bf2d115f20be6b771b805e -
Trigger Event:
push
-
Statement type: