A two-container runtime: a FastMCP application server, and a reverse proxy sitting in front of it that enforces bearer-token auth before traffic reaches the MCP server.
mcp-app/ FastMCP server (Streamable HTTP transport, port 8000, internal-only)
proxy/ Caddy reverse proxy (bearer-token auth, port 8080, exposed)
docker-compose.yml
- mcp-app — a single-file
server.pyusing PEP 723 inline script metadata, run withuv run server.py. It's not published to the host network; onlyproxycan reach it, over themcp-netcompose network. - proxy — Caddy checks the
Authorization: Bearer <token>header againstMCP_PROXY_TOKENand rejects anything else with 401 before reverse-proxying tomcp-app:8000.
cp .env.example .env
# edit .env and set MCP_PROXY_TOKEN to a real value, e.g.:
# openssl rand -hex 32
docker compose up --buildThe MCP endpoint is then available at http://localhost:8080/mcp, and requires:
Authorization: Bearer <MCP_PROXY_TOKEN>
Any request without a matching header gets a 401 from the proxy without ever reaching the FastMCP app.
cd mcp-app
uv run server.pyThis runs the FastMCP server directly (no proxy/auth) on http://localhost:8000/mcp.
- The two example tools (
ping,echo) are placeholders — replace them with real tools inmcp-app/server.py. MCP_PROXY_TOKENis a shared secret for this local/dev setup. For anything beyond local dev, manage it with fnox and swap the Caddy static-token check for something stronger (OAuth, per-client tokens, etc.) before exposing it outside a trusted network.- Lint with
ruff check .from the repo root (config inpyproject.toml).