The real question is not "which is better"
Comparing MCP to a direct API integration sounds like a technology argument, and it is almost always an argument about something else: who consumes the interface. If the consumer is code you wrote, which already knows which operation it wants and in what order, you need an API, and no additional layer improves that. If the consumer is a language model deciding at runtime which operation is required, based on a free-text sentence somebody typed, you need a layer that describes the available operations in a form the model can read and choose from. That, and nothing more, is what MCP is.
To make the comparison something more than marketing claims, take one small task and build it twice.
The task we are comparing
A customer writes into the business messaging channel: "what's happening with my order?". The system has to identify the customer by phone number, find their most recent open order in the CRM, pull the status and estimated delivery date, and write a reply. If there is more than one open order, it should ask which one instead of guessing.
The task is deliberately small. It is simple enough that both implementations can be written out in full, and real enough that every interesting difference shows up in it.
Version A — a bespoke REST integration
What actually gets written, in the order you write it:
1. findCustomerByPhone(phone)
GET /api/v2/contacts?phone=... + 404 and duplicate handling
2. listOpenOrders(customerId)
GET /api/v2/orders?contact=... + pagination + status filter
3. formatReply(orders, lang)
a text template, or a model call with the orders as context
4. intent routing: what is this sentence at all? (regex / classifier)
5. auth: refresh token, secret storage, rotation
6. resilience: retry with backoff, timeout, circuit breaker
7. tests: a mock CRM server, regression coverage per field
Seven components, of which exactly two (1 and 2) are "the connection to the system". The other five are the scaffolding around it, and they are the ones rewritten for every new integration.
Version B — an MCP server
1. an MCP server declaring two tools:
find_customer(phone) → the exact same GET
list_open_orders(customer_id) → the exact same GET
2. an input schema per tool, plus the prose description the model reads
3. permissions: read-only; this server exposes no write tool
4. connect the client (Claude / ChatGPT / your own product) to the server
5. intent routing, reply wording and the clarifying question — at the model
Notice first what did not disappear. Both HTTP calls into the CRM are written in both versions, word for word. The 404 handling, the pagination and the CRM's rate limit are handled in both versions. Secret storage and rotation exist in both versions. Anyone selling MCP as "no integration work" is selling something other than what exists.
What actually changed between the two versions
- Routing moved from your code into the model. Item 4 in version A — the logic deciding which function to run — is gone from your codebase. That is the big win, and it is the big risk: it did not disappear, it became non-deterministic.
- The schema became machine-readable documentation. In REST, a function's fields live in code and in a README that goes stale. In MCP they are part of the response the server returns, so they are current by construction — and if they are wrong, the model will be confidently wrong.
- Reply composition was deleted from the code.
formatReplyand all its templates never gets written. In its place there is a token cost on every inbound message. - A component appeared that did not exist before: action approval. In version B somebody has to decide which tools need human confirmation before they run. Version A never raises the question, because the code will never do anything you did not write.
- Latency went up. A direct REST call is one hop. An MCP answer is at least one model call, usually two or three, each with a context that keeps growing.
The maintenance argument — MCP's strongest case
The most substantial argument for MCP is not initial build speed but what happens in month six. In the traditional approach the connector is written for one consumer. When a second consumer appears — a different internal assistant, a developer tool, a bot in the messaging channel — another connector layer gets written against the same system, with the same bugs and the same auth work. Five systems against three consumers is fifteen layers to maintain. With one MCP server per system it is five servers and three connections.
The second effect is on schema change. When the CRM adds a field or changes a status value, version A needs updating everywhere that field is read, and the usual way you discover you missed a place is a customer telling you. In version B the update happens in one server, and the updated tool description reaches clients on their next connection.
The honest side of the same argument: maintenance did not vanish, it consolidated. An MCP server breaks for exactly the reasons a REST integration breaks — the vendor moved an endpoint, a credential expired, a response shape changed. What changed is how many places you fix, not whether you fix.
When a direct API is the right answer — plainly
There are cases where adding MCP is over-engineering, and some where it is simply wrong:
- A deterministic flow that must run identically every time. Issuing an invoice, charging a card, adjusting stock, filing a report with an authority. In these, "the model chose differently this time" is an incident, not flexibility. Write code.
- High volume. Thousands of structurally identical operations a day do not need a model deciding about them. Cost and time only accumulate in one direction.
- User-visible latency. If the answer has to return within a few hundred milliseconds, a single model round trip has already spent your budget.
- No model in the loop at all. A nightly sync, an ETL job, a webhook updating a table — there is nobody there to read a tool description. MCP here is a translation layer with no consumer.
- A single fixed consumer against a simple, stable API. If the connector is forty lines against an interface that has not changed in two years, an MCP server is more code to maintain, not less.
- An audit requirement demanding an identical trace. When you must show a regulator or auditor that every request is handled by the same exact sequence of operations, explicit code demonstrates it. A sequence chosen at runtime by a model makes that harder to demonstrate.
The simple rule: the more predictable, pre-written and repetitive the operation, the more a direct API wins. The more open and human the input, the more MCP wins.
What MCP does not solve
This is the part missing from nearly every comparison, and the part that prevents disappointment:
- It does not create permissions you do not have. MCP adds no permission model to the system underneath it. The token you hand the server is all there is, and if it is an admin token then "least privilege" is something you implement in the server's code — not a property of the protocol. We covered this in the guide to MCP security.
- It does not make the model deterministic. The same question can produce a different tool sequence across two runs. Anyone who needs absolute uniformity needs code, not a better prompt.
- It does not fix bad data. If the CRM status field is hand-maintained and inaccurate, the model will present that inaccuracy in more convincing prose than before.
- It does not remove rate limits and does not improve performance. The constraints of the underlying system remain exactly as they were.
- It does not exist unless the vendor built it. You can build a server yourself over the vendor's API — but then you have written version A and wrapped it. Our connection map records, per system, what exists, the date it was checked, and what the vendor itself says about the status.
- It does not replace testing. A tool with a valid schema can return a completely wrong answer, and the model will not know.
The real decision: almost always both
The common implementation in a real business is one layer over the other, not a choice between them. The same REST call is served twice: once to internal code running fixed processes, and once as an MCP tool for consumers deciding at runtime. The expensive tool — the one that writes, charges or deletes — stays in code, and if it is exposed over MCP it is exposed behind explicit human approval.
A checklist before you decide
- Who calls the interface — my code, or a model deciding at runtime?
- How many different consumers will need this same system within a year?
- What happens if the operation runs twice, or out of expected order?
- What latency ceiling does my user tolerate?
- What is the daily operation volume, and the per-operation cost under each approach?
- Does an official server exist for this system, or am I building that too?
- Which tools must never run without human approval?
Answer those seven and the decision is made. The remaining economic question — what it is worth on the bottom line — is the model laid out in our article on cost savings from automation.
Summary
MCP and a direct API do not compete for the same job. An API is how a system talks to a system. MCP is how a model discovers what it is allowed to do and does it. In the task we built here, both HTTP calls stayed identical; what changed is who decides when to call them, and how many places you fix when the CRM changes. If that decision must be fixed, write code. If it must stay open, describe tools.
