"Should I use MCP or function calling?" is the wrong question, and it gets asked constantly. It's like asking whether to use a shipping container or a forklift. They operate at different layers. One of them moves the box; the other one is the box, standardized so any port can handle it.
Let me make the actual relationship concrete, because once you see it the "versus" dissolves.
They are the same mechanism, one layer apart
Function calling is the model capability. You hand the model a set of tool schemas, it decides which to call and with what arguments, you execute the function, you feed the result back. That loop is the thing. It lives entirely in your process — the schemas are defined in your code, the execution happens in your code, nothing crosses a boundary.
MCP is a packaging and transport convention for those exact schemas. An MCP server's tools/list returns tool definitions in the same JSON-Schema shape your function-calling loop already wants. The difference is where they come from: instead of being defined inline in your application, they arrive over a connection from a separate process that you didn't have to write.
So MCP doesn't replace function calling. It feeds it. Under the hood of any MCP-enabled host, there's a plain function-calling loop — the tools just got delivered instead of hardcoded.
Once that clicks, the real question reveals itself. It's not "which one." It's "is the delivery worth it for this tool?"
What the delivery layer costs you
MCP is not free. Standing up a server means a separate process, a transport, a handshake, a lifecycle to manage, and — if it's remote — auth, networking, and an attack surface. For a single function that only your one app will ever call, that's pure overhead. You're shipping a container to move a box across the room.
Inline function calling, by contrast, is almost nothing. A decorated function, a schema, done. It's in-process, it's debuggable with a normal stack trace, and there's no extra thing to deploy or secure.
The honest framing: inline tools are cheaper; MCP buys you reuse and decoupling. You pay the protocol tax, and what you get back is that the same tool now works in Claude Desktop, your IDE, your colleague's agent, and a product you haven't built yet — without copying code into any of them.
So when does the tax pay off?
Three triggers, in order of how often they decide it:
Reuse across hosts. If two or more applications need the same capability, MCP turns N copies of integration code into one server. This is the original M+N argument, and it's the strongest case. An internal "query the data warehouse" server written once and consumed by every team's agent is exactly what the protocol is for.
It already exists. There's a catalog of reference servers — filesystem, Git, Fetch, Memory — plus a growing community registry. If someone already wrote and hardened the server you need, "build vs. reuse" isn't close. Pulling in @modelcontextprotocol/server-filesystem beats reimplementing safe path handling yourself.
A process or language boundary you'd cross anyway. Your model app is in Python but the capability lives in a Go service? You were going to talk to it over a wire regardless. MCP gives that wire a standard shape instead of a bespoke one.
If none of those hold — one app, no existing server, same process — write the inline function. You'll be done in five minutes and you won't have a daemon to babysit.
It helps to see how thin the line really is. An inline tool and its MCP equivalent describe the same operation with the same JSON Schema; the only thing that changes is whether the schema is defined in your file or fetched from a server's tools/list. The model can't tell the difference — it sees a name, a description, and parameters either way. That's why "promote a function to a server later" is a real, low-cost move rather than a rewrite: you lift the function into a server, the schema comes along unchanged, and every existing call site keeps working. Start inline without fear of painting yourself into a corner.
The trap: MCP-washing a private function
The failure mode I see most is teams wrapping a single internal function in an MCP server because MCP is the exciting word this year. Now they have a subprocess to launch, a transport to debug, and a tool that's harder to call than the function it wraps, with zero reuse to show for it. They paid the tax and bought nothing.
The tell is simple: if you can't name a second consumer — a different app, a teammate's agent, a future product — you probably don't need the protocol yet. You need a function. You can always promote it to an MCP server later, and the promotion is mechanical because, again, the schemas are the same on both sides.
A way to hold it in your head
Function calling is the verb: the model deciding and acting. MCP is the supply chain: how the things it can act on get manufactured, packaged, and shipped to wherever a model is running. Big systems need a supply chain. A weekend script needs a function.
Don't pick between them. Decide how far your tool has to travel — and let the distance pick for you.
Leave a Reply
Your email address will not be published.