Every few weeks someone asks me whether they should “switch to MCP,” and the question usually contains a category error. MCP and function calling are not competing options at the same layer. One is a wire protocol for connecting tools to clients at runtime. The other is a way of describing tools to a model inside a request. You can use both at once, and plenty of people do without realizing it.
Since I run a router that serves the same nine tools both ways (as a remote MCP server and as plain REST endpoints you can wrap in function definitions), I have opinions about when each side earns its complexity.
What function calling actually is
Function calling is a schema convention. You send the model a JSON description of some functions (name, description, parameters), the model replies with “call this function with these arguments,” your code executes whatever that means, and you feed the result back. That is the whole loop.
The crucial detail: the model never calls anything. Your code does. The “function” can be a local method, an HTTP request, a database query, anything. Function calling is a contract between you and the model about how tool intent gets expressed, and it lives entirely inside your agent loop.
This means function calling is enough whenever you control the loop. One codebase, one agent, tools you wrote or wrapped yourself: just write the schemas, write the dispatch code, done. There is no protocol overhead because there is no protocol. It is the simplest thing that works, and for a single production agent it is usually the right thing.
What MCP actually is
MCP (Model Context Protocol) standardizes the layer function calling leaves undefined: how a client discovers what tools exist and how it invokes them. An MCP server publishes its tools; any MCP client (Claude, IDEs, desktop apps, other agents) can connect, list them, and call them, without anyone writing bespoke dispatch code for that pairing.
The key property is runtime discovery across trust boundaries. With function calling, the tool list is compiled into your application. With MCP, the tool list belongs to the server, and clients you have never met can consume it. That is a genuinely different capability, not a nicer syntax for the same one.
MCP servers come in two flavors: local (a process speaking over stdio on your machine) and remote (a server you connect to over the network). I covered the distinction in more depth in MCP servers explained for developers, but the short version is that remote servers are what make MCP interesting for hosted tools: one URL, no local install, credentials handled server-side.
When you need MCP
The pattern I keep seeing: MCP earns its keep when the person configuring tools is not the person who wrote the agent.
- Off-the-shelf clients. If your users live in Claude or an MCP-capable IDE, MCP is the only way in. There is no agent loop of yours to add function schemas to.
- Many clients, one toolset. If the same tools need to reach a chat client, an internal agent, and a teammate’s experiment, one MCP server beats reimplementing dispatch three times.
- Tools that change without redeploys. Because clients discover tools at connect time, a server can add or modify tools and every client picks them up. With function calling, that is a code change in every consumer.
This is exactly why route.tools ships a remote MCP server exposing all nine tool categories (search, scrape, parse, and the rest): someone using an MCP client gets the whole catalog, with failover and routing intact, without writing a line of integration code. Setup is at /docs/mcp.
When plain function definitions are enough
Equally real: a lot of MCP adoption I see is complexity cosplay. If you are building one agent, in one codebase, calling tools over HTTP, then wrapping those HTTP calls in an MCP server so your own agent can consume them through an MCP client is three layers where one would do. Write the function schemas, call the APIs, ship.
Signals that function calling alone is fine:
- You control the agent loop end to end.
- Your tool list is stable and small.
- Nobody outside your codebase needs these tools.
- Latency and debuggability matter more than pluggability. Every layer between “model emitted a tool call” and “HTTP request happened” is a layer you will eventually debug at 2am.
There is also a context cost argument that cuts both ways: every tool definition you expose, via either mechanism, occupies context window. A protocol does not make tool descriptions free. I dig into that trade-off in one MCP server vs many.
The honest summary
Function calling is how a model expresses tool intent. MCP is how tools get discovered and invoked across application boundaries. If your tools and your agent live in the same repo, schemas are enough. The moment your tools need to outlive one client, one codebase, or one team, a protocol stops being overhead and starts being the product.
If you want to try both shapes against the same nine tools, the quickstart covers REST and MCP with one key and $2 of free credits.