p4ni.

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:

ConfigTools addedPrefillDelta
No MCP servers27,742baseline
serena0 (never came up)27,7420
+ context7, brave-devtools3128,320+578
+ chrome-devtools, career3428,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:

ServerToolsFull schemasPer toolNames only
context724,870 B2,435 B35 B
chrome-devtools2923,257 B802 B463 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:

ServerTools loadedBeforeAfterPer tool
chrome-devtools328,78029,667296
context7228,76630,155694

(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.