How to Expose an Existing REST API to Claude or ChatGPT via MCP
You already have a REST API. You want Claude or ChatGPT to use it. Here is the cleanest path that does not require rewriting anything.
On this page6 sections
You already have a working REST API. You want Claude or ChatGPT to use it. Here's the cleanest path that doesn't require rewriting anything.
Architecture
You're going to build a thin MCP server that wraps your REST API. The MCP server takes tool calls from the model, translates each into an HTTP request to your existing API, and returns the response.
Claude → MCP server (you build this) → your REST API → your DB
This means:
- Your API stays untouched
- Auth, rate limits, and validation stay where they are
- You decide which subset of endpoints the model gets
Step 1: Pick the endpoints
Don't expose everything. Pick the 3 to 8 operations that are useful in a chat context. A good starter set:
search_X: read-only, returns small resultsget_X: fetch one record by idcreate_X: write, but easy to undosummarize_recent_X: aggregation
Skip anything destructive on the first cut.
Step 2: Write the wrapper
For each endpoint, create one MCP tool. The tool description should explain when to use it, not just what it does:
server.tool(
"search_orders",
"Find recent orders by customer email. Use when the user asks about a specific customer's purchase history. Returns up to 20 orders, newest first.",
z.object({
email: z.string().email(),
limit: z.number().int().min(1).max(20).default(10),
}),
async ({ email, limit }) => {
const r = await fetch(
`${API}/orders?email=${encodeURIComponent(email)}&limit=${limit}`,
{ headers: { Authorization: `Bearer ${process.env.API_TOKEN}` } }
);
const data = await r.json();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
);
Step 3: Handle auth properly
Two options:
- Service token. The MCP server holds a token with limited scope and acts as a single user. Easy. Right for internal use.
- Per-user token. The user passes their token via the MCP client config. Right for products where users have their own data.
Don't pass the model a token. It will leak it into responses.
Step 4: Trim responses for the model
Models pay per token. A 50KB JSON blob is wasteful and often unhelpful. Pick the fields a chat user actually cares about and drop the rest:
const trimmed = data.orders.map((o) => ({
id: o.id,
total: o.total,
status: o.status,
created: o.created_at,
}));
Step 5: Test it before pointing a model at it
Run every tool through PreMan with realistic inputs. Confirm the response is small, well-shaped, and useful as plain text. The cheapest debugging is the kind that doesn't require burning model tokens.
Bring the loop to your API
Catch the regression. Open a verified fix.
Join the waitlist to see which users a release may affect, monitor endpoints in production, and prepare a reviewable fix PR when something breaks.