Egenhostade MCP-servrar förökar sig. Först finns en för den interna dokumentationen, sedan en för databasen, sedan skriver någon en liten för driftsättningspipelinen. Varje server får egen port, egen URL och egen hemlighet, och alla i teamet klistrar in alla tre i sin klientkonfiguration. Att byta en token betyder att meddela alla.
En gateway gör det till en adress och en token. Klienterna ansluter till https://mcp.example.com/docs/mcp, .../db/mcp och så vidare. Gatewayen kontrollerar token, skickar varje sökväg till rätt server på localhost, och själva servrarna exponeras aldrig. Det behövs väldigt lite: en liten VPS med egen IPv4 och Caddy.
Vi byggde upplägget nedan på en testserver och kontrollerade det med riktiga klienter innan vi skrev det här.
Det här behöver du
- En VPS med publik IPv4 så att klienterna når 443. Nano-IP räcker, om inte dina MCP-servrar själva gör tungt arbete.
- En domän med en A-post som pekar på servern, här
mcp.example.com. - Ubuntu 24.04 eller Debian 12/13. Caddy finns i standardförråden för båda.
1. Kör MCP-servrarna på localhost
Varje server körs som en systemd-tjänst. Här använder vi referensservern server-everything två gånger så att exemplet är fristående; byt ut mot dina egna. Den kräver Node.js 20 eller nyare:
mkdir -p /opt/mcp && cd /opt/mcp
npm install @modelcontextprotocol/server-everything
# /etc/systemd/system/mcp-everything-1.service
[Unit]
Description=MCP server everything #1
After=network.target
[Service]
WorkingDirectory=/opt/mcp
Environment=PORT=3101
ExecStart=/opt/mcp/node_modules/.bin/mcp-server-everything streamableHttp
Restart=always
User=nobody
[Install]
WantedBy=multi-user.target
En andra kopia använder port 3102. Sedan:
systemctl daemon-reload
systemctl enable --now mcp-everything-1 mcp-everything-2
En sak vi märkte under testerna: den här servern lyssnar på alla gränssnitt, inte bara på 127.0.0.1, och många andra gör likadant. Steg 3 tar hand om det.
2. Caddy: kontrollera token, routa sedan efter sökväg
Generera en token och ge den till Caddy via en miljöfil:
apt install -y caddy
echo "MCP_GATEWAY_TOKEN=$(openssl rand -hex 32)" > /etc/caddy/gateway.env
chmod 600 /etc/caddy/gateway.env
mkdir -p /etc/systemd/system/caddy.service.d
printf '[Service]\nEnvironmentFile=/etc/caddy/gateway.env\n' > /etc/systemd/system/caddy.service.d/env.conf
systemctl daemon-reload
Caddyfile:
mcp.example.com {
route {
@unauthorized not header Authorization "Bearer {$MCP_GATEWAY_TOKEN}"
respond @unauthorized 401
handle_path /everything/* {
reverse_proxy 127.0.0.1:3101 {
flush_interval -1
}
}
handle_path /tools2/* {
reverse_proxy 127.0.0.1:3102 {
flush_interval -1
}
}
respond 404
}
}
caddy validate --config /etc/caddy/Caddyfile && systemctl restart caddy
Caddy hämtar certifikatet för mcp.example.com själv.
route-blocket är ingen dekoration. Vår första version hade tokenkontrollen och handle_path-blocken bredvid varandra på toppnivå, och anrop utan token gick rakt igenom med 200. Caddy sorterar direktiv i sin egen ordning, och handle_path körs före respond. Inuti route körs de i den ordning de står. Anpassar du konfigurationen, testa fallet ”utan token” först.
handle_path tar bort prefixet, så /everything/mcp når servern som /mcp. flush_interval -1 skickar vidare strömmade händelser när de kommer.
3. Stäng allt utom SSH och HTTPS
apt install -y ufw
ufw default deny incoming
ufw allow OpenSSH
ufw allow 80,443/tcp
ufw enable
Därefter gick port 3101 och 3102 inte att nå utifrån i vårt test, medan 22 och 443 svarade. Port 80 är bara öppen så att Caddy kan hämta och förnya certifikat och omdirigera till HTTPS.
4. Kontrollera
TOKEN=$(cut -d= -f2 /etc/caddy/gateway.env)
INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"1"}}}'
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://mcp.example.com/everything/mcp \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d "$INIT"
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://mcp.example.com/everything/mcp \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d "$INIT"
Det första anropet ska skriva ut 401 och det andra 200. I vår körning fick en felaktig och en avkortad token båda 401, och en okänd sökväg fick 404 även med rätt token.
Sedan från en klient, till exempel Claude Code:
claude mcp add --transport http everything https://mcp.example.com/everything/mcp \
--header "Authorization: Bearer $TOKEN"
claude mcp list
Båda servrarna visade ✔ Connected. Samma klient med fel token visade ✘ Failed to connect. Mer om klientinställningar i Anslut EQVPS MCP till Claude Code och Codex.
Lägga till nästa server
Starta den på en ny localhost-port, lägg till ett handle_path-block och ladda om Caddy. Ingens konfiguration ändras, förutom en ny URL under samma värd och med samma token.
Begränsningar värda att känna till
- En token för alla är enkelt och att byta den är en fil, men du kan inte återkalla en person utan att byta för alla. För åtkomst per användare, lägg en autentiseringsproxy framför eller ge varje person en egen route och token.
- Gatewayen gör inte en server säker. En MCP-server som kör skalkommandon är fortfarande ett skal för den som har token. Håll tokens borta från delade konfigurationer och git.
- Dimensionera efter servrarna, inte gatewayen. Caddy själv använder några tiotal megabyte. Kör en av dina servrar en lokal modell eller indexerar ett stort repo, är det den som avgör planen, inte proxyn.
Bara en MCP-server? VPS för MCP-servrar tar upp det enklare fallet. Vill du att dina agenter också sköter servern de bor på? Det gör EQVPS MCP-server (eller REST API om du hellre skriptar), och planer med egen IPv4 börjar på $8 i månaden.
Kommentarer
Inga kommentarer än. Bli först.