4 Open-Source AI Tools, 1 MCP Server β What I Built and What I Learned
TL;DR: This article has been edited to incorporate the fixes and issues raised in the comments β thank you to everyone who engaged. I've shipped v0.4.0 with identity propagation, audit logging, rate limiting, schema validation, and SSE heartbeat. Full list in CHANGELOG.md.
I shipped four AI tools last year. Adoption was lower than I wanted, and the reason was blunt: none of them spoke MCP. The interfaces didn't support the protocol AI coding agents now call on startup. The tools worked. The front door was wrong.
An engineer in 2026 is in their editor, asking Claude Code "what's the status of the payment-service incident?" β and that question can't reach your 2019 incident CLI. The tool is useful. The interface is obsolete.
I didn't want to build four separate MCP servers, one per tool, and maintain four of them. I wanted one server that could front all four β and any other pre-AI tool I already had lying around β and give them a 2nd life behind a single /mcp endpoint. That's MCPlex. A thin, stateless HTTP proxy: you point it at an existing REST endpoint (or add a ~40-line adapter if the tool only speaks HTML/CLI), and any MCP-compatible agent can now call it. The tool keeps its auth, its logic, its governance. MCPlex is just the new front door.
To prove the pattern, I applied it to four of my own repos. Three of them already had backends; one is CLI-only and runs on a mock for now. The point isn't the four repos β the point is the pattern for any pre-AI tool you already have.
The origin story: 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 interfaces were. 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 unreachable from the agent they're already talking to.
So I built a fifth thing. I know, I know. The joke writes itself. But this one is different β it makes the first four reachable from one place, without writing four MCP servers.
It's called MCPlex. A stateless HTTP proxy that maps simple JSON REST endpoints (GET with query params, POST with JSON body, flat parameter mapping) into MCP tools. 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.
The 4 repos below are illustrative β they show the pattern. The pattern applies to any pre-AI tool with a callable endpoint.
github.com/deghosal-2026/mcplex β MIT, on PyPI as mcplex-backplane. Early proof-of-concept; not production-hardened.
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 from their editor. 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 β because it's a terminal command, and they're already in their editor talking to an agent.
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 β and in 2026, none of those steps are "ask the agent that's already open."
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 MCPlex Is Today (and Isn't)
The comments on this post called out that my earlier framing oversold it, so let me be blunt about what's actually in the repo as of v0.4.0.
It's a thin, stateless YAML-configured proxy. It maps simple JSON REST endpoints β GET with query params, POST with a JSON body, flat parameter mapping β into MCP tools. That's the shape of the problem it solves, and the shape it doesn't solve. The MCP wire protocol (initialize β tools/list β tools/call) is implemented in ~400 lines of Python with no MCP SDK. It auto-detects JSON-RPC vs one-shot SSE framing via the Accept header, and the SSE path now sends a heartbeat keepalive frame.
v0.4.0 added the safety basics I'd been missing. Client identity from initialize gets forwarded to backends as X-MCP-Client-Name and X-MCP-Client-Version headers β so the backend at least knows which agent is calling, though this is header forwarding, not token-based auth. Every call gets a structured audit log line: session ID, user ID, tool, backend URL, HTTP status, response size. That's for debugging and compliance, not a full audit system. There's per-tool and per-agent rate limiting β basic bucket limits, not sophisticated policy. Tool arguments get validated against the declared types (string, integer, enum, min/max, required, unknown-param rejection) before the proxy fires. Non-JSON responses β HTML pages, plain text β get wrapped instead of crashing. And there's a shared httpx client so connections pool across calls instead of opening one per request.
What it still isn't: production-hardened. No OAuth/OIDC. No permission enforcement β permission: read|write is still metadata, and write tools still execute immediately with no approval flow. No pagination, path-param templating, retries, or backend streaming. PUT/DELETE are documented but not implemented. The SSE layer has keepalive now but no progress streaming or session continuity. It's not a replacement for backend logic β the tool keeps its auth, governance, and business logic. MCPlex is the front door, not the engine.
What's planned for v1.0.0 is at the bottom of this post.
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 # NOTE: metadata only β not enforced in v0.4.0
That's it for an HTTP connector β no Python file, no MCP SDK import. The proxy handler reads the method, the path, the param mapping, and makes the HTTP call. (Caveat: MCPlex also ships one native Python connector, incidentgpt.py, for the CLI-only incident-commander repo that has no HTTP server. The 80% case is YAML; the 20% case can still be native Python.)
Four connectors in the demo config. Nine tools. One YAML file. Three of the four connectors proxy to real backend repos; the fourth (incident-commander) is CLI-only and currently runs on mock data via a native connector.
A note on permission: the read/write field in the config is purely informational right now. Write tools execute immediately β no approval flow, no confirmation prompt. A tool marked permission: read can still issue POST requests that mutate state if the backend is permissive. This is the most urgent v1.0.0 fix β v0.4.0 started identity propagation (header forwarding), but permission enforcement is still missing.
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.
Why there's still one Python connector. The generic HTTP proxy handler covers the 80% case β any backend that already speaks JSON REST. But for the 20% that need real logic β chained calls, transformations, or backends with no HTTP server at all β MCPlex also supports native Python connectors. Today, one connector uses this path: incidentgpt.py, which provides mock data for the CLI-only incident-commander repo until it gets a real HTTP adapter. Native connectors register only when no HTTP proxy connector in the YAML covers the same tool name, so config-driven connectors always take precedence. The goal is to shrink the 20% over time, not eliminate native connectors entirely.
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 interface-first tools (CLIs, dashboards, webhooks) and assumed someone would call them. Engineers didn't β the interfaces didn't match how they worked β and now not even my own proxy could reach them.
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}
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.
Here's the unspoken requirement I should have led with: MCPlex proxies to REST APIs, but the backend has to be a REST API first. If your tool is a CLI, a webhook receiver, or a dashboard that returns HTML, you'll need the same ~40-line adapter. The "YAML-only connectors" story is true β but only after the backend exposes a clean JSON endpoint. Three of my four repos got adapters. The fourth (incident-commander) is CLI-only with no server at all; it stays on mock data via a native Python connector until I build its adapter. MCPlex does the MCP protocol part; you do the REST endpoint part.
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.
A commenter (Ryan Mingus) rightly pushed back on the "distribution was broken" thesis, and I want to concede the point. Low adoption doesn't automatically mean the interface had too much friction. It can also mean the tool wasn't useful enough, didn't solve a frequent problem, or wasn't trusted. Putting a not-useful tool behind an AI agent doesn't make it useful. MCPlex solves interface friction β it doesn't solve demand. The 2nd-life framing I led with (pre-AI tools with proven utility) is the case where interface is genuinely the bottleneck. For new tools with unproven demand, MCPlex is not the answer; validating the problem is.
The YAML-only story has a hidden prerequisite I should have led with. "Connectors are YAML, not Python" is true β but only after the backend exposes a clean JSON REST endpoint. Three of my four repos didn't. I had to add ~40-line adapters to each before MCPlex could proxy to them. If your tool is a CLI, a webhook receiver, or an HTML dashboard, the YAML config is the easy part; the adapter is the real work.
Observability is a safety requirement, not a usage metric. I originally framed audit logging as "Sam the engineering director wants to know which tools are used." A commenter (Raju Dandigam) pointed out the real need: if an agent calls a tool and gets a confusing result, there's no trail to replay. Structured audit logging β timestamp, session, tool, params, latency, status β is what makes an agent-driven tool surface safe to operate. v0.4.0 ships the basics of this; I'll build it out further before any usage analytics.
On the repo's current maturity: a commenter (Scarab Systems) ran a diagnostic scan and got 206 signals, 0 findings β meaning the implementation is too thin for most checks to find concrete broken boundaries. That's a fair read. This is a ~400-line proof-of-concept, not a production backplane. The architecture is clean (config β registry β transport β connectors), 75 tests pass, and the 4-connector demo works end-to-end. v0.4.0 added identity headers, audit logging, rate limiting, schema validation, and SSE heartbeat β but there's still no OAuth, no permission enforcement, and no write-tool approval flow. If you're evaluating this for production, treat it as a pattern and a starting point, not a finished product. The v1.0.0 list at the bottom is real β I'm building it in the open.
If You Want to Try It
To try the 4 demo connectors, you need the sibling repos (guardian, ci-doctor, sprint-intelligence) cloned alongside mcplex, or you can run the included test-bench mock server (python tests/test_bench.py) which simulates all four backends with fake data. The incident-commander connector runs on mock data regardless. See docker-compose.yml for the full stack, or Dockerfile.bench for mock-only mode.
pip install mcplex-backplane
mcplex serve --config config.yaml
Connect Claude Code:
claude --mcp http://localhost:8000/mcp
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
Note: early proof-of-concept, not production-hardened. v0.4.0 adds identity headers, audit logging, rate limiting, and schema validation β but there's no token-based auth and no permission enforcement yet (see "What Comes Next"). The 4 demo connectors are illustrative of the pattern β the value is in applying it to your own pre-AI tools.
Nine demo tools. Zero new dashboards. The engineer stays in their editor β once the backends expose callable JSON endpoints.
What Changed Since v0.3.0
The comments on this post shaped v0.4.0 more than any roadmap I had. A few people took the time to push back hard, and they were right.
Mustafa ERBAY pointed out that identity propagation comes before write approvals β even read tools leak data if every call reaches the backend through one service identity. v0.4.0 now forwards client identity as X-MCP-Client-Name and X-MCP-Client-Version headers. It's header forwarding, not token-based auth, but it's the first step.
Raju Dandigam reframed audit logging as a safety and debugging requirement, not a usage metric. v0.4.0 now logs every call β session ID, user ID, tool, backend URL, HTTP status, response size. There's a trail to replay when an agent does something confusing.
Scarab Systems ran a diagnostic scan (206 signals, 0 findings) and correctly noted the claim surface was larger than the executable surface. I narrowed the article and the README to match what the repo actually does, and added the "What MCPlex Is Today (and Isn't)" section above. The full list of what changed is in CHANGELOG.md β 11 issues closed, 31 tests added, 75 total passing.
Ryan Mingus pushed back on the "distribution was broken" thesis, and I conceded it in the section above. MCPlex solves interface friction, not demand.
Thanks to everyone who commented. The article is better for it, and the code is too.
What Comes Next
v0.4.0 shipped the safety basics β identity headers, audit logging, rate limiting, schema validation, SSE heartbeat. Here's what's still left for v1.0.0, roughly in priority order.
The big one is permission enforcement. Right now permission: read|write is still metadata β write tools execute immediately, no approval flow. The agent proposes, the human approves, the tool executes. Without that, you can't safely expose anything destructive. This comes after identity propagation, which v0.4.0 started (header forwarding) but which needs to become real token-based auth before it's trustworthy.
Then robustness: config validation that rejects bad YAML at load time instead of silently dropping connectors, full JSON Schema validation on arguments, retries, pagination, and path-param templating so /api/{id} works.
And developer experience: config hot-reload so you can add a connector without restarting, real SSE progress streaming instead of just keepalive, and self-contained integration tests that don't need Docker.
This list is the honest version of what's left β shaped by the commenters I thanked above.
Over to You
I want to hear from people who've lived this.
How many pre-AI internal tools does your team have that already work β tested, in use, with auth and logic baked in β but can't be reached from an AI agent? Those are the tools MCPlex is for. What's sitting in a repo right now that would get a 2nd life if the agent could just call it?
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 β if the call carried the requesting user's identity and the backend could scope results to their team? v0.4.0 forwards identity as headers, but it's not token-based auth yet. Is that enough for your team, or is the lack of real auth a dealbreaker regardless of rate limiting?
I'm serious about the input. The best ideas for v1.0.0 are going to come from people who've watched good tools sit unused because the interface didn't match how engineers actually work in 2026.
Repo: github.com/deghosal-2026/mcplex β issues are open, good first issues are labeled, I'll fix bugs myself. But the real question is the one above β what would you proxy through this, and what's stopping you?













