Development Choices

MCP Server vs Direct REST Integration: Which to Build

Author
Joseph TrasattiMember of technical staff
Published
Section
MCP
Length
13 min read3 sources cited

A direct REST integration is code you wrote: deterministic, testable, and reviewable, so uniform bulk work belongs there. An MCP server is a capability surface for a model, worth its per-item token cost only where the right call depends on what each item turns out to be. Build the REST path first; add MCP over it.

What each one actually is

The two options are not two ways of reaching the same API. They are two different places to put the decision about what to call.

A direct REST integration is code you wrote against a vendor’s HTTP surface. For a media backend that means, concretely, a client that signs and sends requests to endpoints like Cloudinary’s Upload API for ingest and its Admin API for listing, tagging, moving and deleting what is already there. Every parameter is chosen at authoring time. The code is deterministic: given the same input and the same remote state, it issues the same requests in the same order, and it does exactly the same thing on every run. It is testable in the ordinary sense — you can mock the transport, assert on the request body, replay a recorded response — and it is reviewable in a pull request, because the whole decision procedure is on the screen.

An MCP server is a capability surface for a model. It exposes tools — list assets, upload, set metadata, run an analysis — with schemas that describe what each one accepts, and a model connected to it decides at run time which tool to call, with what arguments, based on the conversation and the data it has seen so far. Cloudinary ships five of them — Asset Management, Environment Config, Structured Metadata, Analysis and MediaFlows — with the remote ones authenticating over OAuth and the local ones needing credentials supplied by hand, per its MCP server documentation. The important thing about that surface is that its value is the cases you did not enumerate. If you can write down in advance every branch the integration will take, you did not need a model in the loop. The MCP server earns its place in the cases where the right call depends on what the content turns out to be: this image needs a crop and that one needs a moderation flag and the third is a duplicate that should be deleted, and nobody wrote an if for any of that.

So the comparison is really: where does judgement live? In REST it lives in the developer’s head at authoring time and is frozen into code. In MCP it lives in the model at run time, exercised once per item.

The rest of this page weighs the two on the criteria that actually separate them — cost, failure modes, auditability — and then says which to pick when.

Cost per item scales differently

A REST call costs an API call. Whatever the vendor meters — a request against a rate limit, a transformation, bandwidth — that is the whole bill for that item, and it is the same for item one and item ten thousand.

An MCP tool call costs the same API call plus the tokens spent deciding to make it. On every item. The model has to read the tool schemas that are in context, read whatever the previous tool returned, reason about which tool fits, and emit the call. Then it reads the result. If the result is large — an asset listing, a metadata dump — the reading alone can be the dominant cost, and at the extreme a result can be large enough to push everything else out of the window, which is its own failure covered under MCP tool results large enough to overflow the context window.

The consequence is not that MCP is expensive in absolute terms; it is that the two cost curves have different slopes. REST is roughly linear in the number of items with a small constant. MCP is linear with a much larger constant, and the constant grows with how many tools are enabled and how chatty their results are — the context cost of leaving several MCP servers connected at once is paid before the first useful call. For a hundred items the difference is a rounding error. For a hundred thousand identical items — re-tag every asset, regenerate every derived version, migrate a folder — you are paying a model to re-derive the same decision each time, and the decision was never in doubt.

The cost is justified exactly when the per-item reasoning is doing work: when items genuinely differ and a fixed rule would get some of them wrong. If the reasoning would produce the same answer every time, it is pure overhead.

Failure modes differ in kind, not degree

This is the criterion people most often get backwards, because “the model adapts” sounds like an unqualified advantage.

REST fails loudly on a contract change. If the vendor renames a parameter, changes a response shape or retires an endpoint, your code either fails to compile against the updated types, fails a test, or throws at run time with a specific error. That is annoying on the day it happens and extremely valuable every other day, because the failure is attributed: something changed, here is the line, here is the diff that fixes it. Nothing keeps running in a subtly wrong state.

A model adapts to a contract change. If a tool’s schema shifts, or a response starts coming back in a different shape, the model reads the new schema or the new response and does something reasonable with it. Sometimes that is recovery — the job finishes that would otherwise have crashed. Sometimes it is a silent behaviour change nobody reviewed: the model now calls a different tool, or passes a different argument, or interprets a renamed field as meaning something it does not, and the integration keeps producing output that looks fine at a glance and is wrong in a way no test was written to catch, because there was no code path to write a test against.

Both outcomes come from the same property. You cannot have a model that recovers gracefully from surprises without also having one that quietly changes course when surprised. The question is which failure you would rather have on a Tuesday afternoon: a page that says “broken, here is where”, or a run that completes and a discovery three weeks later that the tags have been subtly wrong since the vendor’s last release. For hosted MCP servers, where the vendor can change the tool surface underneath you, this is worth planning for explicitly — see Surviving version changes in a hosted MCP server.

The REST failure is worse in the moment and better in aggregate. The MCP failure is better in the moment and worse in aggregate, unless the workload is one where a plausible-but-different action is genuinely acceptable — which, for exploratory or one-off work, it often is.

Auditability favours REST, and not by a little

A code path can be read. Before it runs, a reviewer can look at the integration and say what it will do to every item, and that reading is exhaustive: the code has no other behaviour. After it runs, the log matches what the code says, and if it does not, that is a bug with a location.

A model’s tool selection can only be logged after the fact. You can record every call the model made, with arguments and results, and you should. But that log tells you what happened on this run with these inputs. It does not tell you what would happen with different inputs, and it does not let anyone approve the behaviour in advance, because there is no fixed behaviour to approve. Review of an MCP-driven integration is review of the tool surface — which tools are exposed, what each is allowed to do — plus review of the transcript afterwards. That is meaningfully weaker than reading a function, and it is why the useful discipline on the MCP side is to narrow the surface rather than to try to read the model’s mind: restricting which tools an MCP server exposes to a client is the closest thing to a code review you can do up front. If a destructive tool is not exposed, the model cannot select it, and that is a property you can state in advance.

Where auditability matters — anything a compliance function will ask about, anything that touches deletion, anything that changes what customers see — this criterion alone can settle the choice. Where it does not — a developer asking an assistant to find the assets uploaded last week that have no alt text — nobody needs the transcript.

The honest split: volume and uniformity

Put the three criteria together and the line falls in one place. Uniform bulk work belongs in code. Judgement-per-item belongs behind a model.

Uniform bulk work is anything where the same rule applies to every item and the number of items is large: apply a tag to a folder, regenerate a derived format across a library, delete everything matching a query, migrate metadata from one field to another. Here REST wins on every criterion. It costs one API call per item with no reasoning tax, it fails loudly if the vendor changes something, and a reviewer can read the loop and know what it will do to all ten thousand items before it touches any of them. Putting a model in that loop buys nothing and pays for it on every iteration. Whether the code side is a hand-written REST client or the vendor’s own command-line tool is a further, narrower choice — a vendor CLI against an MCP server for bulk asset operations works through it.

Judgement-per-item is anything where the right action depends on the content: this image should be cropped to the subject, that one is a near-duplicate of an existing asset and should be linked rather than stored, this batch of user uploads needs a moderation decision that a fixed threshold gets wrong. Here the MCP server wins, because the value is precisely the branches nobody enumerated. The reasoning cost is not overhead; it is the work. And the after-the-fact log is acceptable because there was never a fixed rule to review.

Most real integrations contain both. A typical media pipeline is a large uniform core — ingest, store, transform, deliver — with a thin layer of per-item decisions on top: what to do with the ambiguous ones, how to handle the case the rules did not anticipate, what a human would have done if they had looked. The mistake in either direction is treating the whole integration as one kind of work. Routing everything through a model because the interesting five percent needs it means paying reasoning cost on the boring ninety-five. Hard-coding everything because the boring ninety-five is easy means the interesting five percent gets a rule that is wrong for it.

Build order: REST first, MCP over it

If you accept that most integrations contain both, the sequencing question is which to build first, and here the answer is not symmetric.

Building the REST path first and adding MCP over it costs less than the reverse, because the tools then wrap an interface that already has tests. Your REST client encodes the vendor’s contract, and its test suite pins that contract. When you later expose some of those operations as MCP tools — either by pointing the model at the vendor’s hosted servers for the judgement layer, or by wrapping your own client — the tools sit on top of behaviour that is already verified. A tool that calls uploadAsset(...) inherits every assertion you wrote about uploadAsset. The model can choose whether and when to call it; it cannot make the call do something the tests do not cover.

The reverse order is more expensive because it inverts that. If the first thing you build is a model driving an MCP server, the operations exist only as the model’s usage of them: there is no client, no fixed call sequence, nothing to write a unit test against. When you later need the bulk path — and you will, the moment someone asks for the same operation across ten thousand items — you end up writing the REST client anyway, and now you have to reconstruct what the model had been doing from transcripts in order to make the code match. The judgement layer did not give you a foundation; it gave you a record.

There is a smaller reason too. Debugging a REST call is ordinary: request, response, status code, done. Debugging an MCP tool call that fails or returns the wrong thing means separating whether the tool was wrong, the arguments were wrong, or the model’s choice was wrong, and having a working REST client under it removes the first of those from suspicion — diagnosing an MCP tool call that fails or returns the wrong thing goes through that in order. Rate limits are the same story: the vendor’s headers tell you what is happening on the REST side directly, and getting that same information back through a tool result takes deliberate work, covered under surfacing rate-limit and request-ID headers in MCP tool results.

None of this means the MCP layer is an afterthought. It means the MCP layer is a thin, well-defined thing when it arrives, because everything under it already behaves.

Where the criteria do not separate them

Two things people expect to be differentiators are not, and it is worth being explicit so they do not distort the decision.

Access. For Cloudinary specifically, MCP server access is included on every plan, Free included, per its pricing page as checked on 2026-08-18. So the choice is not gated by tier; a team on the free plan can reach both surfaces. What is gated is the underlying API: the Free plan allows 500 Admin API requests per hour, and an MCP tool that lists or edits assets is consuming that same budget, so a chatty model against a small quota exhausts it faster than a tight loop would.

Capability. The MCP tools do not, in general, do anything the REST API cannot; they wrap it. Choosing MCP does not unlock an operation. It changes who decides to perform it and when.

Which to pick when

Pick a direct REST integration if the work is uniform across items and the item count is large; if a reviewer needs to know in advance exactly what will happen to every asset; if the operation is destructive or customer-visible and a silent behaviour change would be unacceptable; or if you are building the first version of anything, because it becomes the tested foundation for everything after.

Pick an MCP server if the right action genuinely depends on what each item turns out to be and you cannot write the rule down; if the volume is small enough that the per-item reasoning cost is not the dominant line; if an after-the-fact log is sufficient review; or if the user is a person working interactively — exploring a library, cleaning up a folder, asking questions of the data — where the whole point is that the next step is not known yet.

Pick both, in that order, if the integration is a real one that will live for more than a quarter. Build the REST path, test it, run the bulk work through it. Then put an MCP surface over it for the per-item judgement, and expose only the tools that layer needs. The model gets the cases you did not enumerate; the code gets everything else; and when the vendor changes something, the tests fail loudly on the side that can be read, which is the side you want to find out from.

Sources

  1. Upload APIcloudinary.com
  2. Admin APIcloudinary.com
  3. MCP server documentationcloudinary.com

See also