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.5.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 spend dashboards show money rather than context composition. mcp-top shows the join that is usually missing: advertised MCP footprint, possible upfront context footprint, and actual local usage.
Quickstart
This real output is captured by the reproducible case study in docs/examples/prune-case-study.md:
Coverage: 2 transcripts found, 2 parsed, 0 skipped; 2 in window; 3 tool calls (2 MCP); server queries: 2 ok, 0 failed, 0 not queried
recorded results: 2 paired, 0 partial, 0 unsupported, 0 unmeasurable, 0 unpaired result(s), 0 unpaired call(s) (recorded result footprint is a recorded-bytes lower bound)
SERVER SCOPE TOOLS TOKEN RANGE REGIME CALLS(window) VERDICT
------------- ----- ----- ----------- ------- ------------- ------------------------------------------------------------
unused-server user 2 >=~23..~77 unknown 0 prune -> removes advertised up to ~77 / upfront at least ~23
used-server user 2 >=~22..~76 unknown 2 review
used-server recorded result footprint: 37 recorded UTF-8 byte(s) (lower bound) -> ~10 tok (estimate) across 2 result(s) (max ~5, p90 ~5)
used-server called tools:
- first_tool: 1
- second_tool: 1
Definition token counts use the chars/4 heuristic; ~ means estimate.
The Tool Search Era
Default Claude Code can use 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.
mcp-top reports a range:
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.
Per-Project Usage
Usage is home-wide by default. Pass --project-usage to scope usage counts to the current --project.
Attribution is intentionally conservative. Claude transcripts contain a lossy project slug but no recorded current working directory, so Claude attribution is best-effort and slug-based. Codex sessions record cwd, so Codex attribution uses that recorded path after normalization. Sessions that cannot be attributed are reported and never guessed.
When --project-usage is active and the in-project window is empty, usage becomes no-data; mcp-top will not produce a confident prune verdict from an empty attributed window.
Recorded-Result Footprint
Recorded-result footprint measures result payload text that is actually present in local transcripts: recorded UTF-8 bytes converted to ~bytes/4 token estimates. It is a lower bound on recorded bytes only. It is not a measure of model context, prompt input, invoices, or removal impact.
Human output adds a per-server line when measured result bytes exist. JSON adds coverage.recorded_results and per-server recorded_result_footprint with total_bytes, total_tokens, max_tokens, p50_tokens, p90_tokens, lower_bound: true, basis: "recorded_utf8_bytes", and token_estimate: "bytes/4".
Result taxonomy:
paired: matched to a tool call and fully measured from inline text.partial: matched to a tool call; inline text was measured but opaque/non-text parts were present.unsupported: matched to a tool call but the content shape had no measurable inline text.unmeasurable: matched to a tool call but UTF-8 byte measurement failed.unpaired: result or call records that could not be matched by id.
Loading-Regime Signals
Claude loading regimes are resolved only from documented, locally observable signals:
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1is a hard off switch for Tool Search and resolves toupfront.ENABLE_TOOL_SEARCH=true|falsetakes precedence over soft provider defaults, includingANTHROPIC_BASE_URL.ENABLE_TOOL_SEARCH=autoandauto:Nrecord threshold-mode evidence and stayunknown.CLAUDE_CODE_USE_VERTEX=1is treated as a documented default-off signal whenENABLE_TOOL_SEARCHis unset.- Denying
ToolSearchresolves toupfront; scopedToolSearch(...)denial staysunknown.
There is no client-version inference. Model support is known to matter for Tool Search, but mcp-top does not encode a model allow/deny list because that would rot across model releases.
Snapshots And Diff
mcp-top snapshot writes a redacted aggregate artifact using schema mcp-top-snapshot/v1:
mcp-top --home "$HOME" snapshot --out mcp-top-snapshot.json
Snapshots are designed to be safe to commit for normal use. They are allowlist-built and omit config env, args, command, URL, filesystem paths, errors, regime evidence, prune recipes, and result content. They retain server names and called-tool names as identifiers. Before public sharing, pass --redact-identifiers to replace those identifiers with deterministic unsalted SHA-256 pseudonyms.
mcp-top diff compares two snapshots and emits schema mcp-top-diff/v1 with --json, or a human summary by default:
Snapshot diff
old generated_at: 2026-07-18T14:58:11.150732+00:00
new generated_at: 2026-07-18T14:58:11.565212+00:00
=== claude-code ===
removed: unused-server
unchanged: 1 server(s)
Diffs show added and removed servers plus per-server deltas for advertised maximum, upfront floor, calls, recorded-result total tokens, loading regime, and verdict. If windows or project scopes differ, usage and recorded-result deltas are marked incomparable instead of pretending the populations match.
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 by python scripts/examples/prune_case_study.py:
Suggested removals (never applied; mcp-top is read-only -- edit configs yourself):
- [claude-code] unused-server (user) in C:\Users\User\AppData\Local\Temp\mcp-top-prune-case-study\home\.claude.json -> removes advertised up to ~77 / upfront at least ~23
command: claude mcp remove --scope user unused-server
(~ marks a chars/4 estimate, rough error +/-25%)
Prune candidates -- review before removing (actual removal impact unknown):
none
--prune splits removals into two tiers:
- Suggested removals: a global user-scope server with an observed advertised maximum, an observed upfront floor, zero recent calls, and no lower-precedence server that would reactivate. Claude Code clean suggestions get a copy-pasteable
claude mcp remove --scope ...command. Codex clean suggestions get structured guidance only: the exact file,[mcp_servers.<name>]table, andenabled = falseline to add. - Prune candidates: everything else that scored
prunebut cannot be recommended cleanly, including project-scoped servers, reactivation risk, unparsed config layers, unsupported usage, or incomplete attribution. 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.
CLIs without a usage adapter, including Cursor, are reported as usage unavailable rather than an empty block.
JSON
--json emits machine-readable schema mcp-top/v3. v0.5 keeps mcp-top/v3 and adds fields without breaking the existing v3 contract.
Real excerpt from the case-study snapshot/report data:
{
"schema": "mcp-top/v3",
"clis": [
{
"cli": "claude-code",
"coverage": {
"project_filter_active": false,
"recorded_results": {
"paired": 2,
"partial": 0,
"unsupported": 0,
"unmeasurable": 0,
"unpaired_results": 0,
"unpaired_calls": 0
}
},
"servers": [
{
"server": "used-server",
"calls": 2,
"advertised_max_tokens": {"value": 76, "exact": false},
"upfront_floor_tokens": {"value": 22, "exact": false},
"recorded_result_footprint": {
"results": 2,
"total_bytes": 37,
"total_tokens": {"value": 10, "exact": false},
"lower_bound": true,
"basis": "recorded_utf8_bytes",
"token_estimate": "bytes/4"
}
}
]
}
]
}
JSON v3 rows use advertised_max_tokens, upfront_floor_tokens, loading_regime, and regime_evidence. v0.5 adds attribution fields on coverage, nullable coverage.recorded_results, and nullable per-server recorded_result_footprint. 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.
Snapshot JSON uses mcp-top-snapshot/v1. Diff JSON uses mcp-top-diff/v1.
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.
--project-usage: restrict usage counts to the current --project. Default: off, so usage is home-wide.
--prune: append a suggested-removal block in human output or a suggested_removals array per CLI with --json. Never applied.
--json: emit machine-readable JSON schema mcp-top/v3 instead of the human table. Also accepted before subcommands for consistent option ordering.
snapshot: emit a redacted aggregate snapshot to stdout or --out FILE.
snapshot --redact-identifiers: pseudonymize server and called-tool names.
diff OLD NEW: compare two mcp-top-snapshot/v1 files. Add --json for schema mcp-top-diff/v1.
--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, attribution facts, recorded-result classification, 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 weight was actually observed. With unobserved definition weight, 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 is a review candidate with explicitly unknown removal impact.
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." A malformed enabled_tools/disabled_tools value that is not a list is warned about and applies no filter.
Verdicts
| Verdict | Default |
|---|---|
prune |
0 calls with an observed advertised maximum |
review |
1-3 calls, or 0 calls with unobserved definition weight |
keep |
More than 3 calls |
unknown |
Usage is unsupported or no attributed sessions exist |
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; reserved server names are inventory-only, never queried. | Full: JSONL transcript format 2.x; project attribution is best-effort from the Claude project slug |
| Codex | User scope ~/.codex/config.toml; project-layer .codex/config.toml is read and listed as conditional inventory, not queried and not merged |
Rollout JSONL sessions under ~/.codex/sessions/**/rollout-*.jsonl; project attribution uses recorded cwd |
| Cursor | User ~/.cursor/mcp.json and project .cursor/mcp.json inventory; project scope is inventory-only by default |
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, 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.
Non-Goals
mcp-top is not a spend 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.5.0.tar.gz.
File metadata
- Download URL: mcp_top-0.5.0.tar.gz
- Upload date:
- Size: 113.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78e9d350c19ce38639f7b10c7a1894bc3e1d730fea4f8fd0593308ebb0b672a4
|
|
| MD5 |
26b215aeb5bc176a213549583725f61d
|
|
| BLAKE2b-256 |
acb5d2b7998429e43839ab0e80e7496ac2cf6ac8a6c6c143738bea3e78b46ba4
|
Provenance
The following attestation bundles were made for mcp_top-0.5.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.5.0.tar.gz -
Subject digest:
78e9d350c19ce38639f7b10c7a1894bc3e1d730fea4f8fd0593308ebb0b672a4 - Sigstore transparency entry: 2194778529
- Sigstore integration time:
-
Permalink:
mohamedzhioua/mcp-top@83bfa165eddbb3fb2b802c4aadf442ff7688c059 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/mohamedzhioua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83bfa165eddbb3fb2b802c4aadf442ff7688c059 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mcp_top-0.5.0-py3-none-any.whl.
File metadata
- Download URL: mcp_top-0.5.0-py3-none-any.whl
- Upload date:
- Size: 57.1 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 |
4ba59a75d224780874a38b4bb1a24e132a5663df1e1f7978a680670325c944d1
|
|
| MD5 |
bfbc767398538805632dc9fdc97e201b
|
|
| BLAKE2b-256 |
b227af9c8dbb8f872b75700bd2e576a2b55b9441ecd52e1c7ef3a50d9d89d0cb
|
Provenance
The following attestation bundles were made for mcp_top-0.5.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.5.0-py3-none-any.whl -
Subject digest:
4ba59a75d224780874a38b4bb1a24e132a5663df1e1f7978a680670325c944d1 - Sigstore transparency entry: 2194778535
- Sigstore integration time:
-
Permalink:
mohamedzhioua/mcp-top@83bfa165eddbb3fb2b802c4aadf442ff7688c059 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/mohamedzhioua
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83bfa165eddbb3fb2b802c4aadf442ff7688c059 -
Trigger Event:
push
-
Statement type: