−25%

روی پرداخت سالانه Windows، تا ۳۱ اکتبر. مشاهده پلن‌ها

EQVPS
شروع کنید

VPS برای درگاه MCP: چند سرور MCP پشت یک آدرس و یک توکن

تیم شما سه سرور MCP خودمیزبان دارد و هر نفر سه URL و سه رمز دارد. همه را پشت یک آدرس HTTPS روی یک VPS کوچک بگذارید: Caddy توکن bearer را بررسی می‌کند، بر اساس مسیر هدایت می‌کند و خود سرورها را از اینترنت دور نگه می‌دارد. یک پیکربندی آزموده.

سرورهای MCP خودمیزبان زیاد می‌شوند. اول یکی برای مستندات داخلی، بعد یکی برای پایگاه داده، بعد کسی یک سرور کوچک برای پایپ‌لاین استقرار می‌نویسد. هر کدام پورت، URL و رمز خودش را دارد و هر نفر در تیم هر سه را در پیکربندی کلاینتش می‌چسباند. عوض کردن یک توکن یعنی به همه پیام دادن.

درگاه همهٔ این‌ها را به یک آدرس و یک توکن تبدیل می‌کند. کلاینت‌ها به https://mcp.example.com/docs/mcp، .../db/mcp و مانند آن وصل می‌شوند. درگاه توکن را بررسی می‌کند، هر مسیر را به سرور درست روی localhost می‌فرستد و خود سرورها هرگز بیرون در معرض نیستند. خیلی کم لازم است: یک VPS کوچک با IPv4 اختصاصی و Caddy.

پیکربندی زیر را روی یک سرور آزمایشی ساختیم و پیش از نوشتن با کلاینت‌های واقعی بررسی کردیم.

چه لازم دارید

  • یک VPS با IPv4 عمومی تا کلاینت‌ها به ۴۴۳ برسند. اگر سرورهای MCP شما خودشان کار سنگین نمی‌کنند، Nano-IP کافی است.
  • یک دامنه با رکورد A که به سرور اشاره کند، اینجا mcp.example.com.
  • Ubuntu 24.04 یا Debian 12/13. Caddy در مخزن‌های استاندارد هر دو هست.

۱. سرورهای 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، و خیلی‌های دیگر هم همین‌طورند. گام ۳ این را حل می‌کند.

۲. 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

Caddy گواهی mcp.example.com را خودش می‌گیرد.

بلوک route تزئینی نیست. نسخهٔ اول ما بررسی توکن و بلوک‌های handle_path را کنار هم در سطح بالا داشت و درخواست‌های بدون توکن با 200 رد می‌شدند. Caddy دستورها را با ترتیب خودش مرتب می‌کند و handle_path پیش از respond اجرا می‌شود. داخل route به همان ترتیبی که نوشته شده‌اند اجرا می‌شوند. اگر این پیکربندی را تغییر می‌دهید، اول حالت «بدون توکن» را آزمایش کنید.

handle_path پیشوند را حذف می‌کند، پس /everything/mcp به‌صورت /mcp به سرور می‌رسد. flush_interval -1 رویدادهای استریم را همان لحظه که می‌رسند عبور می‌دهد.

۳. همه چیز جز SSH و HTTPS را ببندید

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

بعد از این، در آزمایش ما پورت‌های 3101 و 3102 از بیرون سرور در دسترس نبودند، در حالی که ۲۲ و ۴۴۳ پاسخ می‌دادند. پورت ۸۰ فقط برای این باز می‌ماند که Caddy گواهی بگیرد و تمدید کند و به HTTPS هدایت کند.

۴. بررسی

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 را دوباره بارگذاری کنید. پیکربندی هیچ‌کس تغییر نمی‌کند، جز یک URL تازه زیر همان میزبان و با همان توکن.

محدودیت‌هایی که باید بدانید

  • یک توکن برای همه ساده است و عوض کردنش یک فایل است، اما نمی‌توانید دسترسی یک نفر را بدون تغییر برای همه لغو کنید. برای دسترسی جداگانهٔ کاربران، یک پراکسی احراز هویت جلویش بگذارید یا به هر نفر مسیر و توکن خودش را بدهید.
  • درگاه سرور را امن نمی‌کند. سرور MCP که دستور shell اجرا می‌کند، برای هر کسی که توکن دارد همچنان یک shell است. توکن‌ها را از پیکربندی‌های مشترک و git دور نگه دارید.
  • اندازه را بر اساس سرورها انتخاب کنید، نه درگاه. خود Caddy چند ده مگابایت مصرف می‌کند. اگر یکی از سرورها مدل محلی اجرا می‌کند یا مخزن بزرگی را ایندکس می‌کند، پلن را او تعیین می‌کند، نه پراکسی.

فقط یک سرور MCP دارید؟ VPS برای سرورهای MCP آن حالت ساده‌تر را پوشش می‌دهد. می‌خواهید ایجنت‌ها سروری را هم که رویش زندگی می‌کنند اداره کنند؟ این کار را سرور MCP شرکت EQVPS انجام می‌دهد (یا REST API اگر اسکریپت را ترجیح می‌دهید) و پلن‌های دارای IPv4 اختصاصی از ماهی $8 شروع می‌شوند.

سؤالات متداول

اصلاً چرا سرورهای MCP را پشت درگاه بگذاریم؟

یک آدرس و یک توکن برای کل تیم به‌جای یکی برای هر سرور، TLS در یک جا، و خود سرورها فقط روی localhost گوش می‌دهند. اضافه کردن سرور چهارم یک تغییر سه‌خطی در درگاه می‌شود، نه یک رمز تازه برای همه.

چه پلنی لازم دارم؟

پلنی با IPv4 اختصاصی، چون کلاینت‌ها باید به پورت‌های ۸۰ و ۴۴۳ برسند. Nano-IP (ماهی $8، 2 vCPU، 1 GB) برای یک درگاه و چند سرور MCP سبک کافی است. اگر سروری خودش کار سنگین انجام می‌دهد، اندازه را بر اساس همان انتخاب کنید.

آیا استریم از طریق Caddy کار می‌کند؟

بله. Streamable HTTP از درخواست‌های معمولی POST به‌همراه server-sent events استفاده می‌کند. روی reverse proxy مقدار flush_interval -1 را بگذارید تا رویدادها فوراً عبور کنند و در بافر نمانند.

آیا یک توکن مشترک به اندازهٔ کافی امن است؟

برای تیم کوچک شروع معقولی است: توکن هرگز از HTTPS بیرون نمی‌رود و با تغییر یک فایل عوض می‌شود. وقتی دسترسی جداگانه برای هر نفر یا امکان لغو لازم شد، یک لایهٔ احراز هویت واقعی جلویش بگذارید. توکن مشترک کف است، نه سقف.

روی پلن NAT می‌شود؟

به‌عنوان درگاه عمومی نه. پلن NAT فقط یک پورت فورواردشده برای SSH دارد و ورودی دیگری ندارد، پس کلاینت‌ها به ۴۴۳ نمی‌رسند. فقط وقتی کار می‌کند که همهٔ کلاینت‌ها روی همان سرور باشند.

نظرات

هنوز نظری نیست. اولین نفر باشید.

یک نظر بگذارید

نظرات پیش از نمایش بررسی می‌شوند.