The Saturday I Tried to Build an MCP Server Myself and Hit Auth on Step One






The Saturday I Tried to Build an MCP Server Myself and Hit Auth on Step One


The Saturday I Tried to Build an MCP Server Myself and Hit Auth on Step One

It was one of those quiet Saturday mornings where the Mac mini was humming, the coffee was hot, and I had a dumb idea: I would build my own MCP server. Just a tiny one. Wrap a free weather API, expose one tool, see Claude call it. How hard could it be?

Two hours later I had not written a single line of tool logic. I was still reading about transports.

I thought I understood MCP. I did not.

I have been using MCP servers for months. I wrote a whole post about the first time an MCP server let Claude read my Notion and how strange it felt. So I figured the builder side would be a natural next step.

It was not. Reading the spec on modelcontextprotocol.io is like opening a door you thought led to a closet and finding a hallway. There are servers, clients, transports, capabilities, resources, tools, prompts, sampling. And before any of that matters, you have to pick how your server talks to Claude.

Stdio or HTTP, and why I stared at this for an hour

The first real wall was transport. MCP servers can run over stdio (Claude launches your process and pipes JSON-RPC over stdin and stdout) or over HTTP with Server-Sent Events. The docs say both. They do not say which one you, a guy on a Mac mini at 10am, should pick.

Stdio sounded simpler. No port, no auth, Claude Desktop spawns the binary and you are done. So I started there. I scaffolded a Python project with the official mcp SDK, wrote a stub server, added it to my claude_desktop_config.json, restarted Claude.

Nothing. No tool showed up. No error in the UI. I had to dig through ~/Library/Logs/Claude/ to find that my Python interpreter path was wrong because I had been using a venv. Of course Claude Desktop does not know about my venv.

Eben’s note: The MCP spec is honest about the protocol but quiet about the lived reality of “your venv does not exist from the perspective of the app launching your script.”

And then I thought, maybe HTTP is easier

So I switched. I would run an HTTP MCP server on localhost, point Claude at it, life would be cleaner. That is when OAuth showed up.

The HTTP transport in the current spec expects authorization. Not always required for local dev, but the moment you read past the first paragraph you are in OAuth 2.1 land with PKCE, discovery endpoints, and bearer tokens. For a weather API that does not need any of that.

I sat there feeling like I had walked into a meeting I was not invited to. I just wanted to return today’s temperature in Seoul. Why am I reading about authorization server metadata?

Going back to stdio and actually fixing it

I gave up on HTTP for the day. Went back to stdio with a clear head. This time I did three boring things that fixed everything.

One, I used the absolute path to the Python inside my venv in claude_desktop_config.json. Two, I logged everything my server did to a file in /tmp because stdout is reserved for the protocol and any stray print will corrupt the JSON-RPC stream. Three, I tested the server manually first by piping a JSON initialize request into it from the terminal before ever asking Claude to talk to it.

That third one is the tip I wish someone had handed me at 10am. If your server cannot answer initialize from a plain shell pipe, Claude will not save you.

The moment Claude saw my tool

Around 1pm I restarted Claude Desktop one more time, opened a new chat, and asked “what tools do you have?” There it was: get_weather, my tool, sitting in the list next to all the official ones. I asked for the weather in Seoul. Claude called it. Returned 18 degrees and clear skies. Correct on the first try.

I sat there grinning at my screen for longer than I want to admit. It was a wrapper around a free API. It did one thing. But Claude could now use it, and I had built the bridge.

Why this gap is bigger than I thought

The distance between “I use MCP servers” and “I can build one” is not about coding skill. The tool function itself was twenty lines. The wall is everything around it: transport choice, process lifecycle, auth model, log discipline, config paths.

It reminded me of how DeepSeek V3 wrote cleaner Python than I did the other morning. The model can write the function. It cannot make the choice between stdio and HTTP for your specific weekend project running on a Mac mini in your living room. That part is still on you.

What I am taking from this

If you want to build your first MCP server, start with stdio, use absolute paths in the config, never print to stdout, log to a file, and test your server with a manual JSON-RPC pipe before involving Claude at all. Skip HTTP and OAuth on day one. Get one tool showing up first. The dopamine from seeing your name in the tool list is worth the two hours of stupid mistakes.

Next weekend I want to wire this same server to read from my own notes folder. That probably means resources, not just tools, which is a whole other section of the spec I have been pretending does not exist.

Related Posts

Tags: #AIagents #ClaudeCode #OpenClaw #MacMini #OpenRouter #buildinginpublic #Eben


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *