Function calling and the Model Context Protocol solve related but different problems. Function calling is a model feature: you describe a function, the model returns arguments, your code runs it. MCP is a protocol for exposing tools, resources and prompts to any compatible host. If you are building one application with a handful of tools, function calling is enough. MCP starts to pay off when the same tools need to work across multiple hosts, or when another team owns them.
Function calling: the model asks your code to do something
With function calling, you define a schema and pass it to the model. The model decides whether to call it and returns structured arguments. Your application executes the function and sends the result back. The loop lives in your code, which means you control retries, authorization and logging.
This is the right default. There is no extra server, no discovery step, and no protocol version to track. The schema sits next to the code that implements it, so drift is visible in review.
{
"name": "create_issue",
"description": "Create a GitHub issue in the configured repository",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string"},
"body": {"type": "string"}
},
"required": ["title", "body"]
}
}MCP: a standard server for tools and context
MCP defines a host, one or more clients, and servers that expose capabilities over JSON-RPC. A server can offer tools the model may call, resources the application may attach, and prompts the user may choose. The host discovers what a server supports instead of hard-coding every schema.
The value is reuse. The same filesystem or issue-tracker server can work in an IDE, a chat app and your own agent. The cost is a moving part: a separate process or service, its authentication, its lifecycle and its protocol version.
Host (IDE, chat app, your agent)
├── MCP client A → files server
├── MCP client B → issue tracker
└── MCP client C → database (read-only)The decision that actually matters
Ask who owns the tools and how many hosts need them. If the answer is 'my team, one application', use function calling. If the answer is 'another team, several applications, and we need discovery', MCP is worth the overhead.
| Situation | Function calling | MCP |
|---|---|---|
| One app, a few internal tools | Best fit | Extra moving parts |
| Same tools across IDE, chat and agent | Duplicated schemas | Strong fit |
| Tools owned by another team | Tight coupling | Clear boundary |
| Offline or embedded runtime | Simpler | Needs a local server |
| Strict audit of every tool call | You own the loop | You own the server too |
Where MCP gets dangerous
A protocol does not make a tool safe. The common failures are predictable. Tool descriptions can be poisoned with instructions the model treats as trusted. A server may request broader scopes than its tools need. A new version of a server can change a schema under a host that cached the old one.
- Pin and review server versions; treat a schema change as an API change.
- Show the user which server and tool is about to run, especially for writes.
- Scope credentials per server, not per host.
- Validate arguments on the server. The model is not a validator.
- Log the tool name, arguments and result hash for every call.
A practical migration path
You do not have to choose once and forever. Start with function calling inside the application. Extract the tool implementations behind a small internal interface. When a second host needs them, put an MCP server in front of that interface. The tool logic does not change; the transport does.
The worst outcome is adopting a protocol before you have a second consumer. You pay the operational cost early and get none of the reuse.
What to remember
- Function calling is a model feature; MCP is a protocol for sharing tools across hosts.
- Use function calling for one app with a few internal tools.
- Reach for MCP when multiple hosts or another team need the same capabilities.
- A protocol does not provide authorization, validation or safety; you still own those.
- Extract tools behind an interface first, then add MCP when a second consumer appears.