Not everything needs to be an MCP tool

Not everything needs to be an MCP tool

MCP is one of the best things to happen to AI tooling. The idea is simple and powerful: give the model access to external systems through a standard protocol, and let it decide when to call what. We use MCP in almost every project. Database queries, API calls, file operations. It works.

But somewhere along the way, we started making everything an MCP tool. Send a message? Tool. Update a user profile? Tool. Log an event? Tool. Our chatbot had six tools, and two of them fired on nearly every single request. That's when we realized we had a problem. Not with MCP itself, but with how we were using it.

We removed two tools, replaced them with structured JSON output, and let plain code handle the rest. Cost dropped by 40%. Response time dropped by half. Reliability went from "usually works" to "always works." MCP didn't change. Our thinking about when to use it did.

Why MCP tools cost more than you think

Every tool call creates a round trip. The model doesn't just call your function and move on. It stops generating, emits a structured tool_use block, waits for your code to execute, and then receives the entire conversation history again, plus the tool result, before it can continue. Anthropic's own documentation shows the overhead: just enabling tool use adds 286 to 675 system prompt tokens depending on the model, before you've defined a single tool[1].

The math gets ugly fast. Say your system prompt is 8,000 tokens and the user message is 2,000 tokens. Without tools, you pay for 10,000 input tokens once. With two sequential tool calls, the model processes 10,000 tokens on the first pass, then 11,500 on the second (after the first tool result), then 13,000 on the third (after the second tool result). That's 34,500 input tokens instead of 10,000. You paid for your system prompt three times.

And that's just the input side. Output tokens cost 5x more than input tokens on Sonnet. Each tool call generates 100 to 300 output tokens for the structured JSON call block. Those are expensive tokens that don't produce any user-visible value.

Key Insight

The real cost of tool calls isn't the tool execution itself. It's the context reprocessing. Every round trip resends your entire conversation history, system prompt included. Two tool calls means paying for your prompt three times. Five tool calls means six times.

The two tools we didn't need

Our chatbot handled customer support. On most requests, it needed to do two things after reasoning: send a reply message and sometimes update the user's profile (language preference, contact info, subscription tier). Both were MCP tools. Both fired on almost every request.

Here's the thing we missed for weeks: both actions are completely deterministic. Sending a message is just "put this text in the response." Updating a profile is "write these fields to the database." The model doesn't learn anything new from calling these tools. It already decided what to say and what to update during its reasoning. It was mailing a letter to itself.

We replaced both tools with a single structured JSON response:

chatbot_handler.py
python
response = client.messages.create(
    model="claude-sonnet-4-6",
    system=system_prompt,
    messages=conversation,
    # No tools needed for these actions
)

# Parse the structured response

result = json.loads(response.content[0].text)

# Deterministic execution — no LLM involved

send_message(user_id, result["reply"])

if result.get("profile_update"):
update_profile(user_id, result["profile_update"])

The system prompt tells the model to return JSON with two fields: reply (the message text) and profile_update (an optional object with the fields to change). No tool definitions needed. No round trips. One LLM call, then deterministic code handles the rest.

The question that separates good tools from waste

MCP tools are perfect when the model needs to discover something it doesn't know yet. Order history, database state, search results, inventory levels. The model can't reason about data it hasn't seen. That's what tools are for.

The test is simple: does the model need new information from this action, or is it just telling your code to do something? If the answer is already in the model's head, you don't need a round trip to extract it.

What Works

Before you register an MCP tool, ask: will the model learn something new from calling this? If not, return the instruction as structured JSON and let your code handle it. Tools should bring information in, not push decisions out.

This isn't just our opinion. Anthropic's own engineering team published an article on advanced tool use that highlights a related problem: tool definitions alone can consume massive token budgets. Their internal benchmark showed that five MCP servers consumed 55,000 tokens in tool definitions before any conversation started[2]. GitHub's tools alone cost 26,000 tokens. Every request pays for those definitions whether or not a single tool gets called.

The reliability argument

Cost savings are nice. But the reliability improvement surprised us more.

Tool calls fail in ways that are hard to predict. The model might hallucinate parameters that don't match your schema. It might call tools in the wrong order. It might enter an infinite retry loop when a tool returns an error. HackerNoon published a detailed analysis of these failure patterns: models fail to conform to tool schemas 1 to 3% of the time, and without circuit breakers, tool failures can trigger unbounded retry cycles that exhaust your entire token budget[3].

Deterministic code fails in ways you can test. A database write either succeeds or throws an exception with a stack trace. A message send either works or returns an HTTP error code. You can write unit tests for this. You can set up monitoring. You can handle every failure mode with a try/catch block.

With our MCP tool approach, roughly 2% of requests had some kind of tool-related failure: malformed parameters, unexpected tool call sequences, or the model deciding to call send_message twice. After switching to structured JSON output, tool-related failures dropped to zero. The only failures left were the ones we could catch in code: JSON parse errors (which we handle with a retry using Anthropic's "prefill" technique) and database errors (which we handle with standard error handling).

Common Mistake

A common failure we saw: the model occasionally called update_profile without calling send_message first, leaving the user with no response but a silently modified profile. With structured JSON output, this is impossible. The code always sends a reply, regardless of whether a profile update is included.

Where else people over-tool

We see the same pattern in almost every AI project we review. Teams discover MCP or function calling and start wrapping everything in tools because the abstraction feels clean. But clean code and cheap code aren't the same thing.

Common tools that probably shouldn't be tools:

  • Sending responses. The model already generated the text. Don't tool-call it back to yourself.
  • Writing to a database. If the model decided what to write, just include it in the structured output.
  • Formatting output. Don't use a tool to convert markdown to HTML. That's a pure function.
  • Routing decisions. If the model classified a ticket as "billing," your code should route it. No tool needed.
  • Logging and analytics. Never use a tool call to log something. Extract it from the structured response.

The broader engineering community calls this "deterministic control flow." The LLM reasons and decides. Deterministic code executes. The LLM should be a stateless processing node, not an orchestrator that calls functions and waits for results[3].

When MCP tools are exactly right

We don't want to be the people who say "MCP bad." MCP is great. We use it every day. The point is knowing when a tool adds value and when it just adds tokens.

Keep your tools when:

The model needs information it doesn't have. Database lookups, API calls to external services, file reads. These are what MCP was built for. The model can't reason about data it hasn't seen, and a tool call brings that data into context.

The action space is too large to enumerate. If your model could call any of 50 different APIs with complex parameters, structured output becomes unwieldy. Tool schemas provide better guardrails than a freeform JSON spec in your system prompt.

The model needs to react to results. If the model calls a search API and then reasons about the results to decide its next step, the round trip adds value. The model learns something new from the tool result and makes a better decision.

Our rule of thumb: if a tool fires on more than 70% of requests and the model doesn't need the result to continue reasoning, replace it with structured output. If it fires on less than 20% of requests, keep it as a tool. Between 20% and 70%, benchmark both approaches with real traffic.

How to implement structured output

The switch is straightforward. Instead of defining tools, you describe the expected JSON format in your system prompt and parse the response.

System prompt excerpt
Respond with a JSON object containing: - "reply": string — the message to send to the user - "profile_update": object | null — fields to update on the user profile, or null if no update needed - "language": string (optional) — ISO 639-1 language code - "contact_email": string (optional) — new email address - "tier": string (optional) — "free", "pro", or "enterprise" - "intent": string — one of "question", "complaint", "request", "feedback" Always include "reply". Only include "profile_update" when the user explicitly requests a change.

With Claude Sonnet 4.6 and newer models, you don't even need the old "prefill" trick of starting the assistant response with {. Anthropic deprecated prefilled responses starting with Claude 4.6 because current models follow JSON formatting instructions reliably from the system prompt alone[4]. If your schema is complex, validate the response with Pydantic or Zod on your side.

We kept our remaining four MCP tools (order lookup, product search, FAQ search, account history) because the model genuinely needs those results to answer questions. Those tools make the chatbot smarter. The two we removed just made it slower and more expensive.

If you're building with MCP and your tool list keeps growing, take ten minutes to sort them into two piles: tools that bring new information into the model's context, and tools that just execute decisions the model already made. You might be surprised how many fall into the second pile. Reach out if you want a second pair of eyes on your agent architecture.

Sources

  1. [1]Anthropic (2026). Tool use with Claude: Pricing. System prompt token overhead per model for tool use
  2. [2]Anthropic (2026). Introducing advanced tool use on the Claude Developer Platform. 55K tokens consumed by 5 MCP server tool definitions
  3. [3]HackerNoon (2025). Designing Reliable LLM Agents With Deterministic Control Flow. 1–3% schema conformance failure rate, infinite loop risk
  4. [4]Anthropic (2026). Prompting best practices: Migrating away from prefilled responses. Prefills deprecated from Claude 4.6; models follow format instructions directly

More articles

The AI chatbot cost checklist we wish we had earlier

The AI chatbot cost checklist we wish we had earlier

We cut our chatbot cost from $0.15 per request to near zero. Five checks that made the difference.

Read more
How we use Claude Code hooks to keep context clean

How we use Claude Code hooks to keep context clean

Stop dumping every rule into CLAUDE.md. Hooks inject tool-specific context only when the agent actually needs it.

Read more

Tell us about your project

Contact

  • Location
    Switzerland
  • Working
    Remote & On-site