직접 호스팅하는 MCP 서버는 계속 늘어납니다. 처음엔 사내 문서용 하나, 다음엔 데이터베이스용 하나, 그러다 누군가 배포 파이프라인용으로 작은 걸 하나 씁니다. 저마다 포트, URL, 비밀이 따로 있고, 팀원 모두가 세 가지를 전부 클라이언트 설정에 붙여 넣습니다. 토큰 하나 바꾸려면 모두에게 연락해야 하죠.
게이트웨이는 이것을 주소 하나와 토큰 하나로 만듭니다. 클라이언트는 https://mcp.example.com/docs/mcp, .../db/mcp 같은 주소로 접속합니다. 게이트웨이가 토큰을 확인하고 각 경로를 localhost의 맞는 서버로 보내며, 서버 자체는 절대 외부에 노출되지 않습니다. 필요한 건 아주 적습니다. 전용 IPv4가 있는 작은 VPS와 Caddy면 됩니다.
아래 구성은 테스트 서버에 꾸린 뒤 실제 클라이언트로 확인하고 나서 글로 옮겼습니다.
준비물
- 클라이언트가 443에 닿을 수 있도록 공인 IPv4가 있는 VPS. MCP 서버 자체가 무거운 일을 하지 않는다면 Nano-IP로 충분합니다.
- 서버를 가리키는 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를 다시 불러오세요. 누구의 설정도 바뀌지 않고, 같은 호스트와 같은 토큰 아래 새 URL 하나가 생길 뿐입니다.
알아 둘 한계
- 모두에게 토큰 하나는 간단하고 교체도 파일 하나면 되지만, 모두의 것을 바꾸지 않고 한 사람만 권한을 회수할 수는 없습니다. 사용자별 접근이 필요하다면 앞단에 인증 프록시를 두거나 사람마다 별도의 경로와 토큰을 주세요.
- 게이트웨이가 서버를 안전하게 만들지는 않습니다. 셸 명령을 실행하는 MCP 서버는 토큰을 가진 누구에게나 여전히 셸입니다. 토큰을 공유 설정이나 git에 두지 마세요.
- 크기는 게이트웨이가 아니라 서버에 맞추세요. Caddy 자체는 수십 MB 정도만 씁니다. 서버 중 하나가 로컬 모델을 돌리거나 큰 저장소를 색인한다면 요금제를 정하는 건 프록시가 아니라 그 서버입니다.
MCP 서버가 하나뿐인가요? MCP 서버용 VPS에서 그 더 단순한 경우를 다룹니다. 에이전트가 자기가 사는 서버까지 관리하게 하고 싶다면 EQVPS MCP 서버가 해 줍니다(스크립트를 선호한다면 REST API). 전용 IPv4 요금제는 월 $8부터입니다.
댓글
아직 댓글이 없습니다. 첫 번째가 되세요.