Skip to content

Measuring what a tool server costs

In this lesson we measure what a Model Context Protocol (MCP) server costs. We count what the tool definitions of a stand-in issue tracker with twenty tools take from the context window. Then we run one query through the server, and the same query through a command-line tool (CLI). The numbers show what to keep connected. The exercise repeats the measurement with a real server in your own agent and ends in a short table.

The previous lesson connected the reference file-system server to a copy of a small directory and read every call in the transcript. Here the question is what that connection costs on each turn, before the agent uses it and after. The lesson on what a token costs showed that a model reads its whole input again on every call. A tool definition is part of that input.

When a client connects a server, it asks for tools/list, and each tool comes back with a name, a description and a parameter schema [1]. Each definition takes tokens of context before any work begins [2]. Without on-demand loading, the client passes the definitions to the model with each request. The model reads them on every turn, whether the turn uses a tool or not.

The fixture for this lesson is at site/examples/customizing-agents/mcp-tool-cost/. tracker_server.py is a stand-in issue-tracker server over stdio with twenty tools, from search_issues and get_issue to create_milestone and subscribe_to_issue. Each has a description of two or three sentences and a schema with one to seven parameters, and each parameter has its own one-line description. The definitions of a real tracker server are about this size. Before you run the measurement, estimate it.

Checkpoint · choice

Before the task starts, how many tokens do the twenty tool definitions of the tracker server take?

definitions.py asks the stand-in for tools/list and counts the result with the rule of thumb from the lesson on what the model can see, about four characters per token. It counts the names alone too, in the mcp__<server>__<tool> form that Claude Code uses for MCP tools in permission rules [3].

Example · run it

Run definitions.py in the fixture directory, and compare with the output below.

tools = measure.list_tools()
definitions = measure.definitions_text(tools)
names = measure.names_text(tools)
print(f"tools: {len(tools)}")
size = f"{len(definitions):,} characters, about {measure.tokens(definitions):,} tokens"
print(f"definitions: {size}")
print(f"names only: {len(names):,} characters, about {measure.tokens(names):,} tokens")
Output
tools: 20
definitions: 13,788 characters, about 3,447 tokens
names only: 553 characters, about 138 tokens

Output verified in CI from site/examples/customizing-agents/mcp-tool-cost/definitions.py.

The count is an estimate. A client counts with the model’s own tokenizer, and in Claude Code the /context command shows what fills the window, by category [4]. The size is what matters here: about 3,447 tokens for the full definitions and about 138 for the names.

Claude Code doesn’t send the full definitions by default. Its tool search holds them back, and the model searches for a tool’s definition when it needs that tool [5]. Until then, only the tool names and each server’s instructions enter the context [6]. The mcp page lists the cases where Claude Code loads every definition upfront instead: ENABLE_TOOL_SEARCH=false, an ANTHROPIC_BASE_URL that points to a third-party host such as a proxy, models before the Claude 4.5 generation on Google Cloud’s Agent Platform, and a Microsoft Foundry deployment hosted on Azure [5]. In the Claude API, a definition that search loads stays in the conversation for later turns, and it counts as input tokens like any other definition [7].

Now the query. We want the open bugs about login. The server has search_issues, and the tracker also has a CLI, which the agent would run in its shell. one_query.py runs the same search both ways and counts both sides of each: the call the model writes and the result it reads back.

Example · run it

Run one_query.py in the fixture directory, and compare with the output below. The CLI’s listing comes first.

call, result = measure.server_query({"state": "open", "labels": ["bug", "login"]})
command, output = measure.cli_query(["list", "--label", "bug", "--label", "login"])
print(output, end="")
print()
print(f"server: call {measure.tokens(call)} tokens, result {measure.tokens(result)} tokens")
print(f"cli: call {measure.tokens(command)} tokens, result {measure.tokens(output)} tokens")
Output
#104  Session cookie is not cleared on logout  (rivera)
#101  Login page times out behind the office proxy  (rivera)
#106  Single sign-on button missing on mobile  (-)
#102  Password reset email arrives twice  (-)

server: call 24 tokens, result 535 tokens
cli: call 13 tokens, result 54 tokens

Output verified in CI from site/examples/customizing-agents/mcp-tool-cost/one_query.py.

The calls are about the same size. The results differ, and the difference comes from how each one was written. search_issues returns the full record of each issue as JSON (JavaScript Object Notation), with the body, the timestamps, the reporter and the URL. The CLI prints one line per issue, which is what a tracker’s CLI does by default. The result has to fit the task. The one-line list answers “which login bugs are open”, and the model reads the 535 tokens of full records without needing them. A server with a fields parameter, or a CLI asked for JSON output, moves those numbers. So measure the server you have, and don’t go by what servers in general return.

One query happens once, and the context it adds stays. The call and its result remain in the conversation, and the definitions arrive with every request. The model reads both again on every later turn. session.py adds that up over a ten-turn session with the query on the first turn, for four setups. The first is the server with all twenty definitions loaded. The second is tool search: the names on every turn, plus the one definition that search loads for search_issues. This row leaves two parts out, because the fixture has no size for them: the definition of the search tool itself, which the API keeps in the context on every turn, and the search call with its result, which stay in the conversation [7]. The real number for this row is higher. The third loads only three definitions, the tools the task uses. The last is the CLI. It runs through the agent’s shell tool. That tool is in every setup anyway, and the CLI doesn’t bring a definition of its own.

Example · run it

Run session.py in the fixture directory, and compare with the output below.

for label, per_turn, query in setups:
print(f"{label:<28} {TURNS * (per_turn + query):>7,}")
Output
tokens read over 10 turns
server, all 20 tools loaded   40,060
server, tool search           10,150
server, 3 tools loaded        11,650
cli                              670

Output verified in CI from site/examples/customizing-agents/mcp-tool-cost/session.py.

Read the table from the top. With the full list the model reads 40,060 tokens over the session. Tool search brings the server down to 10,150, and most of what’s left is the 535-token result, read again on every turn. Keeping three tools, without tool search, reads 11,650, which is in the same range. Which of the two is lower in your client depends on the parts the tool-search row leaves out. The CLI reads 670, because it has no definitions and a short result.

Prompt caching makes a repeated part of the input cheaper to read again [6]. It doesn’t make that part smaller. The definitions still take the same share of the window, and the window is where the rest of the task has to fit.

A second cost doesn’t show in any count. Every tool in the list is something the model can call, and so is every tool that search can find. An instruction injected into a web page or an issue body can ask for delete_comment as easily as the task asks for search_issues. A tool the task never uses costs tokens and adds a way to do damage. A deny rule that names a whole tool, such as mcp__tracker__delete_comment, removes it from what Claude sees [3].

From the measurement:

  • Keep only the tools the task uses. A server with twenty tools and a task that needs three is seventeen tools of cost and risk. Deny the rest by name, or pick a server with fewer tools.
  • Use the CLI for occasional queries. When the system has a good CLI and the agent needs it a few times in a session, the CLI costs no definitions and returns what you asked for. Claude Code’s page on costs recommends CLIs such as gh and aws for the same reason: running a command doesn’t put tool descriptions in the context [6].
  • Use the server for constant, structured calls. When the agent calls the system on most turns and needs typed inputs and structured results, the definitions are paid once per turn and used on most of them, and typed inputs and structured results are more reliable than text the model has to parse [2].
  • Disable the servers the project doesn’t use. Tool search lowers the per-turn cost of a connected server. It doesn’t remove it: the names still load on every turn, some setups load every definition upfront, and any search can reach every tool, including a search that an injected instruction asks for. In Claude Code, /mcp lets you turn a server off without removing its configuration [5], and the cost page names disabling unused servers as one of its controls [6].
Checkpoint · match

Match each need to what you connect for it. The tracker has an MCP server and a CLI, and the CLI can list and show issues but can’t change them.

Checkpoint · choice

A project has six MCP servers connected and uses one. A colleague says: “Tool search is on, so the other five are free. There’s no reason to disable them.” What do you answer?

Exercise

Measure a real server in your own agent, on a copy. Copy the handbook directory from site/examples/customizing-agents/mcp-first-server/ to a place you can delete, change into the copy, and add the reference file-system server there as in the previous lesson. It has thirteen tools, and four of them write [8]. Claude Code gives the server the directory it was started in as its root, so start the agent inside the copy every time [5]. Roots are deprecated in MCP revision 2026-07-28 [9], and the command below also gives the copy as the argument, so a client that stops sending roots still leaves the server on the copy. With your own measurement you can price any server before you keep it connected. The commands use a new copy, so a changed copy from the previous lesson doesn’t show up in your listings.

Terminal window
cp -r site/examples/customizing-agents/mcp-first-server/handbook /tmp/handbook-cost
cd /tmp/handbook-cost
claude mcp add handbook -- npx -y @modelcontextprotocol/server-filesystem@2026.8.31 /tmp/handbook-cost
  1. Start Claude Code with every definition loaded, ENABLE_TOOL_SEARCH=false claude [5], run /context, and write down the tokens it shows for the MCP tools. Quit, start claude without the variable, and write down the same number with tool search on.
  2. In the second session, run /context and note the tokens in use. Ask: “Use the handbook server to list this directory. Answer with the listing only.” Run /context again.
  3. Run /clear [10], run /context, and ask: “Run ls in your shell to list this directory. Answer with the listing only.” Claude Code runs ls without asking, because ls is on its list of read-only commands [3]. Run /context again.

Put the four numbers in a short table: the definitions with and without tool search, and the tokens each query added. A good result has all four from your own /context output, and says in one line which connection you would keep for a task that lists a directory twice a week. Remove the server with claude mcp remove handbook when you are done [5]. Then ask yourself: at how many calls a session would the server be the better choice?

Stretch: Deny every write tool of the server by name in your settings, then run /context again and see how the count for MCP tools changes.

Recap

  1. A connected server’s tool definitions are part of the model’s input. Without on-demand loading, the model reads all of them on every turn, and twenty tools of a normal size come to a few thousand tokens.
  2. A query costs its call and its result, and both stay in the conversation. A verbose result is read again on every later turn, so measure what your server returns and not only what it lists.
  3. Claude Code’s tool search is on by default [5], and until the model searches, only the names and server instructions are in the context [6]. It lowers the per-turn cost, and the names, the upfront fallbacks and the reach of every tool remain.
  4. Keep only the tools a task uses, use the CLI for occasional queries and the server for constant structured calls, and turn off the servers the project doesn’t use [6].

You can now

  • Explains the token and risk cost of a tool before adding it

  1. Anthropic and the MCP contributors. Model Context Protocol specification 2026-07-28. modelcontextprotocol.io. Reference. MCP specification
  2. Addy Osmani, Ivar Soares Urdalen, Leo Simons. MCP deep dive: MCP versus CLI, security failure modes, token cost. Agent Engineer Course. Course. AEC-16
  3. Anthropic. Configure permissions. Claude Code documentation. Reference. Claude Code permissions
  4. Anthropic. Explore the context window. Claude Code documentation. Reference. Claude Code context window
  5. Anthropic. Connect Claude Code to tools via MCP. Claude Code documentation. Reference. Claude Code mcp
  6. Anthropic. Manage costs effectively. Claude Code documentation. Reference. Claude Code costs
  7. Anthropic. Tool search tool. Claude Platform documentation. Reference. Claude docs tool-search-tool
  8. Anthropic and the MCP contributors. Filesystem MCP Server. GitHub, modelcontextprotocol/servers README. Reference. MCP filesystem server
  9. Anthropic and the MCP contributors. Deprecated Features, Model Context Protocol specification 2026-07-28. modelcontextprotocol.io. Reference. MCP deprecated features
  10. Anthropic. Commands. Claude Code documentation. Reference. Claude Code commands