⚡The short answer: it is a layer, not a fork
The framing "MCP vs function calling" implies you pick one. In practice you rarely do. Function calling is the mechanism by which a model asks your code to do something; MCP (the Model Context Protocol, introduced by Anthropic in late 2024 as an open specification) is a standardized way to package, describe, and serve those tools so any compliant host can find and use them. Most MCP clients ultimately expose MCP tools to the model as ordinary function-call schemas.
So the real question is narrower and more useful: given that you already have function calling, when does adding the MCP layer pay for itself? The answer depends on how many hosts need the same tools, who maintains the integrations, and whether the tools change hands between teams or vendors.
This article walks through the mechanism of each, what MCP genuinely adds, what it costs in latency and operational surface, and a decision table for when plain function calling is the right call — because often, it still is.
MCP does not replace function calling any more than HTTP replaced sockets. It standardizes a layer above it. Evaluating "MCP vs function calling" as a rivalry leads to the wrong architecture review.
🔧Function calling fundamentals: the mechanism everything sits on
Function calling (tool use in Anthropic terminology, tools in the OpenAI API) works the same way across major providers. You register a set of tool schemas — name, description, and a JSON Schema for the arguments — alongside your prompt. The model, when it decides a tool would help, emits a structured output: the tool name and a JSON argument payload that conforms to your schema. Your code parses that payload, executes the real function, and appends the result to the conversation as a tool message. The model then continues, now grounded in the result.
Three properties of this mechanism matter for everything that follows. First, the model never executes anything — it only emits JSON, and your runtime is the trust boundary. Second, the schema is the contract: argument validation, error messages on malformed calls, and retry behavior are all yours to build. Third, the tool definitions are compiled into your application. Adding a tool means a code change, a deploy, and usually a prompt adjustment.
That third property is the pain MCP was designed to address. In a world where every agent host (IDE assistant, desktop client, internal chatbot, workflow runner) hand-rolls its own integration to the same GitHub API or the same internal knowledge base, you get N hosts times M tools of duplicated integration code, each with its own auth handling and its own bugs.
| Concern | Plain function calling | Who owns it |
|---|---|---|
| Tool definition | JSON Schema registered in your code, per request or per session | Your application |
| Execution | Your runtime parses args, runs the function, returns the result | Your application |
| Validation & errors | You validate against the schema and craft error messages the model can recover from | Your application |
| Discovery | None — the model only sees the tools you compiled in | N/A |
| Auth to downstream APIs | Handled inside your functions, with your secrets | Your application |
| Portability | Zero — tools are welded to this codebase | N/A |
🧩What MCP actually adds
MCP defines a client-server protocol (JSON-RPC based, over stdio for local servers or streamable HTTP for remote ones, as of the current spec — verify before committing, the transport story has already changed once) with three primitives: tools (callable functions), resources (readable data), and prompts (reusable templates). A server implements the protocol once; any compliant host can connect, list what the server offers, and invoke it.
The first real addition is discovery. An MCP host can ask a server "what can you do?" at connection time and receive tool schemas, descriptions, and resource listings dynamically. Adding a capability to a shared internal MCP server makes it available to every connected host without redeploying any of them. For a platform team maintaining tools used by five different internal agents, that alone can justify the layer.
The second addition is standardization of the boring parts: connection lifecycle, capability negotiation, pagination of tool lists, and an increasingly specified authorization flow for remote servers. The boring parts are exactly where hand-rolled integrations accumulate divergent bugs, so a shared spec has real value even when you control both ends.
The third addition is cross-host portability, and this is the one that gets oversold. In principle, an MCP server written for one host works in any other — a database server usable from an IDE assistant, a desktop client, and your own agent runtime. In practice, as of writing, hosts differ meaningfully in which primitives they support, how they surface approvals, and how they handle auth. Portability is real but not free; expect to test per host, not assume conformance.
The cleanest mental model: MCP turns tools from library code into network services with a standard interface. That is a genuine architectural option with genuine benefits — and all the costs that turning library code into network services always brings.
MCP integration is a service we build and operateBuild your first server: how to build an MCP server
🧾What the MCP layer costs you
Every layer you add between the model and the function has a price, and honesty about the bill is what separates a good architecture review from a hype cycle. The costs fall into four buckets.
Extra hops and latency. A remote MCP call is a network round trip before your actual tool logic even runs — connection setup or keep-alive, protocol framing, then the invocation. For a tool that itself calls a fast internal API, the MCP hop can double its latency. In a multi-step agent loop where the model chains five tool calls, that compounds. Local stdio servers avoid the network but add process lifecycle management instead: spawning, supervising, and restarting per-host child processes.
Spec churn. The MCP specification is young and has moved quickly — transport mechanisms, authorization, and capability details have all been revised since the initial release. As of writing, that trajectory is flattening, but treat anything built against it as needing an upgrade budget. Pin SDK versions, write conformance smoke tests for your servers, and verify the current spec before committing to a design that depends on a specific feature.
Operational surface. A function is deployed with your app and scales with it. An MCP server is a service: it needs hosting, health checks, secrets management, versioning, and an answer to "who is on call when it returns garbage to every connected agent at once." A shared server is a shared blast radius.
Context cost. Discovery is not free at inference time. Every tool the host surfaces lands in the model context as schema text, and large tool catalogs degrade selection accuracy and burn tokens. Well-run MCP deployments curate which tools each agent session sees rather than connecting everything to everyone.
| Cost | Plain function calling | With MCP |
|---|---|---|
| Latency per tool call | In-process, microseconds of overhead | Process hop (stdio) or network round trip (HTTP), then execution |
| Deployment unit | Ships with the app | Separate server artifact per tool set |
| Upgrade risk | Your code, your schedule | Spec and SDK churn plus per-host compatibility, as of writing |
| Failure blast radius | One application | Every host connected to a shared server |
| Context/token cost | Only the tools you register | Full catalog unless you curate exposure |
The most expensive MCP mistake is exposing a whole server catalog to every agent. Tool selection accuracy drops as the tool count grows. Curate the catalog per agent the same way you would scope IAM permissions.
✅When plain function calling is enough
This section exists because the honest answer to "should we adopt MCP?" is frequently no, and the engineer who says so in the design review is usually right.
Plain function calling is enough when one application owns both the agent and the tools. A support copilot with eight internal tools, all maintained by the same team that maintains the agent, gains nothing from interposing a protocol between its own frontend and its own backend. You would be operating a service boundary to talk to yourself.
It is enough when latency is tight. Voice agents, real-time coding assistants, and anything with a multi-tool hot loop feel every added hop. In-process calls are simply faster than any protocol, and the difference is user-visible.
It is enough when the tool set is small and stable. Discovery solves a problem you only have when tools change frequently or outnumber what a single team can keep in its head. Six tools that change twice a year are a config file, not a platform.
And it is enough — for now — when your organization has one host. MCP portability pays off at the second host, not the first. If the only consumer of your tools is your own agent runtime, build clean functions behind a stable internal interface, and you will have done 80 percent of the future migration work anyway.
| Your situation | Recommendation | Why |
|---|---|---|
| One agent, one team, tools live in the same repo | Plain function calling | A protocol boundary to yourself is pure overhead |
| Latency-critical loop (voice, live coding) | Plain function calling | Every hop is user-visible in a chained tool sequence |
| Several internal agents need the same tools | Internal MCP servers | One integration maintained once beats five divergent copies |
| Tools consumed by third-party or vendor hosts | MCP, if the hosts support it | Portability is the entire point of the standard |
| Tools maintained by a platform team for the org | MCP with a curated registry | Discovery and versioning are the platform team product |
| Regulated environment, strict data boundary | Either — but audit the transport | Stdio keeps data local; remote servers need an auth story you have verified |
🚚Migration considerations: from functions to servers
If you do migrate, the good news is that the path is mechanical for most of the distance. An existing function-calling tool — schema, handler, validation — maps almost directly onto an MCP tool definition. Teams commonly write a thin adapter that wraps their existing tool registry and serves it over MCP, which is a few days of work rather than a rewrite.
The parts that are not mechanical are auth, tenancy, and versioning. In-process functions inherit the request context — the user, their permissions, the trace ID — for free. A remote MCP server needs that context passed explicitly and safely, and the authorization specifications for remote MCP are among the most actively revised parts of the protocol as of writing. Design your context-passing as if the spec will move under you, because it may.
Version your servers from day one. Function APIs break one consumer at a time, at compile time, with the compiler as your witness. MCP servers break every connected host at runtime, with the model as your witness — which means the failure looks like "the agent got dumber," not a stack trace. Schema versioning, changelogs, and deprecation windows are not optional once multiple hosts depend on you.
A pragmatic sequencing that works: keep functions as the source of truth, stand up an MCP adapter as a second consumer of the same registry, run both paths in parallel behind an internal agent, and only cut external hosts over once the adapter has survived a month of real traffic. The functions never go away — the protocol becomes one more view over them.
Wrap, do not rewrite
Your handlers, validation, and tests are the asset. The MCP server is an adapter over them, and it should stay thin enough to delete.
Pass context explicitly
User identity, permissions, and trace IDs that were implicit in-process must become explicit, signed, and verified at the server boundary.
Version the schema, not just the server
Hosts cache tool definitions. A silent argument rename is an agent regression, not a deploy error.
Keep a kill switch to plain functions
The adapter pattern means you can route a host back to direct calls if a server misbehaves. Shared infrastructure needs a per-consumer off-ramp.
🌱The honest state of the ecosystem
Adoption is real. Since the specification was published, the major model providers, the major IDE assistants, and a long tail of tool vendors have shipped MCP support, and community server registries list integrations for most mainstream SaaS products. If you want your tools reachable from a wide range of hosts, MCP is the only candidate with that breadth of support as of writing — verify current host capabilities before committing, because support depth varies.
Quality is uneven. Public registries mix well-maintained servers from first-party vendors with weekend projects of unknown provenance. Installing a community MCP server is granting a stranger code execution inside your agent loop, with your credentials, on your machine or your infrastructure. Treat server selection like dependency selection: read the code, check the maintenance cadence, pin versions, and prefer servers you can audit.
The security model is catching up to the adoption curve. The specification has added progressively stronger authorization requirements for remote servers, but the gap between "the spec says" and "the server you found on a registry does" is where incidents live. Prompt injection through tool descriptions and tool outputs is a documented attack class against agent systems generally; MCP increases the number of parties who can write text your model will read.
Net assessment: the protocol has crossed from experiment to infrastructure, but it is young infrastructure. Build on it where its specific benefits — discovery, shared maintenance, host portability — are the ones you actually need, keep your adapters thin, and budget for the spec to keep moving a little longer.
⚖️The verdict
Function calling is the mechanism; MCP is the distribution layer. If your tools and your agent live in one codebase owned by one team, the mechanism is enough and the layer is overhead. The moment your tools have multiple hosts, multiple owners, or a platform team between them and their consumers, the layer starts paying rent.
Build your tools as clean, validated, well-tested functions regardless. That asset survives either choice. Then add MCP as an adapter when — and only when — a second host shows up asking for the same capabilities.