MCP vs APIs vs Function Calling: Three Layers, Not Three Choices

By Arkaitz

You have an API. It has authentication, versioning, tests, and an on-call rotation. Someone now wants to put an MCP server in front of it, because that is what everyone is doing. Another engineer asks whether function calling makes the API obsolete. Both questions assume the three things compete.

They do not. An API is a contract between systems. Function calling is how a model asks your code to do something. MCP is how tools are discovered and transported between clients and servers. You can use all three in one product, or none of them. The decision is not which acronym wins. It is where the boundary belongs, who owns it, and what you are willing to pay to keep it.

That boundary is the part most agent architecture guides skip. They show the loop, the planner, and the memory. Then they draw a box labelled tools and move on. The box is where production incidents live.

This post gives you that decision in order. It also covers the two costs most teams discover late: context tokens and trust.

MCP vs function calling vs an API: what is the difference?

An API is the contract. It defines what a system can do, what it needs, what it returns, and what happens when it fails. REST, GraphQL, gRPC, a message topic, a database protocol: the shape changes, the role does not. The API exists whether or not a model is in the room.

Function calling is the model-facing action interface. You send the model a set of tool definitions. The model decides whether to call one and returns structured arguments. Your application executes the call and sends back the result. The model does not execute anything. It proposes; your code disposes.

MCP, the Model Context Protocol, is a discovery and transport standard. Anthropic published it in November 2024 as an open standard for connecting assistants to tools and data. (Anthropic) A host application runs clients. Each client holds a session with a server. The server exposes tools, resources, and prompts over JSON-RPC. The host controls which servers can connect, what permissions they get, and which calls a user must approve. (MCP specification)

The useful mental model, and the one I use when a team asks me to approve a new server, is a socket rather than a replacement part:

  • The API is the appliance.
  • Function calling is the plug the model can use.
  • MCP is the standard socket that lets many appliances and many plugs meet without a custom adapter for every pair.

That is why the three can coexist without redundancy. MCP standardises distribution. It does not standardise the business capability behind the tool, and it does not remove the model's decision. It also does not retrieve knowledge for you; retrieval is a separate context problem with its own architecture, which is why production-ready RAG systems and tool integration should be designed apart.

Why MCP does not replace function calling

Every MCP tool still reaches the model as an ordinary tool definition. The server returns a name, description, and input schema through tools/list. The client passes that schema to the model. The model emits a tool call. The client forwards it to the server. The server executes and returns a result. (Nango)

The protocol changes who authors the schema and how it travels. It does not change the model's act of choosing a tool. If a tool description is ambiguous, a tool name collides with another, or the model lacks the context to use the result, MCP does not fix it. You have moved the same bad interface behind a standard port.

This matters because teams often adopt MCP expecting a capability upgrade. What they get is a distribution upgrade. That can be valuable. It is not the same thing. If your agent picks the wrong tool today, wrapping the tool in an MCP server will not make it pick correctly. Better descriptions, fewer candidates, and deterministic checks will.

When is a plain API the right answer?

I would use a plain API when no model decides the sequence. A nightly reconciliation job, a payment webhook, a report generator, a migration script: these are deterministic workflows. Adding a planner adds latency, cost, non-determinism, and a new failure mode in exchange for flexibility the workflow does not need.

This is not an anti-AI position. It is a reliability position. A fixed sequence can be unit-tested, replayed, and reasoned about at 3 a.m. A model-driven sequence needs evaluations, guardrails, and a fallback path. Pay for that only when the flexibility earns it. The same contract discipline that makes a distributed system design survive retries and partial failure applies here: idempotency, explicit timeouts, and a clear owner for every side effect.

The API also remains the right public contract for humans, services, and partners. MCP is a protocol for model clients. It is not a replacement for a documented REST API, a stable SDK, or a webhook. Your customers are not going to integrate with your MCP server instead of your API. They may use both: the API for their systems, the MCP server for their agents. When the interaction is a fact that many consumers need, event-driven architecture is often the better contract than either a synchronous call or a tool definition.

There is a test that settles most of these cases. Ask: if the model disappeared tomorrow, would this interface still be useful? If yes, you have an API. If no, you have a model-specific tool, and you should design it as one.

When is function calling enough?

I would stop at function calling when one application owns both sides of the conversation. You control the model, the prompt, the tool definitions, and the execution. The tool set is small and stable. The tools are specific to that application rather than shared infrastructure.

In that setting, MCP adds a server process, a session lifecycle, a capability negotiation, and a discovery layer you do not need. You can define five functions in the same codebase that runs the agent, deploy them together, and test them with the same fixtures. The boundary is already clear because there is only one boundary.

Function calling also keeps authorization simple. The tool runs inside your service, with the user's request context and your existing permission model. An MCP server adds another identity to reason about: which client is calling, on whose behalf, with which credentials, and under which consent. That is manageable, but it is not free.

The signals that you have outgrown pure function calling are concrete. A second client wants the same tools. A different team owns the system behind them. The catalog has grown past the point where you can curate it by hand. Or a third party needs to publish tools you do not want to copy into your codebase. Until one of those is true, keep the tools local.

When does an MCP server earn its place?

I would approve an MCP server when the tool surface is shared and must be discoverable by more than one client. That is the whole case. Everything else is a detail.

Picture an internal platform team that owns deployment status, feature flags, and incident history. Three clients need those capabilities: the company's coding agent, the support team's assistant, and a CI agent that writes release notes. Without a shared layer, each client reimplements the same three integrations and drifts. With an MCP server, the platform team publishes one surface, versions it, and audits it centrally. That is a real win.

The same logic applies to a vendor. If you sell a developer product and want agents to use it, an MCP server is a distribution channel. The vendor maintains the tool definitions; customers do not copy brittle wrappers. The protocol's neutrality helps here: Anthropic donated MCP to the Linux Foundation's Agentic AI Foundation in December 2025, with Anthropic, Block, and OpenAI as co-founders and Google, Microsoft, AWS, Cloudflare, and Bloomberg supporting. (Anthropic) A standard that one vendor controls is a dependency. A standard with neutral governance is infrastructure.

MCP also helps when the catalog is genuinely large, or when the tool surface belongs to a third party you do not control. Discovery beats hardcoding when you cannot know at build time which tools a user's environment exposes. But large catalogs are also where the context cost bites, which is the next section.

What does MCP cost in context?

Tool definitions are prompt text. Every server you connect tends to inject its full catalog: names, descriptions, parameter schemas, and examples. That text is paid for on every request.

The numbers are large enough to change design decisions. Atlassian measured its official MCP server at roughly 10,000 tokens for Jira and Confluence tools alone. The official GitHub server exposes 94 tools and consumes roughly 17.6k tokens. Combine a few servers and you can spend 30k tokens on tool descriptions before the model reads a user message. (Atlassian)

That is not just a bill. It is attention. A model choosing among dozens of similar tools has more opportunities to pick the wrong one. Tool selection degrades as the candidate set grows, and irrelevant tools are noise the model must attend past on every turn.

More capability can make the agent slower and less accurate at the same time.

My rule is that the fixes are architectural, not cosmetic:

  • Enable servers per task. Do not connect the whole company catalog to every session.
  • Shrink the catalog at the server. Expose the ten operations users need, not the hundred the backend supports.
  • Compress descriptions. A proxy or gateway can remove redundant schema text; Atlassian reports 70–97% reductions without changing how the agent calls tools.
  • Split by domain. Give a subagent only the tools for its job instead of giving every agent every tool.
  • Prefer code execution for large result sets. Let the agent write a small script that calls the tool and filters the output, instead of pushing every intermediate result through the context window.

The last point is the direction Anthropic has documented: when agents connect to hundreds or thousands of tools, loading every definition and passing every result through context stops scaling. Code execution moves the orchestration into a runtime and keeps the context for decisions. (Anthropic)

None of this means MCP is too expensive. It means the cost is a design variable. A ten-tool server that saves three teams from writing the same integration is cheap. A forty-tool server connected just in case is expensive even when the subscription is free.

What does MCP cost in security?

MCP moves trust. It does not create it. Every server you connect is code and metadata from someone else, and the model treats both as instructions.

The clearest demonstration is tool poisoning. Invariant Labs showed that a malicious MCP server can hide instructions inside a tool description that users do not see in full. In their experiment, a harmless-looking add tool told Cursor to read the user's ~/.cursor/mcp.json and ~/.ssh/id_rsa and pass the contents back as a parameter. The user approved an addition; the agent read private keys. (Invariant Labs)

Two details make this worse than a single bad package. First, a server can rug pull: it passes review, then changes its tool description later. Second, a malicious server can shadow a trusted one, adding instructions that change how the agent uses a different server's tools. In Invariant's second experiment, a malicious math tool made the agent send every email to an attacker's address, even when the user named a different recipient.

The server code is ordinary software with ordinary bugs. A March 2025 survey of public MCP server implementations, cited by Elastic Security Labs, found that 43% had command-injection flaws and 30% allowed unrestricted URL fetching; the protocol does not sandbox the implementation for you. (Elastic Security Labs) OWASP's 2025 list names the governance failure directly: excessive agency, split into excessive functionality, excessive permissions, and excessive autonomy. (OWASP) A server that can read every table because it was easier than scoping a role is an excessive-agency incident waiting for a prompt.

Why a local server is not automatically safer

Local describes the transport, not the trust boundary. A local server runs with your user's permissions, which may include your SSH keys, cloud credentials, browser profile, and source checkout. A remote server at least forces you to think about authentication. A local one can quietly inherit everything.

The right question is not local or remote. It is: what can this process reach, what can it send out, and who reviewed the code? If the answer is everything and anywhere, the transport does not matter. That is the same blast-radius question you answer in a highly available system design, applied to a process that can now take actions.

The minimum viable control set

I would not try to prompt my way out of prompt injection. The model cannot reliably distinguish data from instructions, so treat every tool description and tool result as untrusted input. Build controls around the model instead:

  • Pin and review. Treat MCP servers like dependencies. Pin versions, review descriptions, and re-review on update.
  • Scope credentials per server. Never give a server a shared admin token because it was convenient.
  • Reduce capability. Expose narrow verbs, not a generic query interface. Read-only by default; write access only where needed.
  • Require approval for irreversible actions. Sending money, deleting data, deploying, and changing permissions should not be one model token away.
  • Control egress. Allowlist outbound destinations; a poisoned tool that cannot reach the internet is a smaller incident.
  • Log tool calls and results. Audit the arguments as well as the name. The hidden payload often travels in an argument.
  • Isolate trust domains. Do not let a server that reads public web pages share a session with a server that holds production credentials.

Microsoft's guidance on indirect prompt injection makes the same point: the defense has to live in the system around the model, not in a stronger instruction. (Microsoft) MCP's specification defines useful boundaries between host, client, and server, including that servers should not see the whole conversation or into other servers. Those boundaries only help if the host enforces them.

How should you wrap an API as an MCP server?

I would wrap the API and never replace it. The MCP server should be a thin adapter that translates a model's intent into the same authorized calls your other clients make. The domain logic, validation, and audit trail stay in the API.

A good adapter has five properties:

  1. Narrow tools. get_deployment_status(service, environment) beats run_query(sql). The tool name should describe an outcome, not a mechanism.
  2. Stable, small schemas. Required fields only. Defaults where the model cannot know the value. Descriptions that state when not to use the tool.
  3. Server-side authorization. The server validates the caller, the tenant, and the user's permission on every call. Never rely on the model to pass the right tenant ID.
  4. Bounded outputs. Return the fields the model needs and paginate the rest. A tool that dumps a 2 MB JSON blob consumes context and hides the answer.
  5. Idempotency and rate limits. Models retry. A create operation without an idempotency key becomes a duplicate record. A read loop without a limit becomes a bill.

The same discipline you apply to a public API applies here: version the tools, document the failure modes, and make the error messages actionable. A model that receives 403 will guess. A model that receives the user lacks `deploy:write` on service `checkout` can stop and ask.

What does the decision look like in practice?

Three common cases, three different answers.

Internal coding agent

The agent needs to read the repo, run tests, and query the issue tracker. Repo and test tools are app-specific and run on the developer's machine: define them directly with function calling. The issue tracker is shared across teams and already has a maintained MCP server: connect that one, scoped to the user's token, with write operations behind approval. You get a small local tool surface and one governed integration.

SaaS product with customer data

The product needs an assistant that answers questions about a customer's account. Do not expose a database MCP server. Keep a domain API that enforces tenant isolation, then expose three read-only tools to the assistant: account summary, invoice history, and subscription status. The assistant never sees a connection string, and the authorization model is the one you already test.

Enterprise data platform

Ten teams want their agents to query curated datasets. A central MCP gateway can own discovery, authentication, quotas, and audit while each team publishes a narrow server. The gateway is where you solve the context problem too: it exposes only the datasets the caller is entitled to and only the operations the task needs.

What should you do first?

I run the decision in this order:

  1. Remove the model if you can. If the workflow is fixed, use the API or a queue. You will ship sooner and sleep better.
  2. Count the clients. One client and one backend? Function calling. More than one client that needs the same tools? Consider MCP.
  3. Count the tools. If the catalog is small and stable, curate it in code. If it is large or externally owned, discovery starts to pay.
  4. Price the context. Estimate the tool-definition tokens per request. If the catalog is a large fraction of your window, fix that before adding another server.
  5. Draw the trust boundary. List what each server can read, what it can send, and who reviewed it. If you cannot answer, you are not ready to connect it.

The point of the layer model is not to pick a winner. It is to stop asking one layer to do another layer's job. Your API should stay a contract. Your function calls should stay small and specific. Your MCP servers should exist where sharing and discovery genuinely reduce duplication.

If you are the manager funding this work, the engineering manager's view of the LLM tool stack gives you the questions to ask before the invoice arrives: what does each server reach, who reviews it, and what happens when it is wrong. The answers are architecture, not procurement.

The newest acronym is not an architecture. The boundary is.

Related Reading