TL;DR
Function calling is how a model asks to run a tool. MCP is how that tool gets found and executed. The difference that actually decides your architecture is who writes the tool schema and when it arrives: with function calling you write it and ship it inside your request, with MCP it is fetched at runtime from a server that may not be yours. Everything else, the extra network hop, the cross-client portability, the security surface, follows from that one change.
This post is about the mechanism, not about whether MCP beats a plain REST API. That question has its own answer.
Deciding how many tools justify a server? We design AI agents around the boundary where runtime discovery starts paying for itself, which is later than most teams expect.
Key Takeaways
- These are not alternatives. Function calling is the model's request format, MCP is the delivery and execution layer around it. A system using MCP still uses function calling underneath.
- The real split is schema ownership. Function calling schemas live in your code and travel in your API request. MCP schemas live on a server and are fetched with
tools/listat connection time. - Runtime discovery is the entire value of MCP: adding a tool means restarting a server, not redeploying an application, and any MCP-speaking client gets it for free.
- Runtime discovery is also the entire risk. A tool description you do not control is text the model will follow, which puts a prompt-injection surface inside your tool list.
- For one application with a handful of stable, private tools, function calling is not the compromise. It is the correct answer, and MCP is overhead with nothing on the other end.
The part everyone agrees on
Function calling is the mechanism a model uses to say "call get_weather with location: Tunis". The model does not execute anything. It returns a structured request, your code runs it, and you feed the result back. That is true whether the tool was described to the model by your own code or by an MCP server.
So MCP does not replace function calling. An agent using MCP still emits function calls. MCP standardises what happens on either side of that moment: how tools are discovered before the call, and how they are executed after it.
That much is well covered elsewhere. The useful question is what changes when you move the schema out of your application.
Who writes the schema, and when it arrives
With function calling, you write the tool definition in your own codebase and include it in the request to the model provider:
tools = [{
"type": "function",
"function": {
"name": "get_project_status",
"description": "Get the current status and open blockers for a project.",
"parameters": {
"type": "object",
"properties": {"project_id": {"type": "string"}},
"required": ["project_id"],
},
}
}]
response = client.chat.completions.create(model="...", messages=messages, tools=tools)
The schema is a build-time artifact. It is in your repository, it goes through your review process, and it changes when you deploy.
It is also a build-time artifact per provider. The same tool for Anthropic's API is not the same object:
tools = [{
"name": "get_project_status",
"description": "Get the current status and open blockers for a project.",
"input_schema": {
"type": "object",
"properties": {"project_id": {"type": "string"}},
"required": ["project_id"],
},
}]
Identical logic, different envelope. Anthropic takes input_schema at the top level; OpenAI nests parameters inside a function object. Support both and you maintain two definitions or write a translation layer, and that cost multiplies by every tool and every provider you add.
With MCP, the client asks a server what it has, using tools/list, and gets back the definition from somewhere else entirely. The application never hardcodes the tool. It hardcodes the connection. The server describes each tool once in the protocol's own shape, and the client, not you, translates that into whatever the model it is talking to expects. Swapping providers stops being a schema migration.
What runtime discovery buys
Three things, and they are real.
Tools change without a deploy. Add a tool to the server, restart it, and every connected client sees it on the next tools/list. With function calling you ship application code.
One integration serves many clients. An MCP server works with any MCP client, including the coding agents that live inside editors, which receive their MCP server list from the editor itself. A function-calling integration works with the application you wrote it into. If two products need the same tools, that difference compounds fast.
Someone else can maintain it. A vendor publishes a server, you connect to it, and you do not write or maintain the schema at all.
None of this matters if you have one application, five tools, and no plans to share them. That is the honest case for skipping MCP, and it is more common than the current discourse suggests.
What runtime discovery costs
An extra round trip, structurally. MCP adds a discovery call when the client connects, and tool execution goes through a server process rather than a function in your own address space. Whether that latency matters depends entirely on your workload, and anyone quoting you a millisecond figure without benchmarking your setup is guessing.
A schema you did not review. This is the part worth slowing down on. A tool's description field is not documentation. It is text the model reads to decide what to call and how, which means whoever writes that field is writing instructions your model will follow. With function calling, that text went through your code review. With a third-party MCP server, it did not, and it can change between one connection and the next without you noticing.
That is a prompt-injection surface sitting inside your tool list, and once a poisoned description or tool result enters context, it contaminates every turn that follows. It is also why an MCP server that reaches anything sensitive needs proper authorization rather than an API key in an env var, and why "just connect the vendor's server" deserves more scrutiny than it usually gets.
Operational surface. A server is a process. It starts, crashes, needs logging that does not corrupt the protocol stream, and needs versioning when a schema changes. We wrote up the day-two version of that because the quickstarts stop before any of it.
Where the current spec sits
Most comparisons describe MCP generically, so two things in the 2026-07-28 spec are worth knowing before you decide.
MCP is now stateless. Every request carries the protocol version and capabilities in a _meta field, and servers advertise what they support through a mandatory server/discover request. A client can send any request directly and handle a version error, rather than performing a handshake first.
There is also a Tasks extension that returns a durable handle for long-running work, so a client can poll for status and collect the result later. That removes one of the older reasons teams reached past MCP for anything slow. Sampling and logging, by contrast, are now deprecated as client primitives.
Worth noting that tools are not the whole protocol either. Servers can expose resources and prompts as well, and clients can expose elicitation to ask the user for input mid-call. Comparing MCP to function calling only compares the tool-shaped slice of what MCP does.
So which one
| Function calling | MCP | |
|---|---|---|
| Schema written by | You, in your repository | The server author, wherever that is |
| Schema arrives | Build time, inside your request | Runtime, via tools/list |
| Adding a tool | Code change and deploy | Server change and restart |
| Reusable across apps | No, per integration | Yes, any MCP client |
| Extra network hop | No | Yes, plus discovery at connect |
| Who reviewed the tool description | Your team | Possibly nobody on your team |
| Right when | One app, few tools, private, latency-sensitive | Many clients, shared or vendor tools, tools that change often |
The decision is not about tool count on its own. It is about whether the tools have more than one consumer, and whether you trust whoever writes their descriptions.
Most real systems run both
The split is per tool, not per system. The test is simple: does anything other than this application need this tool?
Take an incident-response agent that needs four capabilities.
| Tool | Belongs in | Why |
|---|---|---|
| Query the on-call alerting system | MCP | Every agent and dashboard in the company wants it |
| Search the runbook database | MCP | Shared across teams, changes without your deploy |
| Format a summary for one team's Slack channel | Function calling | Specific to this agent's output style |
| Record the agent's own reasoning steps | Function calling | Internal to this agent, nobody else consumes it |
The first two are shared infrastructure and benefit from one place to update, one place to govern, one audit trail. The last two exist only inside this agent, and moving them to a server would add a process, a transport and a trust boundary for nothing.
Migration works the same way, one tool at a time. Start with the one that has been copy-pasted into three repositories. The function-calling versions keep working while you move the shared ones out, so there is no cutover event to schedule.
Frequently asked questions
Does MCP replace function calling?
No. A model connected through MCP still emits function calls. MCP governs how tools are discovered before that call and executed after it. Removing function calling would mean removing the model's ability to request a tool at all.
Does the model need to support MCP?
No, and this is the most persistent confusion about the protocol. The model only ever sees tool schemas and emits function calls, exactly as it always has. MCP support lives in the client, the application that connects to servers, assembles the tool list and executes the calls. Any model capable of tool calling works behind an MCP client, including older ones released before MCP existed.
Does MCP give me multi-step workflows?
No, and comparisons that credit it with chaining or conditional execution are overreaching. MCP standardises the discovery and execution of individual tool calls. Deciding to call three tools in sequence, branching on a result, or retrying a failure is your agent loop's job in both approaches. Choosing MCP does not buy you orchestration.
Is MCP slower than function calling?
Structurally it adds work: a discovery call on connect, and execution through a separate process rather than an in-process function. Whether that is perceptible depends on your tools and transport. A local stdio server behaves very differently from a remote HTTP one, so benchmark your own path rather than trusting a general figure.
Can I use both in the same application?
Yes, and it is common. Private, latency-sensitive tools stay as native function-calling definitions in your code, while shared or third-party tools come from MCP servers. The model sees one combined tool list and does not care where each entry came from.
What is the actual security difference?
With function calling, every tool description was written and reviewed by your team. With MCP, descriptions can come from a server you do not control, and the model treats them as instructions. That makes a third-party MCP server a supply-chain consideration, not just an integration.
When should I not use MCP?
When one application uses a small set of stable tools that nothing else needs. Runtime discovery earns its cost by serving multiple clients or absorbing frequent change. With neither, you are paying for a server, a transport and a trust boundary to reach functions that were already sitting in your codebase.
