DEV Community

Cover image for How much MCP token overhead you pay depends on the harness
ImportStatic
ImportStatic

Posted on Originally published at importstatic.com

How much MCP token overhead you pay depends on the harness

Originally published on ImportStatic.

The issue-writing tool in GitHub's MCP server costs 807 tokens to describe to a model. The help page for gh issue create costs 869. So much for the idea that an MCP tool is inherently heavier than a CLI command.

That comparison comes from a small Go program we wrote after reading a Hacker News discussion of a post about an agent harness adding MCP support. One side said MCP eats context on every turn whether you use the tools or not. The other said it depends on the harness. That's an argument a measurement can settle. Both sides turn out to be right, and the MCP token overhead is easy to measure: GitHub's server with its default settings is 11,207 tokens of tool definitions, and whether those tokens reach the model is now a harness setting.

What we counted, and with which tokenizer

The program starts a server over stdio with the official Go SDK, lists its tools, and serializes each one the way a model API wants it: name, description and input schema. Then it counts tokens.

tiktoken.SetBpeLoader(tiktoken_loader.NewOfflineLoader())
enc, err := tiktoken.GetEncoding("o200k_base")
if err != nil {
    log.Fatal(err)
}
count := func(s string) int { return len(enc.Encode(s, nil, nil)) }
Enter fullscreen mode Exit fullscreen mode
for tool, err := range session.Tools(ctx, nil) {
    if err != nil {
        log.Fatal(err)
    }
    def := functionTool{Name: tool.Name, Description: tool.Description, InputSchema: tool.InputSchema}
    defs = append(defs, def)
    r := row{
        name:   tool.Name,
        total:  countJSON(def),
        desc:   count(tool.Description),
        schema: countJSON(tool.InputSchema),
    }
Enter fullscreen mode Exit fullscreen mode

Two caveats before the numbers. The tokenizer is o200k_base, which OpenAI's tiktoken table maps to the gpt-4o, gpt-4.1, gpt-5 and o-series names. Claude models have their own tokenizer, and the way to count with it is Anthropic's token counting endpoint. It's free and accepts a tools array, but it needs an API key and this machine doesn't have one. So these are OpenAI-encoding counts of compact JSON. Second, a provider is free to render tool definitions into the prompt in its own format, so what the model sees can differ from the JSON you send. Treat the figures as close, not exact.

All four servers were built from source at their latest tagged versions as of October 6, 2026, and started with dummy credentials. tools/list doesn't need real ones.

The MCP token overhead of four real servers

Server Tools Definitions Per tool
gopls v0.23.0 (gopls mcp) 8 878 tokens 109
terraform-mcp-server v1.3.0 9 1,718 tokens 190
github-mcp-server v1.14.0, defaults 46 11,207 tokens 243
mcp-grafana v1.6.3 81 20,493 tokens 253
github-mcp-server v1.14.0, --toolsets all 91 23,206 tokens 255

Small servers are cheap. gopls gives a model eight tools for less than a page of text. The trouble starts when a server mirrors a whole product API: GitHub with every toolset plus Grafana is 43,699 tokens before the user has typed anything.

That lines up with what Anthropic says. Its tool search documentation gives the example of a five-server setup (GitHub, Slack, Sentry, Grafana and Splunk) that "can consume ~55k tokens in definitions before Claude does any work." Different tokenizer, different server versions, same order of magnitude.

And yes, without deferral you pay it on every request. Model APIs are stateless. Anthropic's pricing notes say tool use is billed on input tokens "including in the tools parameter", and that the parameter's tokens are the "tool names, descriptions, and schemas". On top of that sits a fixed tool-use system prompt, which Anthropic lists as 286 tokens for Claude Opus 5.5 and Sonnet 5.5. Next to 11,207, that part is noise.

Most of it is JSON Schema, not prose

Here's the full output for the GitHub server with default settings:

server            github-mcp-server v1.14.0
tools             46
definitions       11207 tokens (243 per tool)
  descriptions    1368 tokens
  input schemas   9287 tokens
output schemas    0 tokens on 0 tools (not included above)
annotations       964 tokens (not included above)
instructions      377 tokens
names only        185 tokens
names + 1st line  1190 tokens
largest tools:
  issue_write                                807 tokens (description 14, schema 783)
  pull_request_read                          560 tokens (description 12, schema 537)
  list_issues                                549 tokens (description 33, schema 505)
smallest tool:
  get_me                                      54 tokens (description 35, schema 9)
Enter fullscreen mode Exit fullscreen mode

The tool descriptions, the part people imagine when they say "tool descriptions waste tokens", are 12% of the total. The input schemas are 83%.

Look at issue_write. Its description is one sentence, 14 tokens. Its schema is 783, because it has seventeen properties and several of them carry a paragraph of usage rules. This is one property out of the seventeen, copied from the server's response:

"parent_owner": {
  "description": "Repository owner of the parent issue. Must be provided with parent_repo. Omit both to use owner and repo. Only used when method is 'create' and parent_issue_number is provided.",
  "type": "string"
}
Enter fullscreen mode Exit fullscreen mode

Grafana's server leans the other way, with 5,777 tokens of descriptions against 13,601 of schema, but schemas still dominate.

If you write a server, that's where to look. Merging "create" and "update" into one tool with a method argument, as GitHub does here, saves a tool slot and then spends the saving on parameters that only apply to one of the two methods. I wouldn't call that wrong. It's a trade, and it's worth knowing which side of it your tokens are on.

One more line in that output deserves a look: instructions. Servers can return free-text instructions when a client connects. GitHub's are 377 tokens with default toolsets and 1,559 with all of them. Terraform's are 924, more than half the size of its nine tool definitions. If you audit a server, count those too.

A CLI isn't smaller, it's just lazy

The usual counter-proposal is to skip MCP and let the agent run gh. We built gh v2.102.0 from source and counted its help output with the same tokenizer.

What the agent reads Tokens
gh --help 610
gh issue --help 346
gh issue create --help 869
gh issue edit --help 1,068
MCP issue_write (create and update) 807

An agent that starts cold and walks down to gh issue create reads 1,825 tokens of help. The two help pages that cover what issue_write does add up to 1,937. Operation for operation, the MCP definition is the more compact of the two.

What the CLI has going for it is timing. Nothing is loaded until the agent asks, and an agent that never files an issue never reads the page. A model may also know gh well enough from training to skip the help entirely; we can't measure that here, and it won't be true for your internal tools.

So the real difference was never the format. It's eager versus lazy loading. And that is exactly the part harnesses have changed.

With tool search, the bill drops to a list of names

Anthropic's API has a tool search tool. You mark tools with defer_loading: true, and the docs are specific about what that does: it "controls what enters the context window, not what you send in the request." The definitions still travel in the tools array, but the model only sees the ones a search returns, up to five per search by default. Anthropic reports this "typically reduces" definition tokens "by over 85 percent". For MCP servers attached through its connector, you set the flag once for the whole server.

OpenAI's Responses API has the same idea: a tool_search tool, supported on gpt-5.4 and later, plus defer_loading on functions or MCP servers. For a deferred server the model initially sees, in the guide's words, "only the namespace or server name and description". OpenAI recommends keeping each namespace under ten functions.

Claude Code turns tool search on by default. Its docs list the setups that go without: a custom ANTHROPIC_BASE_URL, ENABLE_TOOL_SEARCH=false, and older models on Google Cloud. With it on, the docs describe Claude Code listing a server's tool names to the model, which then goes through a ToolSearch call to use them.

How small is a list of names? For GitHub's 46 default tools our program counted 185 tokens, against 11,207 for the definitions. Each harness formats its own list, so that's a floor and not a prediction. Then add what a search pulls in: five average GitHub tools is about 1,200 tokens, and only on the turns that need them.

That's the answer to the thread. In a harness that defers, MCP's standing cost is a few hundred tokens. In one that doesn't, it's the whole table above on every call.

What to do if your harness loads everything

Find out first. If you call a model API yourself and pass the tools from tools/list straight through, you're loading everything. (The stateless revision of the spec changes how a client fetches that list, not how big it is.)

Then trim at the server. GitHub's README says it plainly: "Enabling only the toolsets that you need can help the LLM with tool choice and reduce the context size." We measured what the flags buy:

github-mcp-server v1.14.0 flags Tools Definitions
--toolsets all 91 23,206 tokens
(defaults) 46 11,207 tokens
--read-only 27 6,492 tokens
--toolsets issues 10 3,281 tokens
--tools issue_read,issue_write,list_issues 3 1,693 tokens

An agent that triages issues doesn't need 91 tools, and a server started with --tools costs it 7% of the full set.

If you can use tool search, use it, and keep the handful of tools you call constantly non-deferred. Anthropic suggests three to five. Its own threshold for bothering is ten or more tools, or more than 10k tokens of definitions, which the GitHub server crosses on defaults.

And measure your own setup instead of trusting anyone's table, including ours. With an Anthropic key, send your real tools array to count_tokens for the model you run; the docs note that Claude Opus 4.7 and later produce roughly 30 percent more tokens for the same text than earlier Claude models did, so an old count is stale. Without a key, a short Go program and a published tokenizer will tell you which three tools are eating your context.


Originally published on ImportStatic. This article was drafted with AI assistance; every claim was checked against the sources linked in it and the code samples were compiled and run.

Top comments (0)