What Actually Happens When You Wrap a REST API as MCP Tools

A look under the hood at API-to-MCP wrapping — how an OpenAPI operation becomes a tool the model can call, and what happens on the round trip when it does.

"Automatic tool discovery" is one of those phrases that sounds like magic and is actually just a translation problem plus some careful plumbing. If you've wrapped an API and wondered what the platform is really doing between "here's my spec" and "the model called my endpoint," this is the post that opens the box.

I'll trace it in two halves: build time, where your API's endpoints become tool definitions, and call time, where a model's tool call becomes a real HTTP request and a response comes back. Neither half is mysterious once you see the moving parts — and seeing them makes you better at getting good results, because you learn which inputs actually matter.

If you want the hands-on version first, the no-code walkthrough wraps a real API end to end. This post is the why-behind-the-what.

Build time: from endpoint to tool

When you wrap a provider, the platform walks the OpenAPI spec's paths and turns each operation into an MCP tool definition. It's a structured mapping, field by field:

  • operationId becomes the tool name. This is the identifier the model uses to invoke the tool, so a clear operationId (getPetById, not op_7) is doing real work. If an operation lacks one, a name gets derived from the method and path, but you're better off giving it a good one.
  • The summary/description becomes the tool's description. This is the single most important field, and it's worth dwelling on: it's the only thing the model reads to decide whether the tool is relevant to what the user asked. A vague description means the model can't tell your tool applies, so it never calls it. This is where thin specs quietly fail.
  • Parameters become the input schema. Path params, query params, headers, and the request body all get folded into one JSON Schema inputSchema — the typed object the model has to fill in to make a call. Types, required-ness, and per-parameter descriptions all carry over, because the model uses them to construct a valid call.

The result is a list of tools, each of which is essentially "here's a thing you can do, here's what it's for, here's what you need to give me." That list is what an MCP client fetches when it connects, and it's what the model reasons over.

The takeaway from build time: your spec is the source of truth, and its descriptions are the interface to the model. The single highest-leverage improvement you can make before wrapping isn't technical — it's writing better summaries and parameter descriptions. The model is only as good at using your API as your spec is at explaining it.

Call time: from tool call to HTTP request

Now the interesting half. The model has the tool list, the user asks something, and the model decides to call getPetById with { "petId": 1 }. Here's the round trip that produces an answer.

1. The call arrives at the server. MCP is a JSON-RPC protocol, so the client sends a structured tools/call request naming the tool and its arguments. The server's job is to turn that abstract call into a concrete HTTP request to your API.

2. Arguments map back onto the request. This is build time in reverse. The petId argument goes into the path (/pet/1), a query argument would go on the query string, a body argument becomes the JSON payload. The mapping the platform built when it read the spec is what tells it where each argument belongs on the wire.

3. Credentials get injected — server-side. The upstream credential you configured is attached here, at the last moment, on the server. The model never touched it; it produced arguments, and the server adds the auth. This is the keep-keys-away-from-the-model principle in action, and it's why the tool call in step 1 contained no secret. If your API uses a bearer token, it's added as a header; an API key goes wherever you told it to; OAuth tokens are fetched and refreshed as needed — the three upstream auth types all resolve here.

4. The outbound request passes a safety check. Before the request actually goes out, the destination is validated. The platform resolves the target host and refuses to connect to loopback, private, link-local, or reserved address ranges — the cloud metadata endpoint, internal services, that whole class of targets. Crucially the check happens at connection time against the resolved IP, which is what defeats DNS rebinding (a hostname that resolves public on the first look and private on the second). You don't see this step when it works, which is the point; it's a guard rail that only announces itself by stopping something bad.

5. Rate limiting and caching do their jobs. An AI agent can call a tool in a loop, so there's a throttle in front of your upstream — a token-bucket limiter that enforces per-tier request rates and smooths bursts, so a model that retries an expensive call five times doesn't turn into five hammer blows on your API. And if the same call was made recently, a response cache can return the prior result instead of hitting the upstream again. Both exist to protect the API you're wrapping from the enthusiasm of an automated caller.

6. The response comes back to the model. Your API responds, the server hands the result back through the same JSON-RPC channel, and the model folds it into its answer. From the model's side, the entire round trip was: "I called a tool, I got a result." Everything in steps 2 through 5 was invisible to it — which is exactly the separation you want.

Why the invisible parts matter

It would be easy to think of API-to-MCP wrapping as just "expose the endpoints." But most of what makes it safe and durable lives in the parts the model never sees: credentials injected at the edge instead of handed to the model, a request that's checked before it's allowed out, a throttle that keeps an eager agent from overwhelming your upstream, a cache that keeps repeated calls cheap. Strip those out and you have a demo. Keep them and you have something you can point production traffic at.

That's also the honest argument for not hand-rolling all of this yourself. The build-time translation is genuinely doable — read a spec, emit tool definitions. It's the call-time plumbing, and getting every one of those steps right on every request, that turns into ongoing infrastructure work rather than a weekend project.

The mental model

If you remember the shape, you'll reason about the whole system correctly:

  • Build time is a translation from your spec's operations to MCP tool definitions, and your descriptions are the interface to the model.
  • Call time is a reverse translation — arguments back onto an HTTP request — wrapped in a chain of protections (auth injection, SSRF check, throttle, cache) that the model never sees.

Improve the spec and you improve how the model chooses and calls your tools. Trust the plumbing and you don't have to think about the round trip. If you want to watch it happen against your own API, start free and wrap one — then ask an AI client to use it and picture the six steps running underneath.