Blog

Importing Your API Into MCP Is Usually the Wrong Move

Jerome Louvel ·September 7, 2026
Table of contents

There is a command for it now. Point a generator at an OpenAPI document, get an MCP server, connect an agent. Stainless, Speakeasy, APIMatic and Mintlify all ship it. It takes under a minute and it demos beautifully.

It is also, most of the time, the wrong artifact — because a projection that faithfully mirrors an API inherits the API’s size and shape. The document is the right input. The mistake is treating it as the output.

Size is the complaint everyone makes

A 200-operation API becomes 200 tools, all loaded before the model reads a word of the request. Anthropic measured it: “agents connected to thousands of tools […] need to process hundreds of thousands of tokens before reading a request.” And definitions are re-sent every loop, so an oversized catalog compounds quadratically with the number of turns.

Cloudflare put the consequence plainly: “MCP server designers are encouraged to present greatly simplified APIs as compared to the more traditional API they might expose to developers.” The recommendation is to simplify; the importer’s whole value proposition is that it doesn’t.

Shape is the one that breaks deployments

Cut the catalog to twenty tools and size is manageable. Shape is untouched, because it was never about the count.

An OpenAPI document describes resources. An agent needs jobs. Anthropic states the target almost as a recipe: “Instead of implementing a list_users, list_events, and create_event tools, consider implementing a schedule_event tool which finds availability and schedules an event.”

schedule_event is not a transformation of three endpoints. It is a decision — that these three, in this order, with this conflict rule, are one unit of work worth exposing. That lives in your product knowledge, not in the document.

“But we only import the twelve we need”

Nobody defends importing all 200. The mature version is selective import: hand-pick the dozen operations the agent plausibly needs, and the context bill drops by an order of magnitude. It is a real improvement, and it leaves the problem untouched:

Selection is a subtraction operator. The tools an agent needs are not a subset of your endpoints.

You cannot select your way to get_customer_context, because it is not in the list. It is not endpoint #7 you forgot to tick — it is three endpoints and the decision to fuse them.

So the unit is still a resource, not a job: the agent still plans the sequence, holds intermediate IDs and joins results, and fewer tools does not mean fewer turns. Payloads are untouched, because you selected which endpoints to expose, not what they return. Cross-system jobs stay impossible, because selection operates inside one document while the job spans three. And the selection has no home — it lives as a CLI flag, so when upstream renames an operation nothing tells you, and the valuable part, why these twelve, is discarded.

Choosing which upstream operations to bring in is a decision about consumes — what you may reach. The agent-facing surface is exposes, and selection has no opinion about it.

“Then we’ll annotate the document”

If the document lacks agent metadata, add it. OpenAPI sanctions this — specification extensions, any x- field — so teams attach a better description, a readOnly hint, a friendlier name, and generate from that.

For metadata this genuinely works. But an extension hangs off an operation. It cannot say “these three, in this order, returning only these five fields” — there is no operation to hang that on. It is a statement about a relationship between operations, and the document has no place to put one.

The OpenAPI Initiative reached that conclusion itself. Rather than extend OpenAPI to express call sequences, it published Arazzo — a separate specification for defining “sequences of calls and their dependencies… in the context of delivering a particular outcome.” The people who own the extension mechanism looked at composition and concluded it needed its own document.

The composition an agent needs is not annotation-shaped. It is a second artifact — and once you are writing one, the only question is whether it also carries the credential scope, the response shaping and the exposed surface, or whether you maintain those in three more places.

The escape hatches

Dynamic tool discovery helps the token bill, but selection still happens at runtime inside a probabilistic model, and search needs a query — so the agent must already know your vocabulary. Retrieval doesn’t remove curation, it relocates it.

Code execution makes an oversized, badly shaped surface cheaper to traverse; it does not make it a good surface. The joins still happen in model-authored code, written fresh each run. Ask which systems can this reach? and the answer is still “whatever it writes next time.”

What a designed surface looks like

Scope the surface to the agent’s task, not the product — the argument made in full in Applied Capabilities.

An Ikanos capability is that surface written down as one declarative file: consumes (which systems, under which credentials), aggregates (how calls compose into one unit of work, off the model’s critical path), exposes (the agent-facing surface, as MCP, REST or Agent Skill), and binds (credentials, separated from logic).

aggregates is the section a generator cannot write, and it is the entire point. Here is the get_customer_context that no importer could produce:

capability:
  consumes:
    - import: crm
      from: ./shared/crm.yml        # generated by `ikanos import openapi`
    - import: billing
      from: ./shared/billing.yml    # a different document entirely

  aggregates:
  - display: "Support Desk"
    namespace: support
    flows:
      get-customer-context:
        description: "Everything an agent needs before a billing question"
        semantics:
          safe: true
          idempotent: true
        inputParameters:
          customer-id:
            type: string
            required: true
        steps:                            # three upstream calls, one tool call
          get-customer:
            type: call
            call: crm.get-customer
            with:
              id: "{{customer-id}}"
          list-invoices:
            type: call
            call: billing.list-invoices
            with:
              customer: "{{customer-id}}"
          list-tickets:
            type: call
            call: crm.list-open-tickets
            with:
              customer: "{{customer-id}}"
        mappings:                         # five fields, not sixty
        - target: plan
          value: "$.get-customer.subscription.plan_code"
        - target: unpaid-invoices
          value: "$.list-invoices.data[?(@.status=='open')]"
        - target: open-tickets
          value: "$.list-tickets.data.length"

  exposes:
  - type: mcp
    port: 3001
    namespace: support-copilot
    tools:
      get-customer-context:
        description: "Fetch a customer's plan, unpaid invoices and open tickets"
        ref: support.get-customer-context

Three endpoints across two OpenAPI documents become one tool call. There is no control flow in the file, which is what makes it reviewable: a reader can enumerate every operation it can reach before anything runs.

What Ikanos’s importer will not do

Ikanos ships ikanos import openapi. Its own description is “Import an OpenAPI specification into an Ikanos consumes YAML file.” It writes consumes — the upstream half. It does not emit tools, it does not emit exposes, and it will not hand you an MCP server.

That is not a missing feature; it is the position. Import saves you from re-typing a contract that already exists, then stops exactly where judgement begins.

The rule

The document is the input. The capability is the output. Import is where the work starts, not where it finishes — and every minute it appears to save is repaid in tokens, retries, and the audit conversation you cannot have because the surface only exists at runtime.

Further reading

Subscribe to the Naftiko Manifest

Want to learn more and stay up to date on the latest from Naftiko? Get new posts, releases, and signals from the capability fleet in your inbox.