−25%

na Windows przy płatności rocznej, do 31.10. Do planów

EQVPS
Zacznij

VPS na bramę MCP: kilka serwerów MCP za jednym adresem i jednym tokenem

Twój zespół używa trzech samodzielnie hostowanych serwerów MCP i każdy ma trzy adresy URL i trzy sekrety. Postaw je za jednym adresem HTTPS na małym VPS: Caddy sprawdza token bearer, kieruje ruch po ścieżce i trzyma same serwery z dala od internetu. Przetestowana konfiguracja.

Samodzielnie hostowane serwery MCP się mnożą. Najpierw jest jeden do wewnętrznej dokumentacji, potem jeden do bazy danych, potem ktoś pisze mały do pipeline'u wdrożeń. Każdy dostaje własny port, własny URL i własny sekret, a każda osoba w zespole wkleja wszystkie trzy do konfiguracji swojego klienta. Zmiana tokena oznacza pisanie do wszystkich.

Brama zamienia to w jeden adres i jeden token. Klienci łączą się z https://mcp.example.com/docs/mcp, .../db/mcp i tak dalej. Brama sprawdza token, wysyła każdą ścieżkę do właściwego serwera na localhost, a same serwery nigdy nie są wystawione. Potrzeba niewiele: małego VPS z własnym IPv4 i Caddy.

Poniższą konfigurację zbudowaliśmy na serwerze testowym i sprawdziliśmy z prawdziwymi klientami, zanim ją opisaliśmy.

Czego potrzebujesz

  • VPS z publicznym IPv4, żeby klienci dotarli do portu 443. Nano-IP wystarczy, chyba że Twoje serwery MCP same wykonują ciężką pracę.
  • Domeny z rekordem A wskazującym na serwer, tu mcp.example.com.
  • Ubuntu 24.04 albo Debiana 12/13. Caddy jest w standardowych repozytoriach obu.

1. Uruchom serwery MCP na localhost

Każdy serwer działa jako usługa systemd. Tu dwa razy używamy serwera referencyjnego server-everything, żeby przykład był kompletny; podstaw własne. Wymaga Node.js 20 lub nowszego:

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

Druga kopia używa portu 3102. Następnie:

systemctl daemon-reload
systemctl enable --now mcp-everything-1 mcp-everything-2

Coś, co zauważyliśmy podczas testów: ten serwer słucha na wszystkich interfejsach, nie tylko na 127.0.0.1, i wiele innych robi tak samo. Krok 3 to załatwia.

2. Caddy: najpierw sprawdź token, potem kieruj po ścieżce

Wygeneruj token i przekaż go Caddy przez plik środowiskowy:

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 sam pobierze certyfikat dla mcp.example.com.

Blok route nie jest ozdobą. Nasza pierwsza wersja miała sprawdzanie tokena i bloki handle_path obok siebie na najwyższym poziomie, a żądania bez tokena przechodziły z kodem 200. Caddy sortuje dyrektywy według własnej kolejności, a handle_path wykonuje się przed respond. Wewnątrz route wykonują się w zapisanej kolejności. Jeśli dostosowujesz tę konfigurację, najpierw przetestuj przypadek „bez tokena”.

handle_path usuwa prefiks, więc /everything/mcp trafia do serwera jako /mcp. flush_interval -1 przepuszcza zdarzenia strumienia od razu, gdy przychodzą.

3. Zamknij wszystko poza SSH i HTTPS

apt install -y ufw
ufw default deny incoming
ufw allow OpenSSH
ufw allow 80,443/tcp
ufw enable

Po tym w naszym teście porty 3101 i 3102 były nieosiągalne spoza serwera, a 22 i 443 odpowiadały. Port 80 zostaje otwarty tylko po to, żeby Caddy mógł pobierać i odnawiać certyfikaty oraz przekierowywać na HTTPS.

4. Sprawdź

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"

Pierwsze żądanie powinno zwrócić 401, a drugie 200. W naszym teście błędny i ucięty token dostały 401, a nieznana ścieżka dostała 404 nawet z poprawnym tokenem.

Następnie z klienta, na przykład z Claude Code:

claude mcp add --transport http everything https://mcp.example.com/everything/mcp \
  --header "Authorization: Bearer $TOKEN"
claude mcp list

Oba serwery pokazały ✔ Connected. Ten sam klient z błędnym tokenem pokazał ✘ Failed to connect. Więcej o konfiguracji klientów w Podłączenie EQVPS MCP do Claude Code i Codex.

Dodanie kolejnego serwera

Uruchom go na nowym porcie localhost, dodaj jeden blok handle_path, przeładuj Caddy. Niczyja konfiguracja się nie zmienia, poza nowym adresem URL pod tym samym hostem i z tym samym tokenem.

Ograniczenia, o których warto wiedzieć

  • Jeden token dla wszystkich jest prosty, a jego zmiana to jeden plik, ale nie da się odebrać dostępu jednej osobie bez zmiany dla wszystkich. Dla dostępu per użytkownik postaw przed bramą proxy uwierzytelniające albo daj każdemu własną ścieżkę i token.
  • Brama nie czyni serwera bezpiecznym. Serwer MCP, który wykonuje polecenia powłoki, nadal jest powłoką dla każdego, kto ma token. Trzymaj tokeny z dala od współdzielonych konfiguracji i od gita.
  • Dobieraj rozmiar pod serwery, nie pod bramę. Sam Caddy zużywa kilkadziesiąt megabajtów. Jeśli któryś z serwerów uruchamia lokalny model albo indeksuje duże repozytorium, to on decyduje o planie, nie proxy.

Masz tylko jeden serwer MCP? VPS na serwery MCP opisuje ten prostszy przypadek. Chcesz, żeby agenci zarządzali też serwerem, na którym działają? Robi to serwer MCP EQVPS (albo REST API, jeśli wolisz skrypty), a plany z własnym IPv4 zaczynają się od $8 miesięcznie.

FAQ

Po co w ogóle stawiać serwery MCP za bramą?

Jeden adres i jeden token na zespół zamiast jednego na serwer, TLS w jednym miejscu, a same serwery słuchają tylko na localhost. Dodanie czwartego serwera to zmiana trzech linijek w bramie, a nie nowy sekret dla wszystkich.

Jakiego planu potrzebuję?

Planu z własnym IPv4, bo klienci muszą dotrzeć do portów 80 i 443. Nano-IP ($8/mies., 2 vCPU, 1 GB) wystarczy na bramę i kilka lekkich serwerów MCP. Jeśli któryś serwer sam wykonuje ciężką pracę, dobierz plan pod niego.

Czy streaming działa przez Caddy?

Tak. Streamable HTTP używa zwykłych żądań POST oraz server-sent events. Ustaw flush_interval -1 w reverse proxy, żeby zdarzenia przechodziły od razu, a nie trafiały do bufora.

Czy jeden wspólny token jest wystarczająco bezpieczny?

Dla małego zespołu to rozsądny start: token nigdy nie opuszcza HTTPS, a zmienia się go edycją jednego pliku. Gdy potrzebujesz dostępu per osoba albo odbierania uprawnień, postaw przed bramą prawdziwą warstwę uwierzytelniania. Wspólny token to podłoga, nie sufit.

Czy da się to uruchomić na planie NAT?

Nie jako publiczną bramę. Plan NAT ma jeden przekierowany port do SSH i nic więcej przychodzącego, więc klienci nie dotrą do 443. Zadziała to tylko wtedy, gdy wszyscy klienci są na tym samym serwerze.

Komentarze

Brak komentarzy. Bądź pierwszy.

Zostaw komentarz

Komentarze są moderowane przed pojawieniem się.