Home / Blog / I Built an MCP Server From Scratch — Here's What t...

I Built an MCP Server From Scratch — Here's What the Docs Won't Tell You

By CaelLee | | 7 min read

I Built an MCP Server From Scratch — Here's What the Docs Won't Tell You

TIL that implementing the Model Context Protocol isn't the "weekend project" every YouTube tutorial makes it out to be. Three all-nighters, two ragequits, and one 3 AM existential crisis where I seriously considered becoming a farmer later, my MCP server finally responds without randomly dropping connections. Here's the real talk.

I've been seeing MCP pop up everywhere since Anthropic released it back in November 2024, and like many of you, I thought "cool, another protocol to add to my CV." Spoiler: it's actually useful, but the learning curve is steeper than the hello-world examples suggest. Way steeper.

What actually is MCP? (The non-marketing version)

For those who haven't fallen down this rabbit hole yet, MCP is basically a standardised way for LLMs to talk to external tools and data sources. Think of it as USB-C for AI integrations—one protocol to rule them all, instead of writing custom integrations for every single tool.

Actually, wait—I should clarify that comparison. It's more like USB-C if USB-C also handled discovery and capability negotiation. Which real USB-C kinda does now I guess? Whatever, you get the point.

"It's like if REST and WebSocket had a baby that speaks JSON-RPC"—some comment I saw on r/MachineLearning that's surprisingly accurate

The architecture is client-server. Your MCP server exposes "tools" (functions the AI can call), "resources" (data the AI can read), and "prompts" (pre-written templates). The client—usually Claude Desktop or some VS Code extension—discovers these and uses them. Simple enough on paper.

My implementation journey (the ugly parts)

I decided to build an MCP server in TypeScript. Version 0.5.0 of the SDK, specifically. Why? Because I hate myself apparently. And because all the Python examples I found were using 0.3.2 and half the APIs had changed.

The official SDK helps. It does. But here's where things got real.

1. The transport layer isn't as plug-and-play as advertised

The docs make it seem like you just pick stdio or SSE and go. Reality check: stdio works brilliantly for local development—like, genuinely brilliant, no complaints. But if you're building anything that needs to handle multiple concurrent clients, you'll want SSE.

The problem?

Error handling in SSE is... let's call it "minimal." Charitably.

I spent 4 hours debugging why my server would randomly stop sending events. Not crashing, just... stopping. Silent. Turns out, if your SSE connection drops mid-stream, the default implementation just shrugs and walks away. No reconnection logic, no buffering, nothing. The client sits there waiting for events that aren't coming.


// What the tutorial shows:
const server = new McpServer({ name: 'my-server' });
server.connect(transport);

// What you actually need:
server.on('error', (err) => {
 // Hope you like debugging race conditions
 console.error('Something broke:', err);
 // Spoiler: this fires approximately never when you need it
});

I think there's a GitHub issue about this somewhere. Probably has 80+ thumbs up by now.

2. Tool definitions are deceptively simple

Defining a tool looks straightforward. Name, description, input schema. Three fields. How hard could it be?

Well.

The description field is doing heavy lifting. Like, all the lifting. The AI uses your description to decide when to call the tool, and vague descriptions lead to the AI either never calling your tool or calling it for literally everything.

Real example from my project: I built a database query tool. First description: "Query the database." Result: Claude called it to answer "what's your name?" I wish I was joking. The logs were just Claude repeatedly trying to SELECT username FROM somewhere.

Updated description: "Execute read-only SQL queries against the PostgreSQL database containing user analytics data. Use for questions about user counts, engagement metrics, or historical trends. Do NOT use for system configuration or metadata queries."

Night and day difference. Like, completely different tool behaviour.

But here's the thing nobody mentions—you've got to iterate on these descriptions. I went through 7 versions before it felt right. And what works for Claude might make GPT-4o call your tool way too aggressively or not at all. There's no standard for this yet.

3. The "tools/list" discovery is cool until it's not

When your MCP server starts, the client calls tools/list to discover what's available. Makes sense. But if you have 50+ tools (I may have gone overboard... okay I definitely went overboard with 73 tools), that initial handshake gets chunky.

There's no pagination in the spec. Not yet anyway. I've seen Claude Desktop timeout on the discovery call—just spin forever then silently fail. Nothing in the logs. Took me 2 hours to figure out what was happening.

Pro tip: batch your tool registrations and consider lazy-loading tools that aren't commonly used. Yes, this defeats some of the auto-discovery magic, but your users will thank you when the connection initialises in 2 seconds instead of 15.

I'm currently running about 12 "core" tools at startup and loading the rest on demand. It's hacky. I don't love it.

What actually worked well

It's not all complaints. Once I got past the initial hurdles:

My "production-ready" checklist

After this experience, here's what I'd recommend before calling your MCP server done:

  1. Implement proper logging — The protocol has a logging system built in. Use it. Future you debugging at 2 AM will want those timestamps.
  2. Add rate limiting — Your tools will get called in rapid succession. Claude doesn't know about your API limits. Or care, apparently.
  3. Validate inputs with Zod — The AI will send malformed JSON sometimes. Not often, but when it does, you want a proper validation layer, not a cryptic "undefined is not a function" at 3 AM. Ask me how I know.
  4. Test with multiple clients — Claude Desktop, Continue.dev (v0.9.5), and custom clients all behave slightly differently. Claude is the most forgiving, honestly.
  5. Monitor tool call frequency — You'll discover which tools are actually useful vs. which ones seemed like a good idea after your third coffee. Spoiler: half my tools have never been called once.

The elephant in the room

Is MCP going to be the standard, or will it get Google Wave'd?

I think—and this is just my read after living in this codebase for two weeks—the protocol is solid enough to survive. But the real test is whether major players beyond Anthropic adopt it. OpenAI has their function calling, Google has their thing, and nobody likes being told their baby is ugly.

The protocol itself is open and well-designed, which gives me hope. But I've been burned by "standards" before. Looking at you, SOAP. And GraphQL kinda-sorta-not-really delivering on the dream.

I will say, I've seen more community tools and servers pop up in the last month than I expected. There's an MCP server for Brave Search now, for GitHub, for PostgreSQL. That ecosystem matters.

TL;DR (Key Takeaways)

Edit: Thanks for the gold, kind stranger! Since people are asking—yes, I'll open-source my implementation once I clean up the embarrassing parts. Give me a week. Maybe two. There's a comment block in my SSE handler that just says "// I don't know why this works but don't touch it" and I need to figure that out first.

Edit 2: Several people asked about Python vs TypeScript for MCP servers. I've used both now (Python with the 0.4.1 SDK, TypeScript with 0.5.0), and honestly? Python's async story makes the SSE transport slightly less painful, but TypeScript's type system catches schema validation bugs at compile time. Pick your poison. I went with TypeScript because I'd rather debug types at 2 PM than runtime errors at 2 AM.

Edit 3: Someone in the comments mentioned that MCP Inspector tool. If you're building an MCP server and haven't used it yet, stop reading this and go install it. I can't believe I didn't find it until day 6 of this project.

What's your experience been with MCP? Anyone found a clean way to handle tool versioning, or are we all just YOLOing it with breaking changes and hoping nobody notices?

mcp #ai #typescript #llm #programming #webdev

C

Cael Lee

Full-stack developer with 8+ years of experience. Currently building AI-powered developer tools. I've tested 20+ AI API providers and coding assistants.

Ready to get started?

Get your API key and start building with 180+ AI models.

Get API Key Free