How I Turned a REST API into an MCP Server
🛠️ How I Turned a REST API into an MCP Server (And Why You Should Too)
🍔 Introduction: Turning a Fast Food Joint into Fine Dining
Let’s say you walk into a fast food restaurant. You shout, “One burger!” and they toss it to you in under 30 seconds. Quick, dirty, and done. That’s your typical REST API: fast, unstructured, and a little unpredictable.
But what if you wanted to open a high-end place — where every dish has a printed menu, the ingredients are verified, and the chef follows a consistent recipe? That’s what Modular Contract Protocols (MCPs) are like. They don’t just serve data; they serve it with structure, clarity, and a smile.
In this post, I’ll show you exactly how I wrapped the public Hacker News API in an MCP — turning a chaotic data stream into a clean, LLM-friendly microservice.
🧠 Wait... What’s an MCP Again?
Let me break it down:
MCP stands for Modular Contract Protocol. It’s a way of writing API logic where every endpoint:
- Has a clear input and output contract
- Is modular and self-contained
- Can be called by humans, machines, or LLMs
- Plays nicely in a function-calling world (like OpenAI or Claude)
In simple terms: it's like giving every API endpoint its own resume. It knows who it is, what it accepts, and what it gives back.
🔥 Why Wrap an Existing API?
I wanted to showcase how powerful MCPs are by not building everything from scratch. Instead, I took the Hacker News API — a basic REST interface with zero documentation, no validation, and no structure — and made it:
- 🌱 Type-safe with Zod
- 🤖 Callable by LLM agents
- 📜 OpenAPI-exportable via
zod-openapi - 🌍 Deployable & reusable
You get all the benefits of modern, clean backend architecture — without rebuilding the wheel.
🚧 Step 1: Picking the API
I went with Hacker News API. Why?
- It’s public and requires no authentication
- It returns JSON (yay!)
- It’s a little raw — making it perfect for a glow-up
Hacker News gives you endpoints like:
/topstories.json→ returns an array of story IDs/item/{id}.json→ returns the details for a story or comment
But there’s no validation. No input schema. No docs. Just vibes.
🧱 Step 2: MCP Project Structure
I set up the project with this simple folder structure:
src/
├── contracts/ // Zod + OpenAPI metadata
├── resolvers/ // Business logic
├── handlers/ // Express routes
├── utils/ // Hacker News client
├── docs/openapi.ts // OpenAPI spec generator (zod-openapi)
├── setup/zod-openapi-init.ts // Shared zod setup with OpenAPI support
└── server.ts // Main entry point Think of it like building with LEGO blocks — every piece does one thing, and snaps into place without duct tape.
🧪 Step 3: Creating the First Contract
Let’s start with the endpoint to list top stories.
✅ Zod Contract
export const listTopStoriesOutput = z.array(z.number()).openapi({
description: "Array of Hacker News story IDs",
}); It’s like saying: “Hey, this endpoint gives back an array of numbers. No more, no less.”
⚙️ Step 4: Writing the Resolver
The resolver is the actual brain. It connects to Hacker News, fetches the data, and validates it.
import { fetchTopStoryIds } from "../utils/hnClient";
import { listTopStoriesOutput } from "../contracts/listTopStories.contract";
export const listTopStoriesHandler = async (req, res) => {
try {
const storyIds = await fetchTopStoryIds();
res.json(listTopStoriesOutput.parse(storyIds));
} catch (e) {
res.status(500).json({ error: "Something went wrong!" });
}
}; 📖 Step 5: Adding the getStory Endpoint
This one lets you fetch details about any story by ID.
✅ Input Contract
export const getStoryInput = z
.object({
id: z
.string()
.regex(/^[0-9]+$/)
.openapi({ description: "Story ID" }),
})
.openapi({ title: "GetStoryInput" }); ✅ Output Contract
export const getStoryOutput = z
.object({
id: z.number().openapi({ description: "ID of the story" }),
title: z.string().openapi({ description: "Title of the story" }),
by: z.string().openapi({ description: "Author" }),
score: z.number().openapi({ description: "Score or points of the story" }),
url: z.string().optional().openapi({ description: "URL (if any)" }),
time: z.number().openapi({ description: "Unix timestamp" }),
type: z.string().openapi({ description: "Item type (story/comment)" }),
})
.openapi({ title: "GetStoryOutput" }); 🚀 Step 6: Hosting + OpenAPI + LLM Ready
After writing the resolvers, I hosted the whole thing here:
🔗 https://mcp-news-server.onrender.com
Test it:
/api/listTopStories/api/getStory/8863/openapi.json
And yes, it works with LangChain, Claude, OpenAI, or any custom LLM runner!
🎁 What You Can Do Next
- Wrap any REST API you love in an MCP
- Add contracts, deploy, and share with LLMs
- Use
zod-openapito create swagger-compatible specs - Register it in an agent-aware toolchain or build your own GPT plugin
💬 TL;DR
Modular Contract Protocols give your API structure and meaning. By wrapping a basic REST API like Hacker News with Zod contracts and generating OpenAPI with zod-openapi, you can:
- Build more robust backend tools
- Make them compatible with LLMs
- Reduce guesswork and increase composability
Let’s stop building brittle REST services — and start building smart, structured, machine-readable APIs.
💡 Try it yourself — and let me know what you wrap next!
Ship AI features that survive real traffic
Start from the failure modes, choose the smallest architecture that holds up, and instrument the path before you scale it.
If you found this article helpful, consider buying me a coffee to support more content like this.
Related Articles
Most developers build agents with tools. That's not enough. Here's the architectural difference between agent tools and agent skills — and why getting this wrong burns tokens, degrades accuracy, and makes your agents brittle in production.

Claude doesn't count messages like ChatGPT — it counts tokens, and one conversation can burn 10x more than another. Here are 5 tricks that cut your token usage by 80% or more.

Explore the key differences between Agent-to-Agent (A2A) communication and Modular Contract Protocols (MCP) — and why you need both to build powerful AI systems.
Ship AI features that survive real traffic
Start from the failure modes, choose the smallest architecture that holds up, and instrument the path before you scale it.