
MCP vs API: When to Build an MCP Server, and When the API Is Enough
MCP vs API: MCP lets AI agents discover and call tools at runtime; APIs are the services underneath. Differences, working code, security, when to use each.
Table of Contents
MCP vs API is rarely an either-or choice: MCP is a protocol that lets an AI agent discover and call tools at runtime, and an API is usually the service those tools wrap, so most teams building agents end up with both. The real decision is narrower. When does an agent need its own interface to your system, and when is the API you already have enough?
We ran into this at my company when we wanted Claude and our internal agents to work with the same data our web and mobile apps use every day. We already had a REST API. We built an MCP server anyway. Ours runs on Google Cloud, and the reasons for building it were not the ones I expected. Let us walk through what MCP is, what it adds, what it costs, and a simple way to decide.
The short answer: MCP vs API
An API, usually a REST API, is a contract for software a developer writes. The developer reads the documentation, picks the endpoints and ships code that calls them in a known order. An MCP server is a contract for a model. The agent asks the server what it can do, reads the descriptions, and decides at runtime which tool to call and with what arguments.
| API (usually REST) | MCP server | |
|---|---|---|
| Who decides what to call | A developer, at build time | The model, at runtime |
| How capabilities are found | Documentation or an OpenAPI file a person reads | tools/list: names, descriptions and input schemas returned by the server |
| Transport | HTTP | JSON-RPC over stdio (local) or Streamable HTTP (remote) |
| Auth | API keys, OAuth or service tokens | Optional; OAuth 2.1 for HTTP servers, environment credentials for local ones |
| State | Stateless per request | Stateless per request in the current spec; each request carries its own version and capabilities |
| Versioning | URL or header versions you define | The protocol is versioned by date; your tool names, schemas and descriptions are your contract |
| Context cost | None until your code puts data in a prompt | Tool definitions occupy the model's context window |
| Latency | One network call | Your API call, plus a hop to the MCP server, plus the model's decision |
| Clients | Your own code | Any MCP host: Claude, Claude Code, Cursor, VS Code and others |
| Best for | Known workflows, high volume, integrations you control | Open-ended requests where you cannot predict the sequence of calls |
So the MCP server vs API question is really a question about who is calling. If the sequence of calls is known in advance, write code against the API. If people will ask an agent for things you cannot list in advance, and the agent has to reach your system to answer, that is when an MCP server starts to earn its keep.
What MCP actually is, and what it isn't
The Model Context Protocol is an open protocol, currently specified in its 2026-07-28 revision, for connecting AI applications to tools and data. It uses JSON-RPC 2.0 messages between three roles: a host (the AI application, such as Claude Code), a client inside that host, and a server that offers capabilities.
A server can offer three things: tools the model can call, resources that provide context, and prompts, which are templates a user can invoke. Most servers start with tools, and so did we.
There are two standard transports. stdio runs the server as a local subprocess of the host, which suits developer tools on a laptop. Streamable HTTP sends each message as an HTTP POST to a single endpoint, with the reply as a JSON object or a stream scoped to that request, which suits a shared, remote server.
The current revision made the protocol stateless. Earlier revisions opened a connection-scoped session with an initialize handshake, and many articles still describe that model, along with the older HTTP-plus-SSE transport. Today every request carries its protocol version and client capabilities, so a remote MCP server can sit behind an ordinary load balancer like any other HTTP service.
What MCP is not: a replacement for your backend, or a source of new capability. Every tool does its work by calling something underneath, usually the same API your apps use. If the API cannot do it, the MCP server cannot either.
Can an API be an MCP server? Wrapping one in about 30 lines
Yes. The most common MCP server is a thin, carefully described layer over an existing API, and wrapping one endpoint is the quickest way to learn how to create an MCP server. Here is the job of finding a customer's overdue invoices, first as a direct API call. The endpoint is illustrative, not ours.
import os
import httpx
resp = httpx.get(
"https://api.example.com/v1/invoices",
params={"customer_id": "C-1042", "status": "unpaid", "overdue_days_gte": 30},
headers={"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
timeout=10,
)
resp.raise_for_status()
for invoice in resp.json()["items"]:
print(invoice["number"], invoice["amount_due"])
Now the same capability as an MCP tool, using the official Python SDK. It calls the same endpoint. What changes is that a model can find it, read when to use it, and supply the arguments itself.
import os
import httpx
from mcp.server.mcpserver import MCPServer
from mcp.types import ToolAnnotations
API_BASE = os.environ.get("API_BASE", "https://api.example.com/v1")
mcp = MCPServer("invoices")
@mcp.tool(annotations=ToolAnnotations(read_only_hint=True))
async def get_overdue_invoices(customer_id: str, days_overdue: int = 30) -> list[dict]:
"""List a customer's unpaid invoices that are more than `days_overdue` days past due.
Use this when asked who owes money or which invoices to chase.
Returns an empty list when nothing is overdue; that is an answer, not an error.
"""
async with httpx.AsyncClient(
base_url=API_BASE,
headers={"Authorization": f"Bearer {os.environ['API_TOKEN']}"},
timeout=10,
) as client:
resp = await client.get(
"/invoices",
params={"customer_id": customer_id, "status": "unpaid", "overdue_days_gte": days_overdue},
)
resp.raise_for_status()
return resp.json()["items"]
if __name__ == "__main__":
mcp.run(transport="streamable-http") # serves http://127.0.0.1:8000/mcp
I ran this with version 2.2.0 of the SDK against a small local stand-in for the invoice API. A client connected over Streamable HTTP, listed the tool with its description, input schema and read-only hint, and the call returned the stand-in's invoice. If you are on version 1 of the SDK, the same class is called FastMCP and lives in mcp.server.fastmcp; version 2 renamed it.
To use it from Claude Code, register the endpoint (the syntax is in Claude Code's MCP documentation):
claude mcp add --transport http invoices http://127.0.0.1:8000/mcp
Three details are worth copying. The docstring is the description the model reads, so it says when to use the tool and what an empty result means. The read-only annotation tells hosts the tool changes nothing, although a host should only trust that from a server it trusts. And the tool is shaped around a question someone asks, "which invoices are overdue", rather than around an endpoint with a dozen filters.
When to use MCP vs API
Work down the list and stop at the first yes.
- Is the sequence of calls known in advance? Call the API from code. A nightly job that pulls orders, asks a model to classify them and writes the labels back is a script; the model sits inside your workflow and has nothing to discover.
- Is there exactly one agent, in one framework, using a handful of tools? Define the tools in that framework's function calling. Move them into an MCP server when a second consumer appears.
- Do you need high volume or tight latency? Keep the model out of the request path and call the API directly.
- Will people ask open-ended questions that need live data from your system, from more than one AI host? Build an MCP server on top of your API.
- Does the agent need to change data? Build the server with read tools first, then add narrowly scoped write tools that ask for confirmation.
- Does the agent need a method rather than access, such as how your team writes a release note? That is not an MCP problem. It is a skill, and Claude Skills vs MCP covers where that line sits.
For us, question four decided it. Our one MCP server serves an engineer asking Claude Code about a customer's configuration and a colleague asking Claude a business question. Building a separate wrapper for each host would have meant maintaining the same integration several times.
What we changed after putting an MCP server in front of our APIs
The first version of our MCP server mapped endpoints to tools one to one, which exposed around 150 functions to the model. It worked, and the agent was poor at using it. With that many fine-grained tools, it chained the wrong ones together and spent context on definitions it never needed.
What worked better:
- Fewer, task-shaped tools. One tool that answers "what is the status of this order" can call three endpoints underneath. The model makes one decision instead of three.
- Read before write. We started with read-only tools and added writes one at a time, each narrowly scoped and confirmed by the user in the host. The server has write tools today, and each has a well-defined input and output schema, so what goes in and what comes back is fixed rather than left to the model.
- The user's permissions, not the server's. A call made for a person should see only what that person could see in the app. The specification forbids an MCP server from simply passing the client's token through to your API, so the server has to work out what the user is allowed to do itself. Ours calls an IAM endpoint that returns which functions the signed-in user has permission to use, and works within that list.
- Descriptions under review. A change to a tool description goes through review like an API change, because it changes behaviour.
MCP vs function calling vs CLI
These three get compared constantly, and they sit at different layers.
Function calling is the model-side mechanism. You send tool definitions in an API request, the model replies asking to call one, your code runs it and returns the result. Every MCP host uses function calling underneath. MCP standardises where the definitions come from, so they arrive from a server at runtime instead of being written into each application.
A CLI is the other practical option for coding agents. Claude Code can run shell commands, so a well-built command-line tool over your API, like gh for GitHub, is often enough for developer workflows. It needs no new server, and its help text doubles as documentation. It also needs a shell, which chat applications do not give the model, and every user needs the tool installed and authenticated on their own machine.
MCP is the right layer when the same capability has to work across several hosts, including ones without a shell, with per-user authorization and descriptions written for a model. If you are choosing between MCP and skills rather than these three, the Skills vs MCP comparison covers that decision.
Cost and latency: what MCP adds
MCP adds two costs, and neither is large for a well-designed server.
Context. Tool definitions sit in the model's context window. Every tool's name, description and schema is text the model reads before it does anything useful. A server with a handful of precise tools costs little. A server that exposes every endpoint as a tool can take a meaningful share of the window before the user has asked a question. Hosts are starting to mitigate this; Claude Code, for example, can search for tools and load definitions on demand instead of all at once. Keeping the tool list short remains the most reliable fix.
Latency. Is MCP faster than an API? No. A tool call is your API call plus a hop to the MCP server, and in an agent it also waits for the model to decide which tool to call. That is the right trade for a person asking a question in plain language and the wrong one for a service handling thousands of requests a minute. I have not published timings here, because they depend far more on the model and the API behind the tool than on the protocol.
MCP vs API security
An API exposes operations to code you control. An MCP server exposes them to a model that decides what to call, based partly on text it did not write. That changes the threat model.
- Prompt injection through tool results. A record, email or ticket can contain instructions, and the model may follow them. Treat every tool result as untrusted input, and never let tool output alone authorise a write.
- Tool poisoning and over-broad descriptions. A description tells the model when to use a tool, so a malicious or careless one can steer it. The specification tells hosts to treat descriptions and annotations as untrusted unless they come from a trusted server. Connect only servers you trust, and write your own descriptions narrowly.
- Least privilege. Prefer separate read and write tools over one tool with a mode flag. On HTTP servers, the specification's OAuth flow lets a server ask for the smallest scope an operation needs and request more only when a call requires it.
- Tokens bound to the server. Tokens must be issued for the specific MCP server, and the server must not pass them through to other services. This closes the "confused deputy" route where a server acts with more authority than the user has.
- Consent and audit. The specification requires hosts to get user consent before invoking a tool. Keep a human confirmation step for anything irreversible or customer-facing, and log every call with the user, arguments and result.
Maintenance belongs here too. An MCP server is another service to deploy, monitor and version, and changing a description can change agent behaviour as much as changing code. Test it the same way: keep a small set of realistic requests and check the agent still picks the right tools. It is the same idea as loop engineering, applied to your tool layer.
Will MCP replace APIs?
No. An MCP server normally calls your existing APIs, and your apps, partners and scripts keep using those APIs directly. MCP adds a second front door, designed for models, on top of the same backend. The APIs that matter more in an agent world are the ones with clean, well-scoped operations, because those are the easiest to turn into good tools.
MCP vs API: common questions
Is MCP just an API? In a sense: it is a protocol, built on JSON-RPC, that a client uses to call a server. The difference is the audience. MCP standardises how a model discovers and calls tools, so one server works across many hosts without custom wrappers.
When should I use MCP instead of an API? When the caller is a model rather than your own code, the requests are open-ended, and more than one AI host needs the same capability. For fixed workflows and high-volume integrations, call the API.
Is MCP faster than an API? No. A tool call is still your API call, plus a hop to the MCP server and, in an agent, the model's decision about which tool to use. For a fixed call made from code, the API is always faster. MCP's value is discovery and reuse across hosts, not speed.
Do I need an MCP server to use Claude with my API? No. You can pass tool definitions to Claude directly through function calling, which is often the right place to start. An MCP server becomes worthwhile when several hosts or teams need the same tools.
How do I turn an API into an MCP server? Pick the two or three questions people will actually ask, write one tool for each that calls your API underneath, describe each tool as precisely as you would brief a new colleague, and run it with the official SDK as in the example above. Start read-only. If you are new to extending Claude, how to create Claude Skills covers the lighter-weight option first.
Is MCP secure? It can be, but the security is in your design rather than the protocol: narrow tools, least-privilege scopes, tokens bound to the server, untrusted tool output and a human in the loop for writes.
Where this leaves us
MCP vs API is a question about who is on the other end. When your own code is, the API is enough and simpler. When a model is, deciding at runtime what to do for someone who asked in plain language, an MCP server gives it a way to find your tools, understand them and use them within that person's permissions. For us, the server was not a replacement for the API but a second, carefully designed front door on top of it. The work that mattered most was deciding which few tools deserved to exist, and writing their descriptions as carefully as the code.