← All articles
TechnicalAugust 10, 2026

MCP vs REST: What Two Real Integrations Taught Us

Two developers integrated our clinical coding API in the same week. One shipped, one gave up in three hours. The difference was not the data — it was discoverability.

Two developers integrating a healthcare API from a warm, sunlit office

Two developers started integrating our clinical coding API within days of each other. One is still running. The other stopped after a single afternoon and never came back.

We had the telemetry to watch both happen. What separated them was not the data they wanted, not rate limits, and not pricing. It was whether they had to guess the shape of our API before they could use it.

What we can actually see

We keep hourly counters per credential: request volume, and how many of those requests returned a 4xx. That is enough to watch an integration take shape, and it is deliberately not enough to watch a person. We do not log request bodies or the specific codes anyone looks up.

That granularity matters for what follows. We can see that a developer's error rate rose or fell. We cannot see exactly which status codes they hit or which URLs they tried. Where we draw conclusions below, we are inferring from shape, and we will say so.

Both traces are anonymized and rounded. Neither developer is identified here, and nothing in this post distinguishes them beyond the integration path they chose.

Two traces

The first developer connected through our MCP server. About 1,700 requests over two days. Their first active hour ran an error rate just under 30% — the normal sound of someone discovering what a tool does. By the second hour it was under 10%, and it kept falling. It has been near zero since, and the traffic is still arriving as of this writing.

That is a textbook onboarding curve: a burst of exploratory failure, then a sharp decay as the developer's mental model snaps into place.

The second developer connected directly to the REST API. Roughly 1,200 requests inside a single three-hour session, spread across five different data types — drug classes, units of measure, RxNorm, LOINC, and SNOMED. About one in six requests returned a 4xx.

The error rate did not decay. It rose, ending the session higher than it started. Then the session ended and nothing followed it.

The first curve is someone learning. The second is someone searching, not finding, and stopping.

Our best read on what happened

We went looking at our own REST surface for anything that would punish a developer sweeping five data types in one sitting. We found something.

The path parameter is not named consistently across resources:

GET /v1/loinc/:code
GET /v1/ucum/:code
GET /v1/rxnorm/:rxcui
GET /v1/snomed/:concept_id
GET /v1/rxclass/:classId

Every one of those is a single-resource lookup. Four different parameter names, and a casing change in the last one. Batch endpoints carry a leading underscore — /v1/rxnorm/_batch, not /v1/rxnorm/batch. Some resources expose sub-resources that others do not: SNOMED has /mappings, drug classes have /members, and most have neither.

Each choice is defensible on its own. A LOINC code really is a code; an RxCUI really is an RxCUI. But a developer who has just gotten LOINC working has learned a pattern that does not transfer to the next resource. Doing that five times in one afternoon means five separate rounds of trial and error, and every failed guess costs a request and a little more patience.

We want to be careful about how far we push this. Scope checks also return 4xx, so some share of those errors may have been a key without the right permissions rather than a wrong path. We cannot separate those two cases from the counters we keep, and we are not going to pretend otherwise. What we can say is that our REST surface asks the developer to learn five conventions where one would do, and the failure pattern is consistent with that cost.

Why the MCP path behaved differently

The MCP server exposes the same data through 62 tools, and the naming is uniform by construction:

loinc_get      loinc_search      loinc_batch
rxnorm_get     rxnorm_search     rxnorm_batch
icd10_get      icd10_search      icd10_batch
ndc_get        ndc_search        ndc_batch
npi_get        npi_search        npi_batch

There is no :code versus :rxcui question, because the parameter names arrive with the tool definition. There is no underscore-or-not question about batch endpoints. The agent asks the server what tools exist, gets back names and JSON Schemas for their arguments, and calls them.

This is the part worth sitting with: the MCP developer never had a discovery problem, because discovery is a protocol feature rather than a documentation feature. Their early errors were about semantics — what a tool returns, which argument matters — and semantic confusion resolves quickly because every response teaches you something. Path-guessing does not teach you anything. A 404 tells you that you were wrong and nothing about what would be right.

That is the honest explanation for why one error curve fell and the other climbed.

Where REST is still the right call

We are not going to tell you MCP is the answer to every integration, because our own traffic says otherwise and so does the shape of the problem.

Reach for the REST API when:

  • You are building a conventional backend service. If your code path is a scheduled job enriching a table of drug codes, an HTTP client and a typed SDK are simpler, cheaper, and easier to test than an agent runtime. Our TypeScript SDK exists for exactly this.
  • You need deterministic behavior. Batch endpoints let you send a list of codes and get a list of results, with predictable latency and cost per call. That is a better fit for a pipeline than a model deciding how many tool calls to make.
  • You are integrating into an existing system. If there is already a service layer, adding an HTTP client to it beats introducing a protocol.

Reach for MCP when an LLM is the thing doing the integrating — when the caller benefits from being handed a menu instead of reading a manual.

The two are the same data behind the same authorization model. The choice is about who is holding the other end.

The inconsistency only exists at the raw HTTP layer

Here is the part we did not expect to find when we went looking.

Those four different path parameter names are a property of the wire protocol and nothing else. Every layer we ship on top of it already normalizes them. The MCP tools do it, as shown above. So does the TypeScript SDK:

import { Fhirfly } from "@fhirfly-io/terminology";

const fhirfly = new Fhirfly({ apiKey: process.env.FHIRFLY_API_KEY! });

await fhirfly.loinc.lookup("2160-0");        // was :code
await fhirfly.rxnorm.lookup("161");          // was :rxcui
await fhirfly.snomed.lookup("73211009");     // was :concept_id
await fhirfly.rxclass.lookup("N02B");        // was :classId

Four resources, one method name. Every endpoint in the SDK exposes the same lookup, lookupMany, and search, with resource-specific additions where the data supports them — snomed.mappings(), ucum.convert(), rxclass.members().

The developer who stalled was working at the one layer that still exposes the seams.

Let your editor read the types

This is also where an AI coding assistant earns its keep on the REST path, and for a more specific reason than "AI writes code."

The problem in the failed session was that the endpoint index lived in documentation — a developer had to read it, hold it in their head, and translate it into requests. The SDK moves that index into the type system, where it becomes machine-readable again. Point Claude Code, Cursor, or any editor with language-server access at a project that has @fhirfly-io/terminology installed, and the completions, parameter names, and response shapes are all available without anyone memorizing a URL pattern.

In practice that means you can ask for the outcome rather than the endpoint:

Using the FHIRfly SDK, write a function that takes a list of NDC codes, looks each one up in a single batch call, and returns a map of NDC to generic drug name. Handle the case where a code is not found.

The assistant has ndc.lookupMany() and its argument and return types directly in context. It does not have to guess whether the path is /v1/ndc/_batch or /v1/ndc/batch, because at that layer the question no longer exists.

This is the same principle as MCP, applied one phase earlier. MCP hands a machine-readable menu to the agent at runtime. A typed SDK hands one to the agent at authoring time. Both work because discovery stopped being something a human has to do from memory.

We want to be precise about what this does and does not fix. It is a good answer for teams writing new integration code today. It is not a substitute for a coherent HTTP surface — anyone calling us with curl, from a language we do not ship an SDK for, or through a gateway they do not control still meets the raw paths. Tooling should not be the reason an API stays inconsistent.

What we are changing

The uncomfortable read of this data is that our REST ergonomics did work for the developer who had a machine-readable index of them, and did not work for the developer who had to assemble that index by hand from docs and guesses.

MCP now accounts for roughly 70% of our API traffic, and it would be easy to let that number justify leaving the REST surface as it is. We do not think that follows. The REST API is the foundation — MCP is a layer over the same routes — and a developer's first hour should not be spent learning which of four names we gave the path parameter.

Concretely, we are auditing parameter naming across resources for a future API version, and looking at whether a 404 on a near-miss path can return the correct route in the response body instead of an empty rejection. A 404 that teaches is worth a great deal more than a 404 that is merely correct.

Key Takeaways

  • Discoverability is a failure mode, not a nice-to-have. A developer who cannot find the endpoint looks identical to a developer who does not want your product, right up until they leave.
  • Watch the direction of the error rate, not its level. A high error rate that falls is a developer learning. A moderate error rate that rises is a developer running out of road. The second one is the emergency.
  • MCP's advantage here is structural. Tool definitions make discovery part of the protocol, so the caller never has to guess a path or a parameter name.
  • REST is still the right call for pipelines and existing services. Deterministic batch calls beat an agent loop when you already know what you want.
  • A typed SDK is a machine-readable index too. It gives an AI coding assistant the same thing MCP gives a runtime agent — method names and argument types in context, instead of URL patterns to memorize.
  • Consistency across resources outranks correctness within one. Four defensible parameter names cost more than one imperfect convention used everywhere.

Further Reading

Tagsmcpapi-designdeveloper-experienceagentssdk
Written by The FHIRfly Team — healthcare data, AI, and interoperability folks building better clinical coding APIs.

Build it on real terminology

Try any endpoint live — no sign-up required.

© 2026 FHIRfly.io LLC. All rights reserved. · Terminology data sourced from official registries, updated daily.