AI infrastructure
MCP server fleet
Eight self-hosted MCP servers, split by domain, giving AI agents scoped, auditable access to real systems, with no tool anywhere that can send a message.
- Python
- FastMCP
- Model Context Protocol
- OAuth 2.1
- Streamable HTTP
- pgvector
- Docker
- Traefik
- Cloudflare Tunnel
Overview
A fleet of eight self-hosted MCP (Model Context Protocol) servers, about 120 tools in total, that let AI agents work on real systems instead of guessing. Each one reaches a single domain: a shared, versioned document store; a business funnel (self-hosted CRM, mail, mailing lists, OCR, a social publishing queue); a personal inbox and calendar; a personal Outlook mailbox; a university account; Home Assistant; read-only host operations; and a local research library with cited search. The same documents are readable from a browser chat, from terminal coding agents and from Telegram bots, so context crosses project boundaries instead of dying at the end of a conversation.
Problem
An agent is only useful if it can reach real data, but the obvious approach, one server with every integration, is both a security and a context problem. Every agent inherits credentials for systems its task has nothing to do with, and a single leaked token exposes everything at once. There are also no off-the-shelf connectors for a private document store or a self-hosted CRM, so the integrations had to be built.
Solution
Servers split by domain rather than by service type, so an agent doing one job gets exactly the tools that job needs and nothing else, and a compromised credential costs one domain instead of all of them. Each server is independently deployed and independently rotatable. Access is authenticated two ways: machine bearer tokens for programmatic clients, and an OAuth flow gated behind a human-typed password for chat connectors, because dynamic client registration is necessarily public. The same pattern extended cleanly when social publishing was added: agents got tools to queue, approve, and hold a post, while the staging and publishing steps run only from a host-side scheduler, so no tool path reaches the platform.
Architecture
Python with FastMCP, served over stateless Streamable HTTP, each server an isolated Docker Compose project reached through a tunnel and a reverse proxy. The document-store server wraps a markdown corpus with optimistic concurrency: reads return a version, writes must present it, and a mismatch is rejected with the current state so the caller can merge, plus a file lock and an audit log of who changed what. The business server wraps a self-hosted CRM, an inbox, a transactional-email provider, and vision OCR behind a single coherent tool surface, so a lead can be followed from web form to mailing list to inbox to CRM without switching servers. Consumers are browser chat connectors (OAuth), a terminal coding agent (user-scope config so no token ever lands in a repo), and Telegram bots, with per-agent tool denial for bots that must not see business or personal data.
Key decisions
Split by domain, not by service, to bound the blast radius. Nothing in the fleet can send a message or publish a post, and that is structural, not a convention: no send tool is implemented, the mail integration can only save drafts for human review, and the underlying command allowlist omits the send path entirely, so even a bug in the server cannot reach it. The bulk-email provider’s send endpoint is deliberately left unexposed for the same reason, and so is the publishing step. Optimistic concurrency on the document store because several agents write to it at once; a version guard turns a silent clobber into a mergeable conflict. Each server keeps its own OAuth token home, since a shared one would race on refresh. Two later servers tested the rule. Microsoft Graph has no draft-only permission, so the Outlook server holds a scope that could also move or delete mail, and the guarantee lives in code instead: its write path allows only creating and editing drafts, and no send, delete or move tool exists. The Home Assistant server is the first that can change the physical world, so its one write tool checks a deny-list first, then an allowlist, and fails closed.
Challenges
The most expensive one was a transport property nobody reads until it bites. The original streaming transport keeps per-client session state on the server, so restarting a server to ship a change orphans every connected client, and clients do not renegotiate: they keep calling against a session the server has forgotten. The failure surfaces as an invalid-parameters error naming the tool, which sends you to debug the tool. One server stayed broken for thirty-five hours behind exactly that misreading, while the same operation through the underlying command line worked fine. The fix is a stateless transport, which has no session to lose, added alongside the old one so migration is per-client and reversible. Beyond that: auth had to be raw ASGI middleware, because conventional framework middleware buffers responses and breaks the stream; the library’s DNS-rebinding protection returns an invalid-host error behind a proxy that reads exactly like an auth failure; and list-returning tools arrive as one content block per item, so first-block parsing truncates results without erroring.
Outcome
A working authenticated substrate for agentic work: shared context that survives between sessions and across tools, business systems reachable end-to-end by an agent, and security properties that hold because the capability was never built rather than because a prompt asks nicely. It is the infrastructure layer under the AI-assisted way the rest of this portfolio gets built. Two pieces grew out of running it daily. The document store now writes the prior version to an audit trail before every change, outside the store so the tools cannot rewrite their own history, and refuses the write if the audit fails; any version can be restored. And a research library for a physics thesis became its own read-only server: local embeddings in PostgreSQL with pgvector, hybrid vector and keyword search, and citations down to the page, with nothing leaving the network.