When I decided to build an MCP server I expected a mountain of web framework boilerplate. It turned out to be the exact opposite: a tiny program that reads JSON lines and writes JSON lines. That is essentially the whole job. Here is how I went from zero to a working hello-world tool, and how you can do the same without drowning in docs.
What You Are Actually Building
An MCP server is a process that talks over the protocol. On the wire it reads a request line, does the work, and writes a response line. If that sounds like a small command-line app, you are right. The rules of that conversation are fixed, and if you match them, any AI client can talk to you. The JSON-RPC 2.0 walkthrough shows those exact messages.
The Two Messages You Must Handle
You do not need a framework to be valid. You need to answer two things correctly. First, when the client asks tools/list, reply with a list of your tools and their input schemas. Second, when the client sends tools/call with a tool name and arguments, run it and return a result. Everything else is optional. If you keep that contract in your head, the rest is just plumbing.
A Minimal Skeleton
Here is the shape of the JavaScript starter I used. It is not production-ready, but it shows how little code is involved:
function handle(line) {
const req = JSON.parse(line);
if (req.method === "tools/list") {
return { id: req.id, result: {
tools: [{ name: "hello", description: "Greets a name",
inputSchema: { type: "object", properties: { name: { type: "string" } } } }]
}};
}
if (req.method === "tools/call") {
return { id: req.id, result: {
content: [{ type: "text", text: "Hello, " + (req.params.arguments.name || "world") }] }};
}
return { id: req.id, error: { code: -32601, message: "method not found" } };
}
That block matches the JSON-RPC shape documented in this protocol guide. Notice I did not build a web server yet. Over the local transport I can run this as a stdio process and it just works.
Running It Locally
For a first test, the simplest transport is stdio: the client starts your process and pipes messages in and out. That is why a command like npx -y your-server appears everywhere in config files. The config JSON guide explains that command and args block you will paste into Claude, ChatGPT, or Cursor. Getting this block right is usually the only fiddly part.
Adding a Real Tool Other People Use
A hello-world file teaches you the shape, but a useful server exposes tools people actually call. IPIPAI's server does this with lookup_ip, rate_ip_node, and more, all listed at /mcp-tools.json. If you want to see a live server read itself, that file is a perfect example of what a tools/list response ends up looking like after the client has already spoken.
Testing Before You Trust It
The cheapest way to check your server is to send the two messages yourself with a small script and read the responses. Most failures are not protocol mistakes. They are the server not starting, a wrong package name, or an expired API key. The connection troubleshooting checklist is built exactly around those culprits, so when your first real client refuses to connect, you will know where to look.
Where the Confusion Creeps In
Three things surprised me. First, you do not need a database or an HTTP stack to start. Second, the same tool works over different transports, so do not over-engineer on day one. Third, none of this requires anyone else to adopt a plugin format. You publish the protocol, and every compatible assistant finds your tools through discovery. If you want the bigger picture of why this is useful, this plain-language intro to MCP servers is worth a read first.
Wrapping Up
Building an MCP server is mostly about reading a request and writing a response in the right JSON shape. Start minimal on stdio, expose one useful tool, and watch it get discovered by a real assistant. From there it is just iteration. The rest of the articles on this site can carry you through the surrounding ecosystem, but the core is genuinely that small.