MCP vs Plain Tool Calling: When the Protocol Earns Its Keep
MCP and a plain function schema look identical to the model. The difference is who owns the integration, and that decides which one you should use.
From the model's side there is no difference. It receives a list of tool definitions, emits a call, and gets a result back. Whether that result came from a function in your codebase or from a subprocess speaking JSON-RPC is invisible to it.
So the decision is not about capability. It is about who owns the integration, how many applications need it, and what you are willing to operate. Those questions have clear answers, and they point to plain tool calling more often than the current enthusiasm suggests.
What plain tool calling actually costs
A native tool is a function plus a schema, registered with your model client. There is no process to launch, no protocol version to match, no transport to debug, and no separate failure mode when a server does not start.
You also get to use your existing everything. Your dependency injection, your database pool, your auth context, your logging, your error tracking, your tests. The tool is ordinary code in an ordinary codebase and it fails in ways your team already knows how to diagnose. Tool calling explained covers the mechanics.
The cost appears when a second application needs the same integration. Now you either extract a library, which works well within one language, or you reimplement, which is where the N-by-M problem starts. That inflection point is the whole argument for a protocol.
What MCP buys you
Three things, and it is worth being precise because they are often bundled together in a way that overstates the case.
Reuse across applications. One server works with any MCP-capable host, so an integration written once is available to your agent, your editor, your chat client and anything else that speaks the protocol. This is the genuine benefit and it is substantial when it applies.
A process boundary. The server runs separately, possibly in a different language, possibly under different permissions, possibly on a different machine. That is useful for isolation and for wrapping systems whose client library does not exist in your stack.
A distribution story. Third parties can publish servers you install rather than integrations you write. This is real leverage and also the source of the security considerations, since installing a server is running someone else's code with your credentials. The MCP security model covers the boundaries.
What it does not buy you
It does not make the model better at choosing tools. A badly named tool with a vague description is exactly as unusable over MCP as it is as a local function, and the same design work is required either way. Tool schema design applies unchanged.
It does not solve authentication. A remote server needs per-user identity and scoped credentials, which is more work than passing an already-authenticated context into a local function, not less.
It does not reduce context cost. The tool definitions still occupy the prompt, and the results still land in the transcript. If anything, servers built by wrapping a full API tend to expose more tools than a hand-written set would, which makes the context problem worse.
The decision, stated plainly
Use plain tool calling when the tool is specific to one application, when it needs your application's internal state or auth context, when it is trivial, or when latency matters enough that an extra process hop is worth avoiding.
Use MCP when more than one application needs the same integration, when you are consuming something a third party already published, when the integration must run under different permissions or in a different language, or when a shared team service should own the credentials rather than distributing them.
The clarifying question is: if this integration existed as a server, would anything other than this one application connect to it? If the honest answer is no, the protocol is overhead you are paying for a future that may not arrive.
The hybrid is usually correct
Most mature systems end up running both, and that is not a failure of design discipline.
Application-specific operations stay native — the ones that need the current user's session, that touch your own domain objects, that would be meaningless outside this product. Shared and third-party integrations come in over MCP, where the reuse and the distribution actually pay.
Keep the boundary clean by writing your tools against your service layer rather than against the transport. A well-factored operation can be exposed as a native tool today and wrapped in a server tomorrow without touching the logic. That is the cheapest way to defer the decision until you have evidence.
Watch the tool count either way
The failure mode that dominates real deployments is not choosing the wrong mechanism. It is ending up with sixty tools in the context because five servers were added over six months and nobody removed anything.
Every tool definition costs input tokens on every single turn, and more importantly it adds a branch to a decision the model must get right repeatedly. Selection accuracy degrades as the set grows and as the descriptions start to overlap.
Audit the combined set periodically. Remove servers nobody uses, disable tools within a server that are irrelevant to your workload if your host allows it, and treat the total count as a budget rather than a byproduct. Agent token budgets covers making that visible.
A practical rule
Default to native tools. Reach for MCP when you have a concrete second consumer, a third-party server worth adopting, or a permission boundary you need enforced by a process rather than by discipline.
When you do adopt a server, evaluate it the way you would evaluate a dependency: read what it exposes, pin the version, scope its credential, and check the tool count it adds. And whichever route you take, spend the saved effort on descriptions and return shapes, because that is where agent performance is actually decided. MCP server design covers doing that well on the server side.
Common questions
Can the model tell whether a tool came from MCP?
No. It receives the same tool definitions and emits the same calls either way. The difference is entirely on your side: who owns the integration, how it is deployed, and which applications can reuse it.
When is a native function better than an MCP server?
When only one application needs it, when it depends on your internal state or the current user session, when it is trivial, or when the extra process hop costs latency you care about.
Is it wrong to use both in one system?
No, that is the common end state. Application-specific operations stay native while shared and third-party integrations arrive over MCP. Just audit the combined tool count, since selection accuracy degrades as the set grows.