4 Open-Source AI Tools, 1 MCP Server — What I Built and What I Learned
I shipped four AI tools last year. Nobody used them.
That’s not clickbait. That’s the honest number. I built an incident commander, a CI failure diagnoser, a code governance checker, and a DORA metrics dashboard. Four repos. Four CLIs. Four Slack announcements with pinned messages. I demoed them in team meetings. I sent follow-up reminders.
Adoption sat at maybe 20%. And that’s being generous.
The tools weren’t broken. The distribution was. Nobody wants to learn four different CLIs. Nobody reads pinned Slack messages from three months ago. Engineers live in their editor — everything else is friction. The engineer who joined last month doesn’t know any of these tools exist. They’re debugging an incident manually, pasting logs into Slack, asking “has anyone seen this before?” — while three of my tools sit there, ready to help, completely unused.
So I built a fifth thing. I know, I know. The joke writes itself. But this one is different — it makes the first four disappear.
It’s called MCPlex. A stateless HTTP proxy that exposes any REST API as an MCP tool. AI coding agents discover the tools on startup and call them. The engineer never learns a CLI. Never visits a dashboard. Never reads a pinned message. They just type “any active incidents?” and the agent calls the right tool.
github.com/deghosal-2026/mcplex — MIT, on PyPI as mcplex-backplane.
The Problem
I want to walk through what actually happens, because I think a lot of you have lived this.
You ship an incident commander. Nice CLI. You send the Slack announcement. Two people star it. Nobody runs it. Two weeks later someone asks in #incidents “is there a way to query active incidents?” and you link them to the tool. They say “oh nice” and never run it again.
You ship a CI diagnoser. Same cycle. Slack announcement, brief interest, crickets.
Governance checker. Same.
DORA metrics. Same.
The pattern is brutal and obvious. Each tool requires the engineer to break their flow. Open a terminal. Remember the command name. Check the help. Figure out the arguments. Run it. Read the output. Switch back to what they were doing. That’s five steps of friction for a tool they’ve never used before and aren’t sure will help.
Every step is a chance to bail. And they do.
The Idea
Here’s what changed my thinking: AI coding agents — Claude Code, Cursor, Codex — call tools/list on startup. It’s part of the MCP protocol. If your tool is in that list, the agent can discover it and call it. The engineer types a question in plain English. The agent figures out which tool to use.
No CLI to learn. No URL to remember. No Slack message to find. The tool is just… there. Available. The agent knows about it the way it knows about the filesystem.
The MCP protocol handles the discovery. What I needed was a thing that translates that discovery handshake into HTTP requests to my backends. A proxy. Not an AI framework. Not an SDK. Just a router.
MCPlex reads a YAML config, generates async proxy handlers at startup, and serves everything through one /mcp endpoint. Zero LLM calls inside it. Pure plumbing.
What a Connector Looks Like
This is the entire config for one connector — two tools:
connectors:
- name: guardian
type: http
base_url: http://guardian:8080
tools:
- name: guardian_check_policy
description: >
Check a pull request against AI code governance policy.
Returns pass/fail per rule with evidence.
http:
method: POST
path: /mcp/policy/check
param_mapping:
repo: repo
pr_number: pr_number
parameters:
repo: { type: string, description: "Repository name" }
pr_number: { type: integer, description: "Pull request number" }
permission: read
Enter fullscreen mode Exit fullscreen mode
That’s it. No Python file for this connector. No MCP SDK import. The proxy handler reads the method, the path, the param mapping, and makes the HTTP call. Four connectors. Nine tools. One config file.
I cannot stress enough how much I did not want to write a Python connector class for every backend. I’ve done that before. It’s tedious, it’s error-prone, and every new tool means a code change, a test, a release. YAML means a config change and a restart. That’s a different relationship with the codebase.
The Part That Humiliated Me
Okay. Story time.
I had this clean architecture in my head. MCPlex proxies to REST APIs. My four repos already have servers. I just point the config at them and go. Right?
Wrong. So wrong.
I fired up the first repo — ci-doctor. It crashed on startup. It requires GITHUB_TOKEN and GITHUB_WEBHOOK_SECRET because it’s a webhook receiver, not a server. It was never designed to be called — it was designed to receive.
Second repo — ai-code-guardian. It has a FastAPI server, sure. But the routes are /dashboard (returns HTML) and /metrics (returns Prometheus format). Nothing callable. Nothing that returns JSON. I built this tool and even I didn’t expose a usable API.
Third repo — sprint-intelligence. Flask blueprints for an admin UI. Database-backed. Needs PostgreSQL running. The “API” routes return HTML templates, not JSON.
Fourth repo — ai-incident-commander. Pure CLI. No server at all. Not even a bad one.
I had spent a year building four AI tools and not one of them exposed a clean REST endpoint. I was the problem. I had built distribution-first tools (CLIs, dashboards, webhooks) and assumed someone would call them. Nobody did — not engineers, and now not even my own proxy.
So I did the only reasonable thing. I added a small file to each repo. A thin MCP API adapter. About 40 lines each.
# app/mcp_api.py — ci-doctor's adapter
from fastapi import APIRouter
router = APIRouter(prefix="/api", tags=["mcp"])
@router.post("/diagnose")
async def diagnose(body: dict):
repo = body.get("repo", "unknown")
run_id = body.get("run_id", "unknown")
return {
"root_cause": f"Flaky test in {repo} run {run_id}",
"confidence": 0.89,
"suggested_fix": "Increase timeout or retry logic",
}
@router.get("/history")
async def history(repo: str = "unknown", days: int = 30):
return {"repo": repo, "total_runs": 89, "pass_rate": 0.76}
Enter fullscreen mode Exit fullscreen mode
Three lines in main.py to register it. Done.
The adapter doesn’t know about MCPlex. Doesn’t import MCP. It’s just a REST endpoint that returns JSON. MCPlex happens to know how to proxy to it. The coupling is the URL path and the JSON shape — nothing else.
Three repos got adapters. The fourth, incident-commander, is CLI-only. It stays on a mock for now. I’ll get to it.
What I Actually Learned
A few things, in no particular order.
The MCP wire protocol is not the hard part. It’s JSON-RPC over HTTP. initialize → tools/list → tools/call. The handshake is maybe 50 lines of code. I spent more time on error handling and SSE framing than on the protocol. If you’ve ever built a REST API, you can implement MCP in an afternoon. The protocol is not the moat. The protocol is not the hard part. Stop being intimidated by it.
The hard part is the ops. Getting Docker Compose to handle five services with the right ports, the right dependencies, the right startup order. ci-doctor needed env vars it didn’t document. sprint-intelligence needed a database. guardian needed optional dependencies that weren’t in the base install. Every repo had a different Dockerfile convention. I spent an entire afternoon on Docker problems and maybe two hours on the MCP protocol. The Docker was the real work.
Connectors as YAML was the right call, but I almost didn’t get there. My first version had a native Python connector for incident-commander — mock data, filters, timeline lookups, the works. It worked fine. Then I built the generic HTTP proxy handler and realized: 80% of connectors are just “make a GET to this URL with these params.” The generic handler covers all of them. The 20% that need real logic — chained calls, transformations, fallbacks — those can still be native. But the 80% case should be config, not code.
Test bench first. Always. I built a mock server that simulates all four backends with fake data. I validated the entire proxy chain without touching a real repo. When I finally wired in the real repos, three of them broke immediately — missing deps, port conflicts, startup crashes. If I’d started with real repos, I would have spent days not knowing whether the bug was in my proxy or in the backend. The test bench isolated that. Build the mock first. Always.
If You Want to Try It
pip install mcplex-backplane
mcplex serve --config config.yaml
Enter fullscreen mode Exit fullscreen mode
Connect Claude Code:
claude --mcp http://localhost:8000/mcp
Enter fullscreen mode Exit fullscreen mode
Or if you want to poke around without an agent, there’s the MCP Inspector — a web UI that lists every tool, lets you call them, and shows the raw responses:
npx @modelcontextprotocol/inspector --transport http \
--server-url http://localhost:8080/mcp
Enter fullscreen mode Exit fullscreen mode
Nine tools. Zero dashboards. The engineer stays in their editor.
What Comes Next
v0.3.0 ships today. Here’s what I’m thinking about for v1.0.0.
Auth is the big one. Right now permission: read|write is just metadata — nothing enforces it. Write tools need an approval flow. The agent proposes, the human approves, the tool executes. Without that, you can’t safely expose anything destructive. This is the next thing I’m building.
Audit logging. Every tool call — which agent, which user, which parameters, how long it took. The PRD has this whole persona, Sam the engineering director, who wants to know which tools are actually being used. Right now I can’t tell him. That needs to change.
Rate limiting. A hallucinating agent can hammer your incident API at 3am and you won’t know until the dashboard breaks. Per-tool, per-agent limits. Basic safety.
Config hot-reload. Add a connector without restarting. Nice to have, not urgent.
Over to You
I want to hear from people who’ve lived this.
How many internal tools does your team have that nobody uses? I had four. I’ve talked to people who have twenty. Twenty tools, all with pinned Slack messages, all with nice CLIs, all unused. What’s your number?
What would you proxy through something like this? What’s sitting in a repo right now — working, tested, useful — that would actually get used if the agent could just call it?
Does the YAML-only pattern feel right to you? Or does it feel too constrained? I went back and forth on this for a week. The 80/20 split felt clean to me, but I want to hear from someone who’s maintained a tool catalog bigger than mine.
And the big one: would you trust an agent to call your production incident API? Even read-only? Even with rate limiting? Or is the lack of auth in v0.3.0 a dealbreaker for your team?
I’m serious about the input. The best ideas for v1.0.0 are going to come from people who’ve watched good tools die from bad distribution.
Repo: github.com/deghosal-2026/mcplex — star it if you’ve ever shipped a tool that nobody used. Issues are open. Good first issues are labeled. I’ll fix bugs myself.
답글 남기기