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.
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.