MCP vs function calling: they are not rivals
Function calling and MCP sit at different points in the same request. One is how a model asks for a tool; the other is how that tool reached the model in the first place.
Function calling is a capability of a model API. You send the model a list of function definitions, it returns a structured request to call one, your code runs it and sends the result back. It is the mechanism by which a model expresses intent to act.
MCP is a protocol for exposing tools to an application. It decides how a tool gets discovered, described and invoked across a process or network boundary. It does not replace function calling; the tools an MCP server exposes are usually handed to the model as function definitions.
So the question the two raise together is not “which should I use”. It is “do I need the layer MCP adds”.
How do they fit together in one request?
Trace a single tool call and the division is obvious:
- Your application connects to an MCP server and lists its tools. Each comes back with a name, a description and a JSON Schema.
- Your application passes those to the model as function definitions, in whatever shape your provider’s function-calling API expects.
- The model returns a call request: this function, these arguments.
- Your application routes that back over MCP as a
tools/call. - The server executes and returns a result, which you feed back to the model.
Steps 2 and 3 are function calling. Steps 1, 4 and 5 are MCP. Nothing is duplicated, because they are answering different questions: function calling answers “what does the model want to do”, MCP answers “where does this capability live and how do I reach it”.
What do you actually give up by not using MCP?
If you have three tools that live in your own codebase, nothing. Define them as functions, pass them to the model, run them in-process. Adding a protocol buys you very little and costs you a moving part. Most applications should start here, and plenty should stay.
The layer starts paying when one of these becomes true:
- The tool is not yours. Someone else wrote and maintains it, and you want to consume it without vendoring their code.
- The tool is not in your process. It needs different credentials, a different runtime, or its own network access.
- You want the same tool in more than one application. Writing it once behind MCP beats reimplementing the function per client.
- You want to swap the model or provider. Function-calling formats differ between providers; the MCP side does not change when you switch.
That last one is the least visible of the four. Function definitions are provider-shaped. If your tools are defined against one provider’s API and you move, you rewrite the plumbing. Tools behind MCP are described once, in JSON Schema, and re-mapped at the boundary.
Is there a security difference?
Yes, and it runs in the opposite direction to the convenience argument above.
With plain function calling, every tool is code you wrote, in your repository, reviewed by your team. The blast radius is your own codebase.
With MCP, the tool’s implementation lives behind an interface rather than in your codebase. That is not automatically someone else’s code: a server your own team wrote and self-hosts is still yours, and the boundary is a deployment detail rather than a trust one. What changes the trust position is ownership. A packaged server from a stranger is a dependency you execute locally; a remote server run by a stranger sees every argument your agent sends and can change what a tool does without you deploying anything. Where that describes your situation, you have gained reach and taken on a supply chain.
The description problem sharpens for the same reason. The spec tells clients to treat tool descriptions and annotations as untrusted unless the server itself is trusted, because wherever the host exposes them they are text that influences model behaviour. When you wrote the function yourself, that text was yours and changed only when you shipped. When it comes from a server you do not control, it is someone else’s and can change between calls.
None of this argues against MCP. It does mean a third-party server is worth checking before you wire it in: browse the registry, open the server you are considering, and read its score, its tool list and its transport findings.
Which should you use?
- A handful of tools you own, one application: plain function calling. Skip the protocol.
- Tools you want to reuse across applications, or share: MCP, and get the reuse.
- Third-party tools: MCP, because the alternative is vendoring someone’s code.
- Provider portability matters: MCP, since the tool contract stops being provider-shaped.
- Either way: the quality of your tool descriptions is what decides whether the model uses them correctly.
Where to go next
- The other common framing: MCP vs API covers what changes when the caller is a model rather than a developer.
- Deciding what to expose: what the Model Context Protocol is covers the server primitives and which fits which job.
- Before you connect a third-party server: the security checklist.
Function calling is how a model asks. MCP is how the thing it is asking for got there. They stack, so the question left is whether your tools need to cross a boundary.