Designing an MCP server for internal systems: don't just port your REST API over

Auto-generating an MCP server from an OpenAPI spec is the shortest path to an agent that wastes tokens uselessly. REST serves developers, MCP serves LLMs — these two targets require different designs.

Reading progress 0%
Designing an MCP server for internal systems: don't just port your REST API over

Your team has an internal REST API, and the boss wants to "open it up for AI agents to use." Someone found a tool to convert the OpenAPI spec to an MCP server; run one command, and 40 endpoints become 40 tools. The demo works. Two weeks later, the agent starts calling the wrong tools, responding slowly, and the token bill triples. This has happened in many places over the past year because automatic conversion is the shortest path — and also the wrong one.

MCP is now the de facto standard: the SDK reaches about 97 million downloads per month, and the official registry has nearly 9,600 servers. But a high number of servers does not mean most are designed correctly. Most are still just REST APIs in disguise.

REST serves developers, MCP serves LLMs.

REST is designed for coders. Many small endpoints, each doing one thing, cheap to call, developers read the docs once and then write logic to compose them. GET /orders, GET /orders/{id}, GET /customers/{id}, POST /refunds — a normal thing, even a best practice.

MCP is fundamentally different: each tool definition is a token tax. All tool names, descriptions, and parameter JSON schemas are loaded into the context window before the agent does anything. Connecting 10 MCP servers, each with 5 tools, you have burned thousands of tokens just for the agent to "read the menu" — not to mention this amount is returned in every conversation turn.

Worse: LLMs select tools by reading descriptions. 40 similar tools (get_order, list_orders, get_order_items, get_order_status...) makes the model hesitant, choose incorrectly, or call 5 consecutive times to merge data that a developer would merge in a single function. Each call is a round-trip to the model — slow and expensive.

REST API MCP server
User Developer LLM
Cost per endpoint/tool Nearly 0 Tokens in every context
Ideal quantity Many, granular Few, aggregated tasks
Multi-step composition Developer writes code Model self-reasoning (expensive, error-prone)
Returned results Comprehensive, machine-processed Concise, model-readable

Design based on "tasks to complete", do not mirror CRUD

The right question is not "what resources does our system have" but "what task will the agent be asked to perform". For example, in an internal order system, instead of 6 CRUD tools, think:

  • Customer service staff asks: "What is the status of customer X's order, and are there any issues?"
  • Task to perform: lookup order by customer → get shipping status → check related tickets.

That is a tool, not ba:

{
  "name": "investigate_order",
  "description": "Tra cứu toàn cảnh một đơn hàng: trạng thái, lịch sử giao vận, và các ticket hỗ trợ liên quan. Dùng khi cần trả lời câu hỏi về tình trạng đơn của khách. Nhận order_id hoặc email khách hàng.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "order_id hoặc email khách" }
    },
    "required": ["query"]
  }
}

The server handles data aggregation on the backend — calls 3 internal REST endpoints, joins the results, strips redundant fields — then returns a summarized version. The model receives a clean payload instead of orchestrating three calls itself with three JSON responses full of unnecessary fields.

Principles: Keep the joining logic on the server, do not let the model infer it.Model inference is token-expensive and non-deterministic; code is free and 100% consistent.

Keep REST in its proper place.

Do not mistake MCP for a replacement for REST. Moving into 2026, the common production setup is running all three layers in parallel on the same data layer:

  • REST/gRPC for batch jobs, server-to-server, high-throughput — where no LLM is in the loop, there is no reason to pay the token tax.
  • SDK for internal developers building applications.
  • MCP for AI agents — a thin, task-oriented layer that calls the underlying REST API.

Both WorkOS and Composio recommend this hybrid approach. The best MCP server is often just a few hundred lines of code acting as an adapter in front of an existing API — not a new system.

graph LR
    A[AI Agent] -->|MCP: 5-8 tool task-oriented| M[MCP server<br/>adapter mỏng]
    D[Developer app] -->|SDK| R[Internal REST API]
    B[Batch / cron] -->|Direct REST| R
    M --> R
    R --> DB[(Data layer)]

When there are too many tools: code execution with MCP

For large systems, even with good design, dozens of tools may still be required. Anthropic announced the "code execution with MCP" pattern for this situation: instead of loading all tool definitions into the context, expose the MCP server as a code API — the agent writes code (usually TypeScript) to import and call tools like functions, discovering tools as needed instead of reading them all from the start.

Measured results: a reduction of approximately 98.7% in tokens, from 150K down to ~2K for the same task. Block confirmed a similar reduction when applied to Goose at company-wide scale. For internal systems with over ~20 tools, this is a pattern worth considering before thinking about cutting features.

Pragmatic Checklist

Before shipping an internal MCP server, review:

  1. Number of tools: keep under 10 per server. If it exceeds this, or you are mirroring CRUD, or the server covers too many domains — split it.
  2. Tool names should be verb + noun: investigate_order, schedule_maintenance — not get_order_by_id_v2The name itself must suggest when to use it.
  3. Description is written for the model, not for docs. State clearly when to use this tool, what the input looks like, and when not to use it. A sentence like "Returns order data" is useless; the model needs to know how it differs search_orders Where.
  4. Return concise results. Internal REST responses often have 50 fields; the agent only needs 8. Trim them at the server. Every redundant field returned is a token tax multiplied by the number of calls.
  5. Limit output size. A tool returning 2,000 lines of logs will overflow the context — use pagination, summarization, or return a path for another tool to read.
  6. Don't forget security. Tool poisoning (hiding instructions in descriptions) is the most common client-side vulnerability; the 2026 spec includes Enterprise-Managed Authorization and OAuth 2.1 + PKCE — use them instead of hardcoded API keys.

Conclusion.

OpenAPI-to-MCP converters answer the question "how to expose an API to an agent fastest." But the right question is "what does the agent need to get the job done" — and these two questions almost never share the same answer. Your REST API is an asset, keep it; just don't force the LLM to read it like a developer. It is not a developer, and every time it pretends to be one, you pay for the drama.

Done — check your inbox.
Something went wrong. Please try again.