Research
An MCP Tool Costs 15 Tokens. Loading One Costs 300 to 700.
· 8 min read
On this page
Last week I took my Claude Code startup prefill apart and reported that disabling seven MCP servers moved the number by 241 tokens — which I called the noise floor. That measurement was correct and I stand by it, but the reading I invited from it was wrong. MCP servers aren’t free. They’re metered, and I hadn’t measured the meter.
Here is the other half. Same method, same machine, Claude Code 2.1.233 against Haiku 4.5, in
an empty directory with --strict-mcp-config so nothing but the config file under test is
loaded.
Startup: about 15 tokens per tool
Staging servers in one at a time, claude -p "hi" reports its own usage, and the deltas are
the price of each addition:
| Config | Tools added | Prefill | Delta |
|---|---|---|---|
| No MCP servers | — | 27,742 | baseline |
serena | 0 (never came up) | 27,742 | 0 |
+ context7, brave-devtools | 31 | 28,320 | +578 |
+ chrome-devtools, career | 34 | 28,721 | +401 |
Sixty-five tools for 979 tokens works out to about 15 tokens per tool. That is a tool
name and nothing else — enough for the model to know something called
mcp__chrome-devtools__take_screenshot exists somewhere.
That rate also settles the loose end from last week. The seven servers I disabled there were
mostly cloud connectors, several of them sitting behind an unfinished OAuth flow and exposing
nothing but an authenticate tool. That same set of servers registers 19 tools on my account
today, and 19 × 15 is 285. The number I dismissed as noise was, to within a rounding error,
the price of a name list. It looked like noise because I was comparing it against a 39,810
token prefill, not because nothing was being charged.
The zero on the second row is a different story. serena never comes up on this machine — a
direct tools/list probe times out, and a headless session asked to list every tool name
beginning with mcp__ returns NONE. A server that fails to register contributes no tools
and no tokens, and also does nothing. Mine had been in the config long enough that I’d stopped
seeing it.
One server carries 23 KB of schema
The 15-tokens-per-tool figure is small because the schema stays on the server. To see what’s
being withheld, ask the servers directly — a three-message JSON-RPC handshake over stdio gets
you tools/list. Each message is one line; the wrapping below is for display only, and a real
client must not break a message across lines:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
Measuring the response two ways — the full array, and just the names:
| Server | Tools | Full schemas | Per tool | Names only |
|---|---|---|---|---|
context7 | 2 | 4,870 B | 2,435 B | 35 B |
chrome-devtools | 29 | 23,257 B | 802 B | 463 B |
One browser-automation server carries 23 KB of JSON schema. That is what every “MCP is eating my context window” post was measuring, and those posts were not wrong — they were describing a client that pasted all 23 KB into the system prompt at startup. Modern Claude Code sends the names instead, which for those same 29 tools is roughly 435 tokens.
Note the third column, because it’s the one that matters next: a context7 tool carries three
times the schema of a chrome-devtools tool.
Loading one: 296 tokens per tool, or 694
Deferred loading means the schema arrives when a tool is needed, via a ToolSearch call, and
that arrival is not free. Running headless with --output-format stream-json exposes the usage
on every intermediate step. I ran it twice, against two servers:
| Server | Tools loaded | Before | After | Per tool |
|---|---|---|---|---|
chrome-devtools | 3 | 28,780 | 29,667 | 296 |
context7 | 2 | 28,766 | 30,155 | 694 |
(Both baselines sit a little above the 28,721 in the first table because the prompt itself is
longer than hi.)
So there is no single number. A tool costs somewhere between 300 and 700 tokens to load, and
which end you land on is set by how verbose that server’s schemas are. The ratio between the
two rows, 2.3×, tracks the ratio in the per-tool schema column above, 3.0× — close enough that
the useful heuristic is to divide a server’s tools/list byte count by about three and read
the answer as tokens. That’s a measurement you can take against your own servers in a minute,
and it will beat any per-tool figure quoted in someone else’s post, including mine.
The 296 is also in the same ballpark as the few hundred tokens per tool definition that most write-ups on this quote. Of course it is. It’s the same schema. Nothing got smaller; the delivery moved.
Which puts the exchange rate on this machine somewhere around twenty to one. A tool you
never touch costs 15 tokens. The moment you touch it you pay another 296 on top, and it stays
— the schema is in the transcript now. The step after the load still reported 29,667 rather
than falling back, though I should say I measured a short headless run that ends at DONE;
what happens to a loaded schema across compaction in a long session is not something I tested.
Run the arithmetic on a full server and the old horror stories reappear intact. Loading all 29 chrome-devtools tools in one session would be around 8,600 tokens if every tool were as heavy as the three I happened to load — the 23 KB total suggests the real figure is nearer 6,000, which is still an order of magnitude above the 435 you pay to leave it alone. Published measurements of the 93-tool GitHub server land anywhere between 18k and 55k depending on who is counting; my rates would put it somewhere in the middle of that. The tax didn’t go away. It became usage-based, with a very generous free tier for servers you keep around and rarely use.
Pruning your server list doesn’t pay
The optimization I would have reached for after last week’s post — prune the server list — turns out to be nearly worthless, and now I can put a number on why. Deleting an unused 30-tool server recovers about 450 tokens from your prefill. Deleting a server you do use recovers that plus the schemas you’d have loaded, which is the larger number by far — but you use it, so you’re not deleting it. The servers worth removing are the ones that cost almost nothing, and the ones that cost real tokens are the ones earning their place. There is no version of this list-pruning that pays.
Three things follow that I’ve actually changed:
Batch the ToolSearch call. The MCP guidance in my setup already says to load every tool a
task needs in one select: query rather than one per round-trip, and I’d read that as latency
advice. It’s weaker than it looks as token advice: I’d expect three calls for one tool each to
cost the same per schema as one call for three, though I only measured the batched case. What
batching saves is the per-call overhead and the extra assistant turns around it.
Prefer narrow queries to keyword searches. select: by exact name returns exactly what you
asked for. A keyword query like "browser screenshot" returns up to max_results matches, and
you pay a few hundred tokens for each schema that comes back, including the ones you don’t end
up calling. Guessing wrong twice can cost more than the tools themselves. This is the same
progressive-disclosure architecture I measured on the skills
side, and it has the same failure mode: cheap until
something loads the wrong thing.
Audit for dead servers. Not for cost — they’re free, that’s the problem. A server that fails to register is indistinguishable from a server whose tools you simply haven’t needed, because both contribute nothing to the prefill. Cloud connectors are the usual suspects, since their auth doesn’t survive being moved and a stale token fails quietly. The check is one headless command:
claude -p "List every tool name starting with mcp__, comma separated, or NONE." \
--model haiku --output-format json
Measuring your own
Both numbers take a couple of minutes. For the startup side, make a config file per condition and diff the prefills:
echo '{"mcpServers":{}}' > empty.json
claude -p "hi" --model haiku --output-format json --strict-mcp-config --mcp-config empty.json \
| python3 -c "import json,sys; u=json.load(sys.stdin)['usage']; \
print(u['input_tokens']+u['cache_creation_input_tokens']+u['cache_read_input_tokens'])"
For the load side, use stream-json and read the totals off consecutive steps — the prompt
needs to be a single line, since a line break inside the select: list becomes part of the
query:
claude -p "Call ToolSearch once with query 'select:mcp__context7__query-docs' then reply DONE." \
--model haiku --output-format stream-json --verbose \
--strict-mcp-config --mcp-config mcp5.json
The gap either side of the ToolSearch call is what that tool set costs you for the rest of
the session.
The broader lesson is the one I keep relearning about agent internals: the answer has a shelf life. “MCP servers flood your context” was true, then it was fixed, and the fix was a change in when you pay rather than whether. Any guidance you read about this — including last week’s post and this one — is a measurement of a specific client version, and the only durable skill is knowing which command reproduces it on your machine.