Selvhostede MCP-servere formerer sig. Først er der én til den interne dokumentation, så én til databasen, så skriver nogen en lille til udrulningspipelinen. Hver får sin egen port, sin egen URL og sin egen hemmelighed, og alle i teamet indsætter alle tre i deres klientkonfiguration. At skifte en token betyder at skrive til alle.
En gateway gør det til én adresse og én token. Klienterne forbinder til https://mcp.example.com/docs/mcp, .../db/mcp og så videre. Gatewayen tjekker token, sender hver sti til den rigtige server på localhost, og selve serverne er aldrig eksponeret. Der skal meget lidt til: en lille VPS med egen IPv4 og Caddy.
Vi byggede opsætningen nedenfor på en testserver og tjekkede den med rigtige klienter, før vi skrev det her.
Det skal du bruge
- En VPS med offentlig IPv4, så klienterne kan nå 443. Nano-IP er nok, medmindre dine MCP-servere selv laver tungt arbejde.
- Et domæne med en A-record, der peger på serveren, her
mcp.example.com. - Ubuntu 24.04 eller Debian 12/13. Caddy findes i standardarkiverne for begge.
1. Kør MCP-serverne på localhost
Hver server kører som en systemd-tjeneste. Her bruger vi referenceserveren server-everything to gange, så eksemplet er selvstændigt; brug dine egne. Den kræver Node.js 20 eller nyere:
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 anden kopi bruger port 3102. Derefter:
systemctl daemon-reload
systemctl enable --now mcp-everything-1 mcp-everything-2
Noget vi bemærkede under testen: denne server lytter på alle interfaces, ikke kun 127.0.0.1, og mange andre gør det samme. Trin 3 tager sig af det.
2. Caddy: tjek token, og route derefter efter sti
Generér en token og giv den til 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 henter selv certifikatet til mcp.example.com.
route-blokken er ikke pynt. Vores første version havde tokentjekket og handle_path-blokkene side om side på øverste niveau, og forespørgsler uden token gik lige igennem med 200. Caddy sorterer direktiver i sin egen rækkefølge, og handle_path kører før respond. Inde i route kører de i den skrevne rækkefølge. Hvis du tilpasser konfigurationen, så test tilfældet „uden token“ først.
handle_path fjerner præfikset, så /everything/mcp når serveren som /mcp. flush_interval -1 sender streamede hændelser videre, efterhånden som de kommer.
3. Luk alt undtagen SSH og HTTPS
apt install -y ufw
ufw default deny incoming
ufw allow OpenSSH
ufw allow 80,443/tcp
ufw enable
Derefter kunne port 3101 og 3102 i vores test ikke nås uden for serveren, mens 22 og 443 svarede. Port 80 er kun åben, så Caddy kan hente og forny certifikater og omdirigere til HTTPS.
4. Tjek det
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"
Den første forespørgsel skal skrive 401, og den anden 200. I vores kørsel fik en forkert og en afkortet token begge 401, og en ukendt sti fik 404 selv med den rigtige token.
Derefter fra en klient, for eksempel Claude Code:
claude mcp add --transport http everything https://mcp.example.com/everything/mcp \
--header "Authorization: Bearer $TOKEN"
claude mcp list
Begge servere viste ✔ Connected. Samme klient med forkert token viste ✘ Failed to connect. Mere om klientopsætning i Forbind EQVPS MCP til Claude Code og Codex.
Tilføj den næste server
Start den på en ny localhost-port, tilføj én handle_path-blok og genindlæs Caddy. Ingens konfiguration ændrer sig, ud over en ny URL under samme vært og med samme token.
Begrænsninger værd at kende
- Én token til alle er enkelt, og at skifte den er én fil, men du kan ikke fjerne én persons adgang uden at ændre den for alle. Til adgang pr. bruger, sæt en godkendelsesproxy foran eller giv hver person sin egen route og token.
- Gatewayen gør ikke en server sikker. En MCP-server, der kører shell-kommandoer, er stadig en shell for den, der har token. Hold tokens ude af delte konfigurationer og git.
- Vælg størrelse efter serverne, ikke gatewayen. Caddy selv bruger nogle få titals megabyte. Kører en af dine servere en lokal model eller indekserer et stort repo, er det den, der bestemmer planen, ikke proxyen.
Kun én MCP-server? VPS til MCP-servere dækker det enklere tilfælde. Vil du have, at dine agenter også passer den server, de bor på? Det klarer EQVPS MCP-serveren (eller REST API'et, hvis du hellere vil scripte), og planer med egen IPv4 starter ved $8 om måneden.
Kommentarer
Ingen kommentarer endnu. Vær den første.