All posts
#mcp#function-calling#tool-calling#ai-agents

MCP vs Function Calling: When You Actually Need an MCP Server

MCP does not replace function calling, it feeds it. What each layer does, the token cost of loading every tool upfront, and when a CLI beats a server.

The recal team8 min read

Two architectures side by side on a deep teal surface: on the left a model core wired directly to a small fixed set of tool sockets, on the right the same core connected through an intermediary hub that fans out to many servers, each piling translucent schema cards in front of the model until they crowd it

Function calling is the model capability: you hand the model a list of tool definitions, it replies with a structured request to invoke one. The Model Context Protocol is a transport and discovery layer that fetches those definitions from a separate process and routes the resulting calls back to it. They sit at different heights in the stack, so the question is not which one to pick. The question is whether you need the layer at all.

We work on recal, an assistant whose tools run on the user's own Mac, so this was a decision we had to make rather than an opinion we formed at a distance. We looked hard at MCP for our own tools and chose not to use it internally, and that choice has real costs we state plainly below. Nothing here is a pitch, and the mechanics stand without us.

Key takeaways

  • MCP does not replace function calling. The official message flow is tools/list to discover, then the model selects a tool, then tools/call to execute. The model's native tool calling is still the thing choosing.
  • The cost of MCP is paid in context. The protocol's own documentation puts loading every tool upfront at roughly 150,000 tokens of definitions, against roughly 2,000 tokens when definitions load on demand.
  • MCP earns its keep at process and vendor boundaries: other people's clients, other people's languages, tools that live outside your binary.
  • If your tools are in-process, in one language, and only ever called by your own app, a direct registry costs you nothing per turn and MCP costs you every turn.
  • Adding or removing tool definitions mid-conversation invalidates the provider's prompt cache, so dynamic tool loading is not free either.

Is MCP a replacement for function calling?

No, and the specification is explicit about the order of operations. MCP's documented message flow has the client send tools/list to the server, the server return the list of tools, the model select a tool from that list, and only then the client send tools/call to the server for execution.

The selection step belongs to the model. MCP never takes it over. What the protocol does is answer two different questions: where the tool definitions came from, and where the execution goes. Its documentation is blunt about the boundary, noting that MCP "focuses solely on the protocol for context exchange" and "does not dictate how AI applications use LLMs or manage the provided context."

So a more useful framing is a stack. Function calling is the model contract. MCP is optional plumbing underneath it. You can run function calling with no MCP at all, which is what most single-application agents do. You cannot run MCP without something like function calling on top, because otherwise nothing would decide which tool to invoke.

What does MCP actually add?

Three things, and it is worth being precise because each is a real capability you would otherwise build yourself.

A process boundary. MCP is a client-server protocol over JSON-RPC 2.0, with two transports: stdio for local processes on the same machine, and Streamable HTTP for remote servers. Your tools become a separate program with its own lifecycle, which is exactly what you want when they are not yours.

A discovery contract. Servers expose three primitives: tools (executable functions), resources (contextual data), and prompts (reusable templates). Each has */list for discovery and, for tools, tools/call for execution. Because discovery is a live request rather than a compile-time list, the available tools can change while the app runs, and servers can push notifications/tools/list_changed to say so.

A shared vocabulary. A tool definition carries name, title, description, inputSchema, and optionally outputSchema and annotations. Any compliant client can consume any compliant server. That interoperability is the whole point, and it is not a small thing.

Worth noting for anyone reading older material: as of protocol version 2026-07-28 the protocol is stateless, every request carries its version and capabilities in a _meta field, and the sampling and logging client primitives are deprecated. Elicitation, which lets a server ask the user for input or confirmation, is the client primitive that remains.

What is the token overhead of an MCP server?

This is the part that surprises people, and the honest numbers come from the protocol's own client guidance rather than from critics.

Tool definitions live in the model's context window. A naive host fetches every tool from every connected server and passes the lot to the model at the start of each conversation. The MCP documentation describes what happens next without softening it: when a host has access to dozens of servers exposing hundreds of tools, "those definitions alone can consume the majority of the context window before the model has even read the user's message." Their comparison diagram puts the upfront approach at roughly 150,000 tokens of definitions against roughly 2,000 tokens for on-demand loading.

The recommended threshold is concrete. Load tool definitions normally while they occupy a small share of the window, and switch strategies once they cross something like 1 to 5 percent of the context window.

There is a second cost that is easier to miss. Most providers cache the prompt prefix, and the tools array sits in that prefix. The guidance is direct about the consequence: adding or removing tool definitions mid-conversation invalidates that cache, "and the resulting miss can cost more tokens than the definitions you removed." So the obvious fix for bloat, loading tools dynamically, can cost more than the bloat did. The suggested workarounds are to append new definitions after the cache breakpoint instead of re-sorting the array, or to route everything through one stable meta-tool so the array never changes at all.

None of this makes MCP a bad protocol. It makes tool count a budget you have to manage, which is a different claim.

What do you do when an MCP server has too many tools?

The pattern MCP recommends is progressive discovery, and it is a three-layer structure worth knowing even if you never touch MCP, because the same shape works for any large tool registry.

Catalog. The host exposes a small search_tools meta-tool that takes a natural-language query and returns matching tool names with one-line descriptions. Only this tiny surface sits in context.

Inspect. Once the model has a candidate, it fetches the full schema for that one tool.

Execute. The model calls the tool knowing its interface, having loaded only what it needed.

The retrieval mechanism in the catalog layer is a choice: keyword matching such as BM25, embedding similarity over tool descriptions, a small fast model acting as a selector, or a hybrid. Several providers now ship tool search natively, so check whether yours does before building it. The documented benefit is not only fewer tokens but better selection, since the model weighs a few relevant tools instead of scanning hundreds.

The same idea extends past individual tools. A host can keep a registry of servers, connect to one only when the model decides it needs those capabilities, and disconnect it afterwards to free context.

MCP vs a CLI: when is a server the wrong shape?

Here is the comparison that actually decides most implementations, and the one we had to make.

Direct function callingMCP serverCLI the agent shells out to
Per-turn context costOnly your own toolsEvery connected server's tools, unless you build progressive discoveryOne command's help text, or nothing
Cross-client reuseNone without extra workAny compliant clientAny agent that can run a shell
Language boundarySame process, same languageAny language, separate processAny language, separate process
Failure surfaceIn-process exceptionTransport, protocol version, and server lifecycleExit code and stderr
Human-in-the-loopYou build itSpec says clients SHOULD confirm; the client owns itYou own it at the command layer
Best whenTools are yours and in-processTools cross a vendor or process boundaryTools are already a program a human would run

For recal, our tools are in-process Rust and Swift, called only by our own assistant, on one machine. Routing them through a local server would have added a transport, a protocol version to track, and a per-turn context cost, and bought us interoperability we had no consumer for. We kept one in-process tool registry as the single source of truth and exposed the same capabilities as a recal command for local agents that prefer a shell.

The honest cost of that decision: no third-party MCP client can use recal's tools today. If someone wants recal's search inside their own editor's agent, we have to build a server to give it to them. That is a real limitation, we accepted it deliberately, and it is exactly the trade the protocol exists to solve. If your situation has that consumer and ours does not, the answer flips.

Where MCP clearly wins

Being fair to the protocol matters more than being clever about it.

Use MCP when a tool belongs to someone else and you want it without writing an integration. Use it when your tools are written in a language your host application does not speak. Use it when tools must be added or removed while the application runs, since live tools/list plus change notifications is precisely that design. Use it when you are the one publishing capability and want any client to consume it, which is the case for every hosted product with an agent story. And use it when you want the ecosystem's inspector, SDKs, and reference servers rather than your own scaffolding.

Several local assistants already take this path. Jan speaks MCP alongside its own local server, which is a reasonable call for a general assistant that cannot know in advance which tools a user will want. If you are wiring tools to an agent that also needs to remember things across sessions, the storage side is a separate decision with its own trade-offs, which we covered in our ranking of agent memory frameworks.

One more point in its favour, and it is one we care about. The specification states that for trust and safety "there SHOULD always be a human in the loop with the ability to deny tool invocations," and asks clients to show tool inputs to the user before calling the server. That is the right default for anything that touches a real filesystem or a real account, and it is the same posture we take in recal, where the assistant proposes and the person approves.

Frequently asked questions

Is function calling the same as tool calling? In practice yes. Providers renamed function calling to tool calling as the feature grew past plain functions. Both describe the model returning a structured request to invoke a named capability with typed arguments.

Do I need an MCP server to give my agent tools? No. If your agent and its tools live in the same application, define the tools directly against your provider's tool-calling API. MCP solves distribution across process and vendor boundaries, and if you do not have one, you are paying for a boundary you do not have.

Does MCP make an agent smarter? No, and it does not claim to. It changes which tools are reachable and how their definitions arrive. Selection quality is the model's, and past a certain tool count more choices measurably hurt selection, which is the argument for progressive discovery.

Can I use MCP and direct function calling together? Yes, and most serious hosts do. Ship your own first-party tools in-process where the context cost is fixed and the failure modes are simple, and reach for MCP at the edges where third-party capability arrives.

What is programmatic tool calling or code mode? Instead of the model calling tools one at a time with every intermediate result flowing back through context, the model writes a script against generated typed stubs, the script runs in a sandbox, and only the final output returns. It cuts the token cost of tool results rather than of tool definitions, so it pairs with progressive discovery rather than replacing it. It also adds a code execution surface, so the sandbox needs no network access and each brokered call still needs the same approval policy as a direct one.

The short version

MCP against function calling is not a fork in the road. Function calling is how a model asks for work. MCP is one way of telling it what work exists and carrying the request somewhere else. If your tools already live inside your application, the layer is overhead you pay on every turn. If they live behind a process or vendor boundary, the layer is the cheapest correct answer available, and progressive discovery is how you keep it affordable once the tool count grows.

Pick the boundary first. The protocol follows from it.


Sources: the Model Context Protocol architecture overview, tools specification, and client best practices, read at protocol version 2026-07-28.

Written with AI assistance and reviewed against the primary sources linked above by the recal team.