−25%

no Windows com pagamento anual, até 31/10. Ver planos

EQVPS
Começar

VPS para um gateway MCP: vários servidores MCP atrás de um endereço e um token

Sua equipe usa três servidores MCP auto-hospedados e cada pessoa tem três URLs e três segredos. Coloque-os atrás de um endereço HTTPS num VPS pequeno: o Caddy confere um bearer token, roteia por caminho e mantém os servidores fora da internet. Uma configuração testada.

Servidores MCP auto-hospedados se multiplicam. Primeiro há um para a documentação interna, depois um para o banco de dados, depois alguém escreve um pequeno para o pipeline de deploy. Cada um ganha sua porta, sua URL e seu segredo, e cada pessoa da equipe cola os três na configuração do cliente. Trocar um token significa avisar todo mundo.

Um gateway transforma isso em um endereço e um token. Os clientes se conectam a https://mcp.example.com/docs/mcp, .../db/mcp e assim por diante. O gateway confere o token, envia cada caminho ao servidor certo em localhost, e os servidores nunca ficam expostos. Precisa de muito pouco: um VPS pequeno com IPv4 própria e o Caddy.

Montamos a configuração abaixo num servidor de testes e a conferimos com clientes reais antes de escrever.

O que você precisa

  • Um VPS com IPv4 pública, para os clientes chegarem à 443. O Nano-IP basta, a menos que seus servidores MCP façam trabalho pesado.
  • Um domínio com registro A apontando para o servidor, aqui mcp.example.com.
  • Ubuntu 24.04 ou Debian 12/13. O Caddy está nos repositórios padrão de ambos.

1. Rodar os servidores MCP em localhost

Cada servidor roda como serviço do systemd. Aqui usamos duas vezes o servidor de referência server-everything para o exemplo ficar completo; troque pelos seus. Ele precisa do Node.js 20 ou mais novo:

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

Uma segunda cópia usa a porta 3102. Depois:

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

Uma coisa que notamos nos testes: esse servidor escuta em todas as interfaces, não só em 127.0.0.1, e muitos outros fazem o mesmo. O passo 3 resolve isso.

2. Caddy: conferir o token e depois rotear por caminho

Gere um token e passe ao Caddy por um arquivo de ambiente:

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

O 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

O Caddy obtém sozinho o certificado de mcp.example.com.

O bloco route não é enfeite. Nossa primeira versão tinha a checagem do token e os blocos handle_path lado a lado no nível superior, e requisições sem token passavam direto com 200. O Caddy ordena as diretivas do jeito dele, e handle_path roda antes de respond. Dentro de route, elas rodam na ordem escrita. Se adaptar esta configuração, teste primeiro o caso "sem token".

handle_path remove o prefixo, então /everything/mcp chega ao servidor como /mcp. flush_interval -1 repassa os eventos em streaming assim que chegam.

3. Fechar tudo menos SSH e HTTPS

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

Depois disso, no nosso teste, as portas 3101 e 3102 ficaram inacessíveis de fora do servidor, enquanto 22 e 443 respondiam. A porta 80 fica aberta só para o Caddy obter e renovar certificados e redirecionar para HTTPS.

4. Conferir

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"

A primeira requisição deve imprimir 401 e a segunda 200. Na nossa execução, um token errado e um token cortado receberam 401, e um caminho desconhecido recebeu 404 mesmo com o token certo.

Depois, a partir de um cliente, por exemplo o Claude Code:

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

Os dois servidores mostraram ✔ Connected. O mesmo cliente com um token errado mostrou ✘ Failed to connect. Mais sobre configurar clientes em Conectar o EQVPS MCP ao Claude Code e ao Codex.

Adicionar o próximo servidor

Suba-o numa nova porta de localhost, adicione um bloco handle_path e recarregue o Caddy. A configuração de ninguém muda, exceto por uma URL nova sob o mesmo host e o mesmo token.

Limites que vale conhecer

  • Um token para todos é simples e trocá-lo é um arquivo, mas não dá para revogar uma pessoa sem mudar para todas. Para acesso por usuário, coloque um proxy de autenticação na frente ou dê a cada pessoa sua própria rota e token.
  • O gateway não deixa um servidor seguro. Um servidor MCP que executa comandos de shell continua sendo um shell para quem tiver o token. Mantenha os tokens fora de configurações compartilhadas e do git.
  • Dimensione pelos servidores, não pelo gateway. O próprio Caddy usa algumas dezenas de megabytes. Se um dos seus servidores roda um modelo local ou indexa um repositório grande, é ele quem decide o plano, não o proxy.

Tem só um servidor MCP? VPS para servidores MCP cobre esse caso mais simples. Quer que seus agentes também cuidem do servidor onde vivem? O servidor MCP da EQVPS faz isso (ou a API REST, se preferir scripts), e os planos com IPv4 própria começam em $8 por mês.

FAQ

Por que colocar servidores MCP atrás de um gateway?

Um endereço e um token por equipe em vez de um por servidor, TLS num lugar só, e os servidores escutando apenas em localhost. Adicionar um quarto servidor vira uma mudança de três linhas no gateway, não um segredo novo para todo mundo.

Qual plano eu preciso?

Um plano com IPv4 própria, porque os clientes precisam chegar às portas 80 e 443. O Nano-IP ($8/mês, 2 vCPU, 1 GB) basta para um gateway e alguns servidores MCP leves. Se um servidor faz trabalho pesado, dimensione por ele.

O streaming funciona através do Caddy?

Sim. O Streamable HTTP usa requisições POST comuns mais server-sent events. Defina flush_interval -1 no proxy reverso para que os eventos passem na hora em vez de ficarem no buffer.

Um token compartilhado é seguro o bastante?

Para uma equipe pequena, é um bom começo: o token nunca sai do HTTPS e você o troca mudando um arquivo. Quando precisar de acesso por pessoa ou revogação, coloque uma camada de autenticação de verdade na frente. O token compartilhado é o piso, não o teto.

Dá para rodar isso num plano NAT?

Não como gateway público. Um plano NAT tem uma porta encaminhada para SSH e nada mais de entrada, então os clientes não chegam à 443. Só funciona se todos os clientes estiverem no mesmo servidor.

Comentários

Nenhum comentário ainda. Seja o primeiro.

Deixe um comentário

Os comentários são moderados antes de aparecerem.