Platform integration

Agent infrastructure

How to turn an API into an MCP server: a step-by-step guide

How to turn an API into an MCP server: a 7-step guide covering OpenAPI mapping, tool design, auth, testing with MCP Inspector, and when to stop hand-writing.

7 minute read
Decorative imagery showcasing Pontil's brand

By the end of this guide, you'll have a working MCP server that wraps an existing REST API and exposes it to agents like Claude or any other Model Context Protocol (MCP) client. We'll start from an OpenAPI spec, scaffold the server, define tools that map to endpoints, handle auth, and test the result with MCP Inspector.

Prerequisites: Node.js 18+ (or Python 3.10+ if you prefer), an existing REST API with an OpenAPI 3.x spec, and credentials to call that API. Time required: about 45 minutes for a small API (5–10 endpoints). Longer if your spec is messy or auth is unusual.

This guide assumes you've read up on what MCP actually is. If not, start with what is an MCP server and come back.

Step 1 — Audit the API surface you want to expose

Before you write code, decide which endpoints belong in the MCP server. Not every route should be a tool. Agents work better with a small set of high-signal tools than a dump of every CRUD operation.

Open your OpenAPI spec and mark each endpoint as:

  • Expose: the action is meaningful on its own and an agent would plausibly call it.
  • Compose: useful, but only as part of a larger workflow — wrap it inside a higher-level tool later.
  • Skip: internal, deprecated, or only makes sense in a UI flow.

A good first MCP server covers 5–15 tools. If you're looking at 50+ endpoints and want to expose them all, you're past the point where hand-writing a server makes sense — see step 7.

Step 2 — Scaffold the MCP server

The official MCP SDKs cover many languages now, including TypeScript, Python, Java, Kotlin, C#, Go, Ruby, Rust, and Swift. For most API-wrapping work, TypeScript and Python are the most mature — pick the one that matches your API's existing codebase so you can reuse types and HTTP clients.

For TypeScript:

mkdir my-api-mcp && cd my-api-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init

Create src/index.ts with a minimal server:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "my-api-mcp",
  version: "0.1.0",
});

const transport = new StdioServerTransport();
await server.connect(transport);

Run it with npx tsx src/index.ts. It won't do anything yet — no tools are registered — but the process should stay alive and speak JSON-RPC over stdio.

Step 3 — Define your first tool from an OpenAPI operation

Pick one endpoint from your "expose" list. Translate it into an MCP tool. The translation is mechanical but matters: the tool name, description, and input schema are what the agent sees.

Say your API has GET /customers/{id}. Register it like this:

import { z } from "zod";

server.tool(
  "get_customer",
  "Fetch a single customer record by ID. Returns the customer's profile, contact details, and subscription status.",
  {
    customer_id: z.string().describe("The unique customer identifier (UUID)."),
  },
  async ({ customer_id }) => {
    const res = await fetch(`${API_BASE}/customers/${customer_id}`, {
      headers: { Authorization: `Bearer ${getToken()}` },
    });
    const data = await res.json();
    return {
      content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
    };
  }
);

(The current TypeScript SDK also exposes server.registerTool(name, { description, inputSchema }, handler) — both work; the config-object form is the newer recommended style.)

Three things to get right here:

  1. Name uses snake_case and reads as a verb_object. get_customer, not customerById.
  2. Description is for the agent, not a human dev. Say what it does and what it returns. Skip implementation detail.
  3. Input schema uses .describe() on every field. The agent picks arguments by reading these.

If your OpenAPI spec has good summary and description fields, copy from there. If it doesn't, this is where you pay for thin docs — agents will guess wrong.

Step 4 — Handle authentication properly

Most REST APIs need a token. The wrong way is hard-coding it. The right way depends on who the agent is acting as.

Two patterns:

Service-account auth (the agent acts as a single shared identity): read a token from an environment variable at startup. Acceptable for internal demos, dangerous in production — every action is attributed to the service account, and you lose per-user permissions and audit trails.

User-scoped auth (the agent acts as the authenticated end user): the MCP client passes a user token through, and your server uses that token when calling the API. This is what you want for anything customer-facing.

For the user-scoped pattern, take the token from the MCP request context rather than the environment. The exact mechanism depends on transport — over stdio you'll typically pass tokens through environment variables set by a launcher; over the Streamable HTTP transport (which replaces the older HTTP+SSE transport) you'll read them from the request.

If your API uses OAuth and tokens expire mid-session, the server needs refresh logic too. Don't skip this — silent token expiry is one of the top reasons MCP servers "work in demo, break in production." The build vs buy economics of doing this well across many APIs are worth thinking about early — we go deeper in our build vs buy integrations guide.

Step 5 — Map the rest of your endpoints

Now repeat step 3 for each endpoint on your "expose" list. A few rules that keep the tool surface usable:

  • One operation per tool. Don't build manage_customer that takes an action: "create" | "update" | "delete" argument. Split it.
  • Return structured text, not raw HTTP. Strip status codes and headers the agent doesn't need. Include error messages in plain language.
  • Paginate explicitly. If an endpoint returns a list, expose limit and cursor arguments. Don't dump 10,000 rows into a context window.
  • Compose where it helps. If three tools always get called in sequence, consider a fourth tool that wraps the sequence with a clearer name.

For a 10-endpoint API, this step is usually an hour or two of mechanical work.

Step 6 — Test with MCP Inspector

Don't connect your server to Claude or any other client before you've tested it directly. MCP Inspector is the official debugging tool — it shows you the JSON-RPC traffic, lists registered tools, and lets you invoke them by hand.

npx @modelcontextprotocol/inspector npx tsx src/index.ts

It opens a browser UI. You should see:

  • Your server name and version under "Server info".
  • All your tools listed with their descriptions and input schemas.
  • A form for each tool to invoke it with test arguments.

Work through each tool: invoke it with valid input, then with invalid input. Confirm the agent-facing error messages are useful. If something looks off in Inspector, it'll look ten times worse to a real agent — fix it here. We've got a full walkthrough in our MCP Inspector guide.

Step 7 — Decide whether to keep hand-writing this

For a small, stable API owned by one team, the steps above scale fine. Maintenance is a few hours when the API changes.

For a large API, a portfolio of products, or a B2B SaaS company exposing many surfaces, hand-writing doesn't compound. Every endpoint change in the upstream API means a manual update to the MCP server. Multiply by every product and you're funding a small integration team forever.

Three honest paths from here:

  1. Keep hand-writing. Fine for narrow scope. Budget the maintenance — connector maintenance cost is real and we've mapped it.
  2. Generate from OpenAPI. Several tools (open source and commercial) generate MCP server skeletons from a spec. They get you to step 5 faster. They don't solve maintenance — you regenerate and re-test on every spec change.
  3. Move to a generation-and-runtime platform. The category here is the tools layer of the agent stack, distinct from MCP gateways and embedded iPaaS. Where each fits is worth reading before you commit to a path.

The right answer depends on how many APIs you're exposing, how often they change, and whether per-user auth and observability are hard requirements.

Common pitfalls

A few patterns that catch teams shipping their first MCP server:

  • Exposing every endpoint. Agents pick worse tools when given more tools. Curate.
  • Vague descriptions. "Returns customer data" doesn't tell the agent when to use it. Say what data, when, and what it doesn't include.
  • Returning raw API errors. A 422 with a nested validation object is noise. Catch errors, translate to plain text, suggest a fix.
  • Sharing service-account tokens. Works in demo, fails audit. Plan for user-scoped auth from day one.
  • No observability. Once an agent is invoking your tools in production, you need logs of which tool was called, with what arguments, by which user, and what came back. Stdio servers make this harder than HTTP servers — choose transport accordingly.

Get through these and you've got a working MCP server. Whether you scale it the same way is the next decision.

Join our weekly newsletter

Stay up to date on the ever changing agentic landscape.

POSTS

Related content

Agent infrastructure

Platform integration

What is an MCP server? A deep-dive for engineering teams

9 minute read

Agent infrastructure

Platform integration

How to use MCP Inspector to debug your MCP server

6 minute read

Agent infrastructure

Platform integration

MCP servers: a practical setup and architecture guide

8 minute read