WTF is MCP?
What MCP is, why it matters, and what I learned building one for Renalto in production.
TL;DR MCP (Model Context Protocol) gives LLMs a standardized way to call real-world tools — databases, APIs, actions. I built one for Renalto's quote system. It worked perfectly in dev and broke immediately on deploy. Here's what happened and what I learned.
LLMs Are Brains Without Hands
Large language models are great at predicting text. They can summarize, explain, argue. But ask one to update a database or send an invoice?
Sorry, I'm just a text generator.
That's the limit. On their own, LLMs have no way to interact with the real world.
MCP is the fix. It's a standardized layer that connects LLMs to real-world tools — one shared protocol instead of a different API dialect for every service. Before MCP, connecting ten tools to one LLM meant building ten custom bridges. With MCP, it's one highway.
The ecosystem has four parts:
Client → LLM → MCP Server → Tool/Service → Response → Client
Clean, pluggable, and future-proof.
The SSE Problem (Before You Start)
Early MCP relied on Server-Sent Events (SSE) for streaming tool responses. SSE works, but it comes with real costs: persistent connections, long-lived server state, and it burns through serverless compute fast.
The fix is Streamable HTTP — now in the MCP spec. MCP servers behave like classic REST endpoints. Stateless, ephemeral, no websocket timeouts. If you're building one in 2025, build for Streamable HTTP from day one.
From Concept to Production
My goal was a simple MCP server for Renalto's quote management system — an AI assistant named Rita that could:
- Update VAT rates on quotes
- Split compound line items
- Calculate surface estimates
Stack: Vercel Functions, Supabase, @vercel/mcp-adapter, Zod.
Everything worked beautifully in dev. Then I deployed.
TypeError: Cannot read properties of undefined (reading 'addEventListener')
500 errors across the board. The culprit: the MCP adapter was calling browser-specific APIs (addEventListener) inside a Node.js serverless environment. A known issue with @vercel/mcp-adapter@0.3.1 on Vercel Functions.
I tried forcing Node.js runtime, configuring Redis for SSE state, and disabling SSE. None of it worked. The adapter was fundamentally incompatible with serverless.
The Fix: Direct API Endpoints
After enough debugging, the answer was obvious in hindsight: I didn't need the full MCP adapter for simple tool calls. I bypassed it entirely.
// /api/apply-surface.ts
export default async function handler(req, res) {
const { quoteId } = req.body
const { data } = await supabase
.from("quotes")
.select("surface, height")
.eq("id", quoteId)
.single()
const S = data?.surface ?? 75
const H = data?.height ?? 2.6
const M2 = S * H
const P2 = S
const M1 = M2 * 0.2
const P1 = P2 * 0.2
await supabase.from("quotes").update({ M1, M2, P1, P2 }).eq("id", quoteId)
return res.status(200).json({ success: true, data: { S, H, M1, M2, P1, P2 } })
}
It worked. The final architecture is a hybrid:
+---------------------+
| LLM / Agent |
+---------+-----------+
|
v
+---------+-----------+
| /api/server.ts |
| MCP Handler |
+---------+-----------+
|
+-----------+-----------+
| MCP Tool Registry |
+-----------+-----------+
|
(SSE fallback)
|
v
+---------+-----------+
| /api/apply-surface |
| Direct API Handler |
+---------+-----------+
|
v
+---------+-----------+
| Supabase DB |
+---------------------+
What I Actually Learned
SSE and serverless don't mix. The addEventListener error is a symptom of a real architectural mismatch. Know this before you start.
Direct endpoints are more reliable. No SSE, no Redis, no adapter complexity. Standard HTTP is boring and it works.
Validate your environment on startup. A simple connection check catches dead credentials before they become 500s in production.
Health endpoints are worth the two minutes. A /api/health that pings your database tells you immediately whether the issue is your code or your config.
Resilience is part of the product. An AI assistant that breaks silently is worse than one that never shipped. Rita needed to fail gracefully before she could be trusted.
Made with ☕ and significantly fewer tabs open