I Built a Claude Code MCP Connector in 3 Days—And Accidentally DDoS'd My Own Database
I Built a Claude Code MCP Connector in 3 Days—And Accidentally DDoS'd My Own Database
TL;DR: Built a custom MCP connector that slashed my debugging time by 73% and reduced churn by 0.7%. Also learnt that connection pooling isn't optional, Claude makes very aggressive API calls, and 2 AM coding sessions produce questionable variable names. Open-sourcing the template next week.
Last Tuesday at 2 AM, I was debugging a production database issue with cold pizza in one hand and SELECT * FROM users WHERE churn_probability > 0.7 in the other. Not exactly my finest moment as a founder.
Three days later, I shipped a custom Claude Code MCP connector that cut my debugging time by 70%. Here's the full story—what worked, what broke spectacularly, and why I'm never going back to manual CSV exports.
Actually, before we dive in—when I say "debugging manually," I mean I was literally running queries at 2 AM while my churn rate ticked up to 3.8%. My CAC was already $24 per user. Every hour I spent staring at pgAdmin was an hour I wasn't building features that actually retained customers.
The Problem Nobody Talks About
Most indie hackers I know—shoutout to James over at TinyPilot who warned me about this months ago—rely on pre-built integrations for their AI tools. It makes sense. Why build when you can buy?
Here's the thing, though. When you're bootstrapped and running a SaaS with 200+ paying customers, your database schema is a unique snowflake. Off-the-shelf connectors don't understand your userretentioncohorts table. They've never seen that weird subscriptionstatus enum you added at 3 AM six months ago. (I still don't know why I named it subscriptionstatus instead of just status. Haunts me.)
My setup: PostgreSQL 15.6 on Railway, analytics in BigQuery, and a custom billing system I built because Stripe's default reporting wasn't granular enough for my $10k MRR operation. Every time I wanted Claude to help analyse churn patterns, I had to export CSVs, clean them manually, and paste context into the chat window. It was 2024 and I was working like it was 2015.
Pieter Levels once tweeted something that's stuck with me: "The best tools are the ones you build yourself when you're too lazy to do the manual work." I think about that tweet roughly once a week. It's basically my entire product philosophy at this point.
What the Heck is MCP Anyway?
MCP stands for Model Context Protocol—Anthropic's open standard for connecting AI models to external tools. Think of it as USB-C for AI: instead of every tool having a custom integration, MCP provides a universal protocol.
Claude Code (Anthropic's agentic coding tool, launched late 2024) can connect to any MCP server you build. The magic: Claude doesn't just query your database. It understands the schema, writes optimised SQL, and reasons about the results—all within your security boundary.
Well... mostly within your security boundary. Look, it's complicated. The tool definitions need to follow specific JSON schemas, and Claude Code's tool calling behaviour is—let's say "enthusiastic." It'll hammer your endpoints if you let it. More on that disaster in a bit.
No data leaves your infrastructure. At least in theory. In practice, you need to be really careful about what you expose. I cannot stress this enough.
The Build: 3 Days, 2 Pivots, and 1 "Oh Sh*t" Moment
Day 1: The Naive Approach (1,200 Lines of Garbage)
I started by following Anthropic's MCP Python SDK docs (v0.2.3, latest as of November 2024). The example looked straightforward: define tools, implement handlers, connect to Claude Code. Simple enough, right?
Wrong.
Mistake #1: I tried to build a universal SQL connector that could handle any query. By 6 PM, I had 1,200 lines of unmaintainable code that kept timing out on JOIN queries. The error that broke me:
psycopg2.errors.QueryCanceled: canceling statement due to statement timeout
CONTEXT: SQL function "get_user_analytics" statement 1
Over and over. 47 times in one afternoon. I counted.
Pivot: I scoped down. Dramatically. Instead of "query anything," I built three specific tools:
getuserchurnrisk(userid)— analyses individual user behaviourqueryretentioncohort(cohort_date)— pulls cohort retention datagetmrrbreakdown(month)— revenue segmentation by plan
I probably should've started with just one. But you know how it is at 11 PM when you're in the zone and everything feels obvious.
Day 2: The Architecture That Actually Worked
Here's the architecture I landed on. I had this beautiful Miro board with three clean boxes—Claude Code, MCP Server on Railway, PostgreSQL—with neat arrows showing tool calls and query responses. Then I accidentally closed the tab without saving. Lost the whole thing.
The code, though? That survived.
# Core MCP tool definition (simplified)
@mcp.tool()
async def get_user_churn_risk(user_id: str) -> dict:
"""
Analyses churn risk for a specific user based on:
- Login frequency (last 30 days)
- Feature usage patterns
- Support ticket history
- Payment failure count
"""
# Complex query joining 4 tables
query = """
WITH user_activity AS (
SELECT
COUNT(DISTINCT login_date) as active_days,
AVG(session_duration) as avg_session
FROM user_sessions
WHERE user_id = $1
AND login_date > NOW() - INTERVAL '30 days'
),
feature_usage AS (...)
"""
# Returns structured JSON with risk score
Why this worked: Instead of giving Claude raw SQL access—which is terrifying from a security standpoint, I still have nightmares about accidental DROP TABLE statements—I encapsulated business logic in Python functions. Claude reasons about the output, not the query construction. The model never sees a SQL string.
Day 2 metrics:
- Lines of code: 340 (down from 1,200)
- Query response time: 1.2s average
- Security: Zero raw SQL exposed to the model
I went to bed at 3 AM feeling like an absolute genius.
I was wrong.
Day 3: The "Oh Sh*t" Moment (And The Fix)
Deployed to production at 11 AM. By 11:23 AM, my error logs exploded.
The MCP server was opening a new database connection for every single request instead of connection pooling. With Claude making 15+ tool calls per analysis session—and doing them in parallel because it's "efficient"—I hit Railway's connection limit in minutes. The exact error:
FATAL: remaining connection slots are reserved for non-replication superuser connections
My phone started blowing up. Users couldn't log in. I was mid-coffee-pour and nearly dropped the French press. My Apple Watch thought I was having a cardiac event. Not exaggerating—it actually triggered the high heart rate notification.
Fix: Implemented pgBouncer 1.21 connection pooling and added a connection cache with 300-second TTL. Total downtime: 7 minutes. My heart rate: approximately 400 BPM.
Post-fix metrics:
- Concurrent connections: 5 (down from 50+)
- Average tool call latency: 0.8s (down from 1.2s)
- Zero connection errors in 72 hours since
I also added rate limiting. Because Claude will absolutely DDoS your infrastructure if you let it. Ask me how I know. Actually don't—it's embarrassing.
Real Results (With Numbers)
I've been using this connector for 2 weeks now. Here's what changed:
| Metric | Before MCP | After MCP | Delta |
|---|
| Debug time per churn case | 45 min | 12 min | -73% |
|---|
| Weekly DB queries manually written | 80+ | 12 | -85% |
|---|
| Time to identify churn patterns | 3-4 days | 4 hours | -92% |
|---|
| Monthly churn rate | 3.8% | 3.1% | -0.7% |
|---|
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.