Platform integration
Agent infrastructure
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.

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.
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:
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.
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.
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:
get_customer, not customerById..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.
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.
Now repeat step 3 for each endpoint on your "expose" list. A few rules that keep the tool surface usable:
manage_customer that takes an action: "create" | "update" | "delete" argument. Split it.limit and cursor arguments. Don't dump 10,000 rows into a context window.For a 10-endpoint API, this step is usually an hour or two of mechanical work.
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:
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.
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:
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.
A few patterns that catch teams shipping their first MCP server:
Get through these and you've got a working MCP server. Whether you scale it the same way is the next decision.
Stay up to date on the ever changing agentic landscape.