Presso Network
Note06 of 06

Building on MCP

Building an MCP server from scratch, and the recurring third bug

Building an MCP server from scratch means picking tools over endpoints, wiring auth before the first tool call, and shipping to a real host before you trust it. Here is what that actually looks like, including the three defects we found in our own admin server within hours of merging it.

You already have an API. Somewhere in your product there is a function that fetches an order, refunds one, or suspends an account. Building an MCP server from scratch is mostly the work of describing that function well enough that a model can call it without guessing, wrapping it in a small JSON-RPC server, and putting it somewhere a host like Claude or ChatGPT can reach it. The part that takes days, not the part that takes hours, is everything after “it works on my machine.”

Building an MCP server from scratch, and the recurring third bug - infographic

What does it actually take to build an MCP server from scratch?

Three pieces. A server that speaks the Model Context Protocol, published by Anthropic in November 2024 as an open spec (modelcontextprotocol.io) so any host and any tool provider can talk to each other over the same wire. Tools, which are the functions you expose. And a transport, either stdio for a server running on the same machine as the client, or streamable HTTP for a remote server that anyone’s Claude or ChatGPT can connect to.

If you have ever written a REST endpoint, you can write a tool. The difference that actually matters is what you name it and what you make it do.

The obvious mistake is one tool per database table, because that is how you would design a REST API and old habits are strong. Resist it. A model doing three separate calls to get_order, get_customer, get_payment_status to answer “did this refund go through” burns tokens, burns turns, and gives it three places to misread the join. One tool called check_refund_status that does the join internally and returns a sentence-shaped answer is worth more than the three endpoints combined. Task-shaped tools, not table-shaped ones.

Auth is the next wall, and it is not optional the way it can feel optional on day one of a local prototype. The moment your server is remote and multi-tenant, you need OAuth wired in before you ship your first real tool, not after, because retrofitting auth onto tools that already assume an open connection means touching every tool a second time. Get the resource server pattern right once, at the start, and every tool after that is just business logic.

Then you test it against a real host before you believe any of it. Not a mock client, not a script that pretends to be Claude. The gap between “the JSON-RPC response is well formed” and “Claude actually calls the right tool at the right moment” is bigger than it looks, and it only shows up when a real model reads your tool descriptions and decides what to do with them.

We shipped a new admin server for our own ticketing platform this week, so this is not theory. It went from an idea to live in days, the way the factory is supposed to work. Inside twenty-four hours of merging it, three separate defects turned up in the thing we had just called done. A stale-state guard on the two reversible admin actions, suspend and reactivate, was opt-in rather than required, so a caller who simply forgot to pass the check silently skipped it. Our own commit security review caught that one, not us. The server’s own instructions still told the model it was read-only, days after we had added the two levers that made that sentence false, and a human using it in the first real session was the one who noticed the tool description was lying to the model that was reading it. And a test guide told a user to reconnect their connector to fix a problem the server’s own logs already disproved, because the client re-initialises on almost every message and reconnecting was never the fix.

None of those three were exotic. Every one of them was a case of the server confidently describing itself as something it was no longer, because the code moved and the description did not move with it. That is the pattern worth taking away, and it is not specific to us. A tool’s description is a promise to a reader who cannot see your source code and has no way to check whether the promise is still true. You will update the function. You will forget the sentence next to it. Go check the sentence next to it right now, in whatever MCP server you are running, because there is a reasonable chance it is describing last month’s version of the function.

How many tools should an MCP server have?

Fewer than your first instinct. A host has to hold every tool description in context on every turn, so a server with forty granular tools costs the model attention on thirty-nine it will not use this turn. Start from the handful of things a user actually asks for in plain language, build one task-shaped tool per ask, and only split a tool when you catch the model reaching for the wrong one because you bundled two unrelated jobs into it.

Shipping the server is the easy day. Keeping its own description of itself honest for the following weeks is the actual job, and it is the one nobody puts in the demo.

Where this stops working

Required field, min 1 / 3 entries
  1. 01

    This is written from building remote, hosted MCP servers with OAuth. A single local server run over stdio for your own machine skips most of the auth and multi-tenancy advice entirely.

  2. 02

    The tool design guidance assumes a host that already does context management well (Claude, ChatGPT). Less capable hosts may need more hand-holding per tool than described here.

  3. 03

    Three defects in one server in one week is our number, not a universal law. It says more about how fast a server changes hands after merge than about MCP itself.