−25%

cho Windows trả theo năm, đến 31/10. Xem các gói

EQVPS
Bắt đầu

VPS cho MCP gateway: nhiều máy chủ MCP sau một địa chỉ và một token

Nhóm của bạn dùng ba máy chủ MCP tự host và mỗi người có ba URL, ba bí mật. Đặt chúng sau một địa chỉ HTTPS trên một VPS nhỏ: Caddy kiểm tra bearer token, định tuyến theo đường dẫn và giữ chính các máy chủ tránh xa internet. Một cấu hình đã được kiểm thử.

Máy chủ MCP tự host ngày một nhiều. Đầu tiên có một cái cho tài liệu nội bộ, rồi một cái cho cơ sở dữ liệu, rồi ai đó viết một cái nhỏ cho pipeline triển khai. Mỗi cái có cổng, URL và bí mật riêng, và mọi người trong nhóm dán cả ba vào cấu hình client của mình. Đổi một token nghĩa là phải nhắn cho tất cả.

Gateway biến tất cả thành một địa chỉ và một token. Client kết nối tới https://mcp.example.com/docs/mcp, .../db/mcp và tương tự. Gateway kiểm tra token, gửi mỗi đường dẫn tới đúng máy chủ trên localhost, và bản thân các máy chủ không bao giờ lộ ra ngoài. Cần rất ít: một VPS nhỏ có IPv4 riêng và Caddy.

Chúng tôi dựng cấu hình dưới đây trên một máy chủ thử nghiệm và kiểm tra bằng client thật trước khi viết.

Bạn cần gì

  • Một VPS có IPv4 công khai để client tới được 443. Nano-IP là đủ, trừ khi máy chủ MCP của bạn tự làm việc nặng.
  • Một tên miền có bản ghi A trỏ tới máy chủ, ở đây là mcp.example.com.
  • Ubuntu 24.04 hoặc Debian 12/13. Caddy có sẵn trong kho chuẩn của cả hai.

1. Chạy các máy chủ MCP trên localhost

Mỗi máy chủ chạy như một dịch vụ systemd. Ở đây chúng tôi dùng máy chủ mẫu server-everything hai lần để ví dụ tự đủ; hãy thay bằng máy chủ của bạn. Nó cần Node.js 20 trở lên:

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

Bản thứ hai dùng cổng 3102. Sau đó:

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

Một điều chúng tôi nhận thấy khi thử: máy chủ này lắng nghe trên mọi giao diện, không chỉ 127.0.0.1, và nhiều máy chủ khác cũng vậy. Bước 3 xử lý việc đó.

2. Caddy: kiểm tra token trước, rồi định tuyến theo đường dẫn

Tạo token và đưa cho Caddy qua một tệp môi trường:

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 tự lấy chứng chỉ cho mcp.example.com.

Khối route không phải để trang trí. Phiên bản đầu của chúng tôi đặt phần kiểm tra token và các khối handle_path cạnh nhau ở cấp cao nhất, và yêu cầu không có token đi thẳng qua với 200. Caddy sắp xếp các chỉ thị theo thứ tự riêng, và handle_path chạy trước respond. Bên trong route, chúng chạy theo thứ tự đã viết. Nếu bạn chỉnh cấu hình này, hãy thử trường hợp “không có token” trước tiên.

handle_path bỏ tiền tố, nên /everything/mcp tới máy chủ dưới dạng /mcp. flush_interval -1 chuyển các sự kiện streaming ngay khi chúng tới.

3. Đóng mọi thứ trừ SSH và HTTPS

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

Sau đó, trong thử nghiệm của chúng tôi, cổng 3101 và 3102 không truy cập được từ bên ngoài máy chủ, còn 22 và 443 vẫn trả lời. Cổng 80 chỉ mở để Caddy lấy và gia hạn chứng chỉ, cũng như chuyển hướng sang HTTPS.

4. Kiểm tra

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"

Yêu cầu đầu phải in ra 401 và yêu cầu thứ hai in ra 200. Trong lần chạy của chúng tôi, token sai và token bị cắt đều nhận 401, còn đường dẫn lạ nhận 404 ngay cả với token đúng.

Rồi từ một client, ví dụ Claude Code:

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

Cả hai máy chủ đều hiện ✔ Connected. Cùng client đó với token sai hiện ✘ Failed to connect. Thêm về cài đặt client trong Kết nối EQVPS MCP với Claude Code và Codex.

Thêm máy chủ tiếp theo

Khởi động nó trên một cổng localhost mới, thêm một khối handle_path, tải lại Caddy. Cấu hình của không ai thay đổi, ngoài một URL mới dưới cùng host với cùng token.

Những giới hạn nên biết

  • Một token cho mọi người đơn giản và đổi chỉ cần một tệp, nhưng bạn không thể thu hồi quyền của một người mà không đổi cho tất cả. Để có quyền theo từng người dùng, đặt một proxy xác thực phía trước hoặc cấp cho mỗi người route và token riêng.
  • Gateway không làm máy chủ an toàn. Một máy chủ MCP chạy lệnh shell vẫn là một shell với bất kỳ ai giữ token. Đừng để token trong cấu hình dùng chung hay trong git.
  • Chọn cấu hình theo máy chủ, không theo gateway. Bản thân Caddy chỉ dùng vài chục megabyte. Nếu một máy chủ chạy mô hình cục bộ hoặc lập chỉ mục một kho mã lớn, chính nó quyết định gói, không phải proxy.

Chỉ có một máy chủ MCP? VPS cho máy chủ MCP nói về trường hợp đơn giản hơn đó. Muốn tác tử của bạn quản lý luôn máy chủ mà chúng đang sống? Máy chủ MCP của EQVPS làm được (hoặc REST API nếu bạn thích viết script), và các gói có IPv4 riêng bắt đầu từ $8 mỗi tháng.

FAQ

Vì sao nên đặt máy chủ MCP sau một gateway?

Một địa chỉ và một token cho cả nhóm thay vì mỗi máy chủ một bộ, TLS ở một chỗ, và bản thân các máy chủ chỉ lắng nghe trên localhost. Thêm máy chủ thứ tư chỉ là sửa ba dòng trên gateway, không phải một bí mật mới cho mọi người.

Tôi cần gói nào?

Gói có IPv4 riêng, vì client phải tới được cổng 80 và 443. Nano-IP ($8/tháng, 2 vCPU, 1 GB) đủ cho gateway và vài máy chủ MCP nhẹ. Nếu một máy chủ tự làm việc nặng, hãy chọn cấu hình theo máy chủ đó.

Streaming có chạy qua Caddy không?

Có. Streamable HTTP dùng các yêu cầu POST thông thường cộng server-sent events. Đặt flush_interval -1 ở reverse proxy để sự kiện được chuyển ngay thay vì bị giữ trong bộ đệm.

Một token dùng chung có đủ an toàn không?

Với nhóm nhỏ, đó là khởi đầu hợp lý: token không bao giờ ra khỏi HTTPS và bạn đổi nó bằng cách sửa một tệp. Khi cần quyền truy cập theo từng người hoặc thu hồi, hãy đặt một lớp xác thực thật sự phía trước. Token dùng chung là mức sàn, không phải mức trần.

Có chạy được trên gói NAT không?

Không phải dưới dạng gateway công khai. Gói NAT chỉ có một cổng chuyển tiếp cho SSH và không có gì khác đi vào, nên client không tới được 443. Chỉ chạy được nếu mọi client nằm trên cùng máy chủ.

Bình luận

Chưa có bình luận nào. Hãy là người đầu tiên.

Để lại bình luận

Bình luận được kiểm duyệt trước khi hiển thị.