2026-02-17

I Built an MCP Server for My Portfolio — Death by Tiny Bugs

How I exposed my portfolio blog system as an MCP server so Claude could operate it with natural language — and the 5 small but painful bugs that stood in the way.

MCPPythonFastMCPDebuggingDeveloper ToolsLearning In PublicPortfolio

Why I Even Touched MCP

My portfolio already works fine.

  • Next.js frontend
  • FastAPI backend for embeddings + semantic search
  • Pinecone for vectors
  • MongoDB for storage

Blogs go live. Recommendations generate. Everything fine.

But the thing that kept irritating me was this: every time I wanted to create a blog, check system health, or search content — I had to open a dashboard, hit an API manually, or use Postman.

I kept thinking… why can't I just tell an AI to do this?

Then I found MCP (Model Context Protocol).

Basically:

  • You expose tools
  • AI clients (Claude, Cursor, etc.) can discover them
  • They call your tools automatically

So it's like building an API, but the consumer is an LLM.

I thought — okay cool, let's build it.


First Attempt — The "It Should Just Work" Phase

I wrote ~600 lines of Python.

The plan was simple:

  • Use MCP Python SDK
  • Expose tools like create_blog, search_blogs_by_text, health_check
  • Each tool just calls my existing APIs over HTTP
  • Run over stdio so Claude Desktop can spawn it

Everything looked clean. Schemas defined. Tools registered.

Then I ran it.

And that's when reality started.


Bug 1 — The MongoDB Ghost

I had a tool called get_blog_recommendations. Inside it:

db = await self._get_mongo_db()
recs = db.recommendations.find_one({"blogSlug": slug})

Looks fine, right?

Except _get_mongo_db() was literally:

pass

So it returned None. So basically:

None.find_one(...)

Crash.

But here's the bigger realization: why is my MCP server touching MongoDB at all?

It's supposed to be a thin proxy. My ML API already has search. So instead of MCP → MongoDB, I changed it to call the ML API /search endpoint and build recommendations from there.

Simpler. Cleaner.

Lesson: Just because you can connect to a DB doesn't mean MCP should. Keep it thin.


Bug 2 — I Used the Wrong Abstraction Level

I started with the low-level MCP Server class, which meant:

  • Manual JSON schemas
  • Manual router
  • Big if-elif chain in call_tool
  • Lots of boilerplate

It worked. But it was ugly.

Then I discovered FastMCP.

@mcp.tool()
async def health_check():
    ...

That's it. The docstring becomes the description. The function signature becomes the schema.

I deleted 100+ lines immediately.

Lesson: Don't use low-level APIs unless you actually need them.


Bug 3 — stdout Broke Everything

This one was evil.

MCP stdio transport works by sending JSON-RPC over stdout. So what happens if you do:

print("🚀 Starting MCP server")

You corrupt the protocol. The client reads that line expecting JSON. Silent failure. No error. Just nothing works.

Took me 20 minutes to realize.

Fix:

print(msg, file=sys.stderr)

All logs go to stderr. stdout is sacred.

This was the most annoying bug of the entire session.


Bug 4 — GET Request with a JSON Body

This one hurt my ego.

My helper function was passing json= to every HTTP request — including GET requests. httpx .get() doesn't accept json=.

AsyncClient.get() got an unexpected keyword argument 'json'

HTTP 101. GET doesn't have a body. I still messed it up.

Fix:

if method != "get" and json is not None:
    kwargs["json"] = json

Five lines. Fixed four tools.


Testing Flow That Actually Worked

I learned the hard way that testing order matters.

Step 1: Just import the module.

python -c "import mcp_server"

If this fails, don't even try running the server.

Step 2: Run HTTP mode + MCP Inspector.

Inspector is great because you see raw JSON-RPC and can test each tool individually. Claude Desktop is a black box — if something fails, you get silence.

Step 3: Only after Inspector works, add it to Claude Desktop config. Restart.

That moment when Claude called my list_blogs tool, hit my Vercel deployment, and returned formatted results — that was satisfying.


The Architecture That Actually Makes Sense

The MCP server is a translator. Nothing more.

Claude → MCP tool → HTTP call → Next.js or FastAPI → DB

It's not:

  • A database layer
  • An ML engine
  • A business logic container

Just a clean bridge between Claude and your existing system. And that's the correct design.


Overall

Total time: ~2 hours
Total bugs: 5 meaningful ones
Most painful line: json=json in a GET request

But now I can say:

"List my blogs."
"Check embedding health."
"Search my content."

And an AI uses my server to operate my system.

MCP isn't magic. It's just clean protocol + good architecture.

But when it works… it feels fab.

Related Reading

Subscribe to my newsletter

No spam, promise. I only send curated blogs that match your interests — the stuff you'd actually want to read.

Interests (optional)

Unsubscribe anytime. Your email is safe with me.