~/potatohd.orgRU
← All posts
Jun 28, 2026·8 min read

Wiring AI agents into my own infrastructure with MCP

MCPAI toolingself-hosted

One MCP server in the fleet won't boot without a reachable Redis instance. No retry loop, no backoff — the process starts, checks for Redis, and exits the moment it doesn't find one. That turned out to be one of the milder surprises in wiring up 8+ MCP servers, because nearly every one of them shipped assuming a vendor's cloud product sat on the other end of the connection. My project tracker, deploy platform, analytics, observability, and billing all run on my own infrastructure instead, so almost none of these servers worked as installed — each one needed a different URL, a different auth header, sometimes a completely different way of authenticating, before an agent could actually use it.

Diagram: forked AI CLIs go through a self-hosted LLM gateway to MCP servers rewired from cloud OAuth to self-hosted instances
Where it landed: one way in for the agents, and only my own services behind it

#Built for someone's cloud

The pattern repeated almost everywhere: an MCP server ships wired for an OAuth exchange against the vendor's cloud endpoint, with no path to a self-hosted instance anywhere in it. The project tracker was the clearest case. In place of the OAuth flow it needed exactly three things: my instance's API URL, a workspace identifier, and a plain API key on the header — the same key every other integration already uses. Getting to that list took longer than using it: the documentation covers the cloud flow and stays silent on self-hosted setups.

#Standing up my own MCP server

Analytics turned into the longest chase. The vendor's official MCP was a dead end immediately: it only speaks to their cloud, with no field for pointing it elsewhere. The second candidate, an npm package advertising self-hosted support, installed cleanly and contained no runnable binary at all — a library with no server behind it.

The third attempt worked: the same vendor publishes a prebuilt Docker image of their MCP server. The first run came up and exited within seconds, no retry, with every log line pointing at Redis. The dependency is absolute — nothing starts without it, not even in a reduced mode. Pointing it at the Redis already running elsewhere fixed it outright. Auth was the easy part: a personal API key as a bearer token.

#Small things that cost time

Two smaller stories, each eating more time than its size warranted. The deploy platform's CLI can report a failed connection on the very first run, and the instinct is to second-guess the key and the permissions. The real cause is duller: the platform's cold start doesn't finish inside the CLI's health-check timeout. Waiting and retrying clears it, before there's any reason to rewrite the config.

The observability platform's MCP shipped a different kind of surprise. Nearly every other server in this fleet arrives as an npm package — install, run, done. This one shipped as a Go binary instead, the only one in the fleet built that way: a binary matched to the right architecture, an execute permission, a path entry in the agent's config, a different install shape entirely from the rest. Its auth header is easy to get subtly wrong, too — the wrong case or the wrong exact format on the header name is enough for the server to reject the request silently, with nothing in the response pointing at what was actually wrong.

#Two hosts behind one login

Billing was the case where the mistake nearly made itself. The obvious move was pasting the same host used to log into the billing dashboard in a browser straight into the MCP config. That host turned out to be the wrong one. The real API lives on a separate domain, close enough in name to the dashboard's to invite the mix-up, and that's the host the MCP server actually needs along with its own API key on the header. The dashboard's login-gated host has no role in that exchange, since the MCP talks to the API directly and never goes through a browser session at all.

The same shape shows up in more than just billing. A lot of the self-hosted UIs in this fleet sit behind a dedicated SSO gateway that protects the frontend with a login and a session, without necessarily protecting the API sitting behind that same product on its own separate host. None of that is visible from a single browser tab — the dashboard loads, everything looks fine, and the gap only surfaces once an MCP server tries to reach the API directly and finds out the address bar was never pointing at it.

#Wiring the databases

The rest of the databases took direct connections with no MCP-side detours: a columnar analytics database, a cache, and a vector database, each with its own authentication shape and its own client. A handful of connection variables per database, and every one of them worked on the first try.

Postgres, the primary relational database, was the exception — the only place in the whole project where the fix belonged to the infrastructure underneath rather than to any MCP server's configuration. The MCP client connected over direct SSL, and the handshake was failing during protocol negotiation, a stage that completes or fails before Postgres itself ever gets a chance to see the connection, let alone authenticate it. A failure at that layer says nothing about credentials or the database itself — it only means the two ends of the TLS connection couldn't agree on what protocol to speak next. The actual cause sat in the TLS proxy in front of the database: its configuration was missing exactly one protocol name from the list of what it was willing to negotiate on incoming connections. Adding that one name and restarting the proxy was the entire fix — the handshake completed on the very next attempt.

#One gateway in

One more layer sits underneath everything above: a self-hosted, cluster-wide LLM gateway, built on an open-source LLM-routing project and deployed on the same platform as everything else described here, just another service in the same infrastructure with no separate cloud account behind it. The point is that every internal service and every agent reaches language models through that single entry point, rather than each one holding its own set of provider keys.

MCP servers and the gateway sit at different points in the same chain. MCP servers are how an agent reaches my systems — the tracker, the deploy platform, the databases. The gateway is how that same agent reaches the models themselves.

#Locking the agents down

The last piece of this was restricting the coding agents themselves. Two of the open-source AI coding CLI tools I run are forked builds, each compiled to reach exactly one endpoint and one credential, baked in at build time — that internal gateway, nothing else reachable from the binary at all. Every other outbound path is blocked at the network level, by firewall rules that live outside the process entirely, somewhere the tool has no visibility into and therefore no way to touch.

The practical difference is significant. An agent that can physically reach only infrastructure under my own control is a fundamentally smaller attack surface than one with open outbound access to anywhere at all, waiting for a prompt injection or an ordinary bug to point it somewhere it shouldn't go.

// available for hire

Need a website or setup, done right?