AI coding

MCP vs Function Calling: Deciding What to Connect and How

Antonio Evans10 min read

A split scene: on the left, a developer at a desk with a single laptop, a direct line drawn from the laptop to a small API icon; on the right, three different client devices (laptop, phone.
Key points

Key points

  1. 1Function calling is a model-level ability to emit a structured request, and your code still executes the call.
  2. 2MCP is a shared protocol that exposes tools, resources and prompts from servers to any compatible client.
  3. 3Use function calling for a few tools inside one app you fully control, and MCP when multiple clients need the same tools.
  4. 4The integration math decides most cases: N clients times M tools means N+M connector builds with a protocol.

Two Different Layers, Not Two Rivals

The most common mistake in this comparison is treating function calling and MCP as alternatives at the same layer. They are not. Function calling is a capability of the model API: you send a set of JSON schemas alongside the prompt, the model decides whether to call one, and it returns structured arguments that your application code then executes in the Prefect walkthrough. MCP sits above that. It is an open standard for connecting AI applications to external systems, described by its authors as a USB-C port for AI applications in the MCP introduction.

That layering matters because it tells you what each mechanism can and cannot solve. Function calling solves routing: given a user request and a list of available operations, which one should run, with what arguments. MCP solves distribution: how does one tool, built once, become available to every host, client and agent that might want it.

Anthropic open-sourced MCP as a standard for connecting assistants to the systems where data lives, with the explicit goal of replacing fragmented, per-source integrations with a single protocol in the original announcement. The protocol itself uses JSON-RPC 2.0 and defines a client-server architecture with tools, resources and prompts as its core server primitives in the architecture overview.

The practical consequence: you can run MCP without ever thinking about a model's tool schema format, and you can run function calling without MCP at all. Most production systems do both, with MCP doing the transport and function calling doing the last mile between the host and the model.

What Function Calling Actually Gives You

Function calling is a contract between you and one model provider. You define tools as JSON schemas, send them with the prompt, and handle the dispatch loop yourself: match the model's call to your function, run it, feed the result back. Prefect's comparison shows the same weather tool written for Anthropic and OpenAI, and the definitions are near-identical except for the wrapper: Anthropic nests the schema under input_schema, while OpenAI nests it under parameters inside a function object in the same walkthrough.

That small difference is the whole story of function calling's cost. Every provider has its own shape, so a tool you want in two agents on two providers means two definitions, a translation layer, or both. A beginner-friendly breakdown makes the same distinction in simpler terms: the model converts a request into a JSON call and your backend executes it, which keeps control tight and reduces hallucination because the set of possible actions is fixed in the function calling explainer.

Function calling is also the fastest path to a working prototype. There is no server to deploy, no transport to configure and no lifecycle to negotiate. The tool logic lives in the same codebase as the prompt, which means one deploy updates both. For a single application with a handful of internal tools, that simplicity is not a compromise. It is the correct design.

The costs arrive later, and they arrive predictably:

CostWhat it looks likeWhen it bites
Schema duplicationSame tool defined per providerSecond provider or second agent
No discoveryTool list hardcoded in each appEvery new tool is an N-way code change
No sharingEach team rebuilds the connectorThird team asks for the same integration
Tight couplingTool and app deploy togetherTool changes break unrelated apps

What MCP Adds on Top

MCP's contribution is structural. Databricks frames it as the fix for the N times M integration problem: without a protocol, every client must integrate with every tool, producing N times M separate integrations, while a shared protocol requires each client and each server to implement it once, reducing the total to N plus M in the Databricks explainer.

That arithmetic is the decision rule most teams should apply first. Work out how many clients and how many tools you expect over the life of the system. If either number stays at one, function calling is simpler. If both grow, the shared protocol wins on maintenance alone, before you count any other benefit.

MCP also standardizes discovery. Rather than hardcoded endpoints, MCP servers expose a machine-readable capability surface that clients query at runtime, and the protocol supports bidirectional, stateful communication with streaming and progress notifications that ordinary request-response APIs do not provide natively per the same Databricks analysis. Google's overview describes the same flow concretely: a client searches available tools, finds a database query tool and an email tool, invokes each in turn, and the servers translate the calls into secure SQL and an outbound message before returning results to the model in Google Cloud's MCP guide.

The third primitive is the one people forget. Besides tools, MCP servers expose resources, which are application-controlled context like files, database schemas or logs identified by a URI per the resources specification. Servers can also expose prompts, which are templated workflows that a user selects explicitly, typically surfaced as slash commands per the prompts specification. Function calling has no equivalent for either. If your integration story is "read this config file and run this standard review," MCP models it directly; function calling makes you wrap both as tools.

A useful distinction from the community docs: resources are designed to be application-controlled, so the client decides when they enter context, while tools are model-controlled, meaning the model decides when to invoke them in the resources concept guide. That framing helps you classify each capability before you write a line of server code.

from fastmcp import FastMCP

mcp = FastMCP("Support")

@mcp.tool()
def search_tickets(status: str, priority: str = "high", limit: int = 10) -> list:
    """Search support tickets by status and priority."""
    return query_tickets(status, priority, limit)

if __name__ == "__main__":
    mcp.run()

That is the whole server side. Notice what the host gets: a tool name, a description and a schema, discovered at runtime rather than pasted from a docs page. Any compatible client can now call it, and the clients listed in the MCP introduction span assistants, editors and agents per the getting started page.

A Decision Rule You Can Apply Today

Most teams do not need a philosophical answer. They need a repeatable test. Run these four questions in order and stop at the first strong signal.

Question 1: How many clients will call this tool? One application you own means function calling is enough. Two or more clients from different codebases, teams or vendors means a shared interface is paying for itself. This is the N plus M argument in miniature.

Question 2: Who changes the tool most often? If the team that owns the app also owns every tool, keep them together and skip the protocol. If a platform or data team owns the tools while product teams own the clients, MCP gives you a boundary where the interface is versioned and discoverable rather than a shared library everyone must upgrade.

Question 3: Do you need resources or prompts, not just actions? If the integration is mostly "read this data" or "run this standard workflow," MCP's resources and prompts primitives fit better than forcing everything through a tool schema per the resources specification.

Question 4: What is your risk appetite for third-party tools? This is the question the first search result usually skips. Connecting an agent to someone else's MCP server is a security decision, not just an integration decision, and it deserves its own gate.

SignalChoose function callingChoose MCP
Clients calling the toolOneTwo or more
Tool ownershipSame team as the appSeparate platform team
Interface stabilityChanges with the appNeeds a versioned contract
Context needsActions onlyResources or prompts too
Third-party accessNot neededNeeded, with an allowlist

One hybrid pattern works well and deserves a mention: expose tools through an MCP server, then let the host translate those tools into whatever function calling format the selected model expects. The server stays provider-neutral, the host absorbs the schema differences, and you get distribution without rewriting tool logic per provider. This is the architecture MCP was designed for, since the protocol focuses solely on context exchange and does not dictate how applications use models per the architecture overview.

A Worked Example With Numbers

Suppose your platform team supports three internal AI clients: an IDE assistant, a support chatbot and an analyst agent. Each needs access to five internal services: ticketing, billing lookup, a deployment log reader, a customer database and an email sender.

With direct function calling in each client, you build and maintain 3 times 5 equals 15 connector implementations. Add a fourth client next quarter and you are at 20. The same numbers under a shared protocol require each client and each server to implement the protocol once, so you build 3 plus 5 equals 8 integrations, and the fourth client brings that to 9 per the Databricks N plus M framing.

The absolute numbers matter less than the shape. The function calling line grows multiplicatively with every client; the protocol line grows additively. At three clients the gap is 15 against 8. At six clients it is 30 against 11. The crossover happens earlier than most teams expect, which is why the honest advice is: start with function calling, and switch the moment the second client appears rather than trying to predict the third.

There is a second number to track, time to value. Mitiga's comparison puts a hand-built API integration at days of work (read the docs, set up auth, map fields, handle pagination, catch errors) against minutes to connect an MCP server once in Mitiga's MCP vs API writeup. That estimate assumes a server already exists for the service you need. If you have to build the server, the first integration costs more than the direct call, and every subsequent client is nearly free. Budget the build against the number of clients you expect, not the first one.

For teams standardizing on one provider and one internal app, none of this applies. Skip the server, write the schemas, ship. The decision rule is about expected client count and ownership boundaries, not about which technology is more modern.

Security Is the Hidden Variable

Here is the insight that changes the decision for many teams: MCP shifts part of your trust model from your code to other people's servers. Tool descriptions are visible to the model in full, and Invariant Labs documented tool poisoning attacks where hidden instructions embedded in a tool description cause the model to read sensitive files and exfiltrate them through a parameter, all while the user sees a benign summary in the Invariant Labs disclosure.

The OWASP entry explains why this works: tool descriptions are reviewed once at connect time, but tool responses flow into the model's context with no equivalent check, and that unguarded runtime channel is what an attacker abuses per the OWASP Foundation entry. Elastic's research adds variants including rug-pull redefinitions, where a server changes a tool's definition after approval, and cross-tool orchestration, where one tool's output steers calls to another per Elastic Security Labs.

None of this argues against MCP. It argues for treating the connect decision as a security decision, which function calling inside your own codebase largely avoids because every tool is yours. If you adopt MCP, the defenses are concrete:

  • Maintain an allowlist of approved servers and do not let users point agents at arbitrary URLs per OWASP.
  • Constrain tool responses to a fixed schema and reject anything that does not match, since free text is where injected instructions hide per OWASP.
  • Isolate high-privilege tools like file access and internal APIs in a separate agent context that external servers cannot reach per OWASP.
  • Enforce restrictions server-side at the tool execution layer rather than relying on system prompt instructions per OWASP.
  • Require explicit user confirmation outside the model context before destructive or data-moving actions per OWASP.

The MCP specification itself takes a position on this: tool annotations and descriptions should be considered untrusted unless they come from a trusted server, and hosts must obtain explicit user consent before invoking a tool per the specification. If your threat model cannot tolerate a compromised third-party server influencing your agent's behavior, function calling with your own vetted tools remains the safer default.

Where Each Approach Fits in Practice

Function calling fits greenfield agents, internal copilots with a fixed toolset, and any case where latency matters and the tool and the app ship together. It also fits regulated flows where the entire call path must be auditable inside one codebase.

MCP fits platform teams exposing an internal service to several AI surfaces, vendors who want their product usable from any assistant, and any organization where the list of tools changes without a client redeploy. The discovery model is the payoff: clients query what is available at runtime, so a new tool appears to every connected client without shipping new client code per the Databricks comparison of MCP against hardcoded APIs.

A third option worth naming is the plain API call with no model in the loop at all. When your backend needs to fetch a user profile, it should not ask a model to decide. Function calling adds intelligent routing on top of an API call, and MCP adds standardized distribution on top of that per the Jam with AI decision framework. Use the simplest layer that solves your problem, and only climb when a real constraint pushes you up.

If the model roster itself is a moving part, one API for text, image, video, audio and embedding models through a single OpenAI-compatible key removes another translation layer. That is where Quanta Inference fits: it keeps the provider choice behind one interface so your tool schemas and MCP servers do not need to be rewritten every time the underlying model changes.

FAQ

FAQ

Is MCP just function calling with extra steps?

No. Function calling is a model API feature that returns structured arguments for your code to execute; MCP is a protocol that moves tool discovery and execution to a server any compatible client can reach per Prefect. They operate at different layers and are commonly used together, with MCP handling transport and function calling handling the host-to-model step.

Can I use MCP without function calling?

In practice the host still needs a way to tell the model which tools exist and collect its choice, which is what function calling provides. MCP standardizes how tools and resources are discovered and invoked across clients and servers, but it does not dictate how the application uses models per the architecture overview.

How many clients justify building an MCP server?

The honest threshold is two clients from different codebases or teams. At N clients and M tools, direct integration costs N times M builds while a shared protocol costs N plus M, so the gap opens quickly per Databricks. Below that, the build cost of the server usually outweighs the duplication you avoid.

What are MCP resources and prompts for?

Resources expose context data such as files, database schemas or logs, and are application-controlled rather than model-controlled per the resources guide. Prompts expose reusable templates that users select explicitly, often as slash commands per the prompts specification. Neither has a direct equivalent in plain function calling.

Is connecting a third-party MCP server safe?

Only with controls. Tool poisoning attacks hide instructions in tool descriptions that the model reads but users do not per Invariant Labs, and OWASP recommends an allowlist of approved servers, schema-constrained responses and server-side enforcement of access rules per OWASP. If you cannot vet the server, keep the tool inside your own codebase.

Sources

Sources

  1. MCP vs Function Calling: When to Use Which - Prefect
  2. What is the Model Context Protocol (MCP)?
  3. Introducing the Model Context Protocol - Anthropic
  4. Architecture overview - What is the Model Context Protocol (MCP)?
  5. Function Calling vs Tool Calling in LLMs: A Beginner ...
  6. What is the Model Context Protocol (MCP)? - Databricks
  7. What is Model Context Protocol (MCP)? A guide | Google Cloud
  8. Resources
  9. Prompts
  10. Resources
  11. MCP vs API: When to Use Each (and Why You Need Both) - Mitiga.io
  12. MCP Security Notification: Tool Poisoning Attacks - Invariant Labs
  13. MCP Tool Poisoning | OWASP Foundation
  14. MCP Tools: Attack Vectors and Defense Recommendations for ...
  15. Specification
  16. When to use - MCP vs API vs Function/Tool call in your AI Agent
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles