−25%

на річну оплату Windows, до 31.10. До тарифів

EQVPS
Почати

VPS для MCP-шлюзу: кілька MCP-серверів за однією адресою й одним токеном

У команди три власні MCP-сервери, і в кожної людини три адреси й три секрети. Зберіть їх за однією HTTPS-адресою на маленькому VPS: Caddy перевіряє bearer-токен, маршрутизує за шляхом і ховає самі сервери від інтернету. Перевірена конфігурація.

Власні MCP-сервери множаться. Спершу з'являється один для внутрішньої документації, потім для бази даних, потім хтось пише маленький для пайплайна деплою. У кожного свій порт, своя адреса й свій секрет, і кожна людина в команді вставляє всі три в конфіг свого клієнта. Змінити токен — означає написати всім.

Шлюз перетворює це на одну адресу й один токен. Клієнти підключаються до https://mcp.example.com/docs/mcp, .../db/mcp і так далі. Шлюз перевіряє токен, відправляє кожен шлях на потрібний сервер на localhost, а самі сервери ніколи не відкриті назовні. Потрібно зовсім небагато: маленький VPS із власним IPv4 і Caddy.

Конфігурацію нижче ми зібрали на тестовому сервері й перевірили справжніми клієнтами, перш ніж описувати.

Що потрібно

  • VPS із публічним IPv4, щоб клієнти діставалися до 443. Nano-IP вистачить, якщо ваші MCP-сервери самі не виконують важкої роботи.
  • Домен з A-записом на сервер, тут mcp.example.com.
  • Ubuntu 24.04 або Debian 12/13. Caddy є у стандартних репозиторіях обох.

1. Запустити MCP-сервери на localhost

Кожен сервер працює як systemd-сервіс. Тут ми двічі використовуємо еталонний server-everything, щоб приклад був самодостатнім; підставте свої. Потрібен Node.js 20 або новіший:

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

Друга копія використовує порт 3102. Потім:

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

Що ми помітили на тестах: цей сервер слухає всі інтерфейси, а не лише 127.0.0.1, і багато інших поводяться так само. Крок 3 це вирішує.

2. Caddy: спершу перевірка токена, потім маршрут за шляхом

Згенеруйте токен і передайте його Caddy через файл оточення:

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

Сертифікат для mcp.example.com Caddy отримає сам.

Блок route тут не для краси. У першій версії перевірка токена й блоки handle_path стояли поруч на верхньому рівні, і запити без токена проходили з 200. Caddy сортує директиви у власному порядку, і handle_path виконується раніше за respond. Усередині route вони виконуються в написаному порядку. Якщо адаптуєте цю конфігурацію, спершу перевірте випадок «без токена».

handle_path відрізає префікс, тож /everything/mcp доходить до сервера як /mcp. flush_interval -1 пропускає потокові події одразу, щойно вони приходять.

3. Закрити все, крім SSH і HTTPS

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

Після цього на нашому тесті порти 3101 і 3102 ззовні сервера були недосяжні, а 22 і 443 відповідали. Порт 80 лишається відкритим лише для того, щоб Caddy отримував і продовжував сертифікати та перенаправляв на HTTPS.

4. Перевірити

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"

Перший запит має вивести 401, другий — 200. На нашому прогоні неправильний і обрізаний токени отримали 401, а невідомий шлях — 404 навіть із правильним токеном.

Потім із клієнта, наприклад із Claude Code:

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

Обидва сервери показали ✔ Connected. Той самий клієнт із неправильним токеном показав ✘ Failed to connect. Докладніше про налаштування клієнтів — у статті Підключити EQVPS MCP до Claude Code і Codex.

Додати наступний сервер

Запустіть його на новому порту localhost, додайте один блок handle_path, перезавантажте Caddy. Ні в кого конфіг не змінюється, крім нової адреси під тим самим хостом і з тим самим токеном.

Обмеження, про які варто знати

  • Один токен для всіх — це просто, і змінюється він одним файлом, але відкликати доступ в однієї людини, не змінивши його для всіх, не вийде. Для доступу кожного окремо поставте перед шлюзом auth-проксі або дайте кожній людині власний маршрут і токен.
  • Шлюз не робить сервер безпечним. MCP-сервер, що виконує shell-команди, лишається shell'ом для кожного, у кого є токен. Не тримайте токени в спільних конфігах і в git.
  • Розмір обирайте під сервери, а не під шлюз. Сам Caddy займає кілька десятків мегабайт. Якщо один із серверів запускає локальну модель чи індексує великий репозиторій, тариф визначає він, а не проксі.

Лише один MCP-сервер? Простіший випадок розібрано в VPS для MCP-серверів. Хочете, щоб агенти ще й керували сервером, на якому живуть? Це вміє MCP-сервер EQVPS (або REST API, якщо зручніше скриптами), а тарифи з власним IPv4 — від $8 на місяць.

Часті питання

Навіщо взагалі ставити MCP-сервери за шлюз?

Одна адреса й один токен на команду замість окремих для кожного сервера, TLS в одному місці, а самі сервери слухають лише localhost. Додати четвертий сервер — це три рядки в конфігурації шлюзу, а не новий секрет для всіх.

Який тариф потрібен?

Тариф із власним IPv4, бо клієнти мають достукатися до портів 80 і 443. Nano-IP ($8/міс., 2 vCPU, 1 ГБ) вистачить на шлюз і кілька легких MCP-серверів. Якщо сервер сам виконує важку роботу, підбирайте тариф під нього.

Чи працює стримінг через Caddy?

Так. Streamable HTTP використовує звичайні POST-запити плюс server-sent events. Поставте flush_interval -1 на reverse proxy, щоб події передавалися одразу, а не накопичувалися в буфері.

Чи достатньо безпечний один спільний токен?

Для невеликої команди це розумний початок: токен ніколи не виходить за межі HTTPS, а змінюється правкою одного файлу. Коли знадобиться доступ для кожної людини окремо або відкликання, поставте перед шлюзом повноцінну автентифікацію. Спільний токен — це мінімум, а не стеля.

Чи можна зробити це на NAT-тарифі?

Як публічний шлюз — ні. На NAT-тарифі один проброшений порт для SSH і більше нічого вхідного, тож клієнти не дістануться до 443. Працюватиме, лише якщо всі клієнти на тому самому сервері.

Коментарі

Поки немає коментарів. Будьте першим.

Залишити коментар

Коментарі проходять модерацію перед публікацією.