−25%

en Windows con pago anual, hasta el 31/10. Ver planes

EQVPS
Empezar

VPS para un gateway MCP: varios servidores MCP tras una dirección y un token

Tu equipo usa tres servidores MCP autoalojados y cada persona tiene tres URL y tres secretos. Ponlos tras una dirección HTTPS en un VPS pequeño: Caddy comprueba un bearer token, enruta por ruta y mantiene los servidores fuera de internet. Una configuración probada.

Los servidores MCP autoalojados se multiplican. Primero hay uno para la documentación interna, luego otro para la base de datos y después alguien escribe uno pequeño para el pipeline de despliegue. Cada uno tiene su puerto, su URL y su secreto, y cada persona del equipo pega los tres en la configuración de su cliente. Rotar un token significa avisar a todo el mundo.

Un gateway lo convierte en una dirección y un token. Los clientes se conectan a https://mcp.example.com/docs/mcp, .../db/mcp y así sucesivamente. El gateway comprueba el token, envía cada ruta al servidor correcto en localhost y los servidores nunca quedan expuestos. Hace falta muy poco: un VPS pequeño con IPv4 propia y Caddy.

Montamos esta configuración en un servidor de pruebas y la comprobamos con clientes reales antes de escribir esto.

Qué necesitas

  • Un VPS con IPv4 pública, para que los clientes lleguen al 443. Nano-IP basta, salvo que tus servidores MCP hagan trabajo pesado.
  • Un dominio con un registro A que apunte al servidor, aquí mcp.example.com.
  • Ubuntu 24.04 o Debian 12/13. Caddy está en los repositorios estándar de ambos.

1. Ejecutar los servidores MCP en localhost

Cada servidor corre como servicio systemd. Aquí usamos dos veces el servidor de referencia server-everything para que el ejemplo sea autocontenido; pon los tuyos. Necesita Node.js 20 o superior:

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

Una segunda copia usa el puerto 3102. Después:

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

Algo que notamos en las pruebas: este servidor escucha en todas las interfaces, no solo en 127.0.0.1, y muchos otros hacen lo mismo. El paso 3 se ocupa de eso.

2. Caddy: comprobar el token y luego enrutar por ruta

Genera un token y pásaselo a Caddy mediante un archivo de entorno:

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

El 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 obtiene solo el certificado de mcp.example.com.

El bloque route no es decoración. Nuestra primera versión tenía la comprobación del token y los bloques handle_path uno al lado del otro en el nivel superior, y las peticiones sin token pasaban con 200. Caddy ordena las directivas a su manera y handle_path se ejecuta antes que respond. Dentro de route se ejecutan en el orden escrito. Si adaptas esta configuración, prueba primero el caso «sin token».

handle_path quita el prefijo, así que /everything/mcp llega al servidor como /mcp. flush_interval -1 deja pasar los eventos en streaming según llegan.

3. Cerrar todo salvo SSH y HTTPS

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

Después de esto, en nuestra prueba los puertos 3101 y 3102 no eran accesibles desde fuera del servidor, mientras que 22 y 443 respondían. El puerto 80 queda abierto solo para que Caddy obtenga y renueve certificados y redirija a HTTPS.

4. Comprobarlo

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"

La primera petición debe imprimir 401 y la segunda 200. En nuestra prueba, un token incorrecto y uno truncado recibieron 401, y una ruta desconocida recibió 404 incluso con el token correcto.

Luego desde un cliente, por ejemplo Claude Code:

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

Ambos servidores mostraron ✔ Connected. El mismo cliente con un token incorrecto mostró ✘ Failed to connect. Más sobre la configuración de clientes en Conectar EQVPS MCP a Claude Code y Codex.

Añadir el siguiente servidor

Arráncalo en un nuevo puerto de localhost, añade un bloque handle_path y recarga Caddy. La configuración de nadie cambia, salvo por una URL nueva bajo el mismo host y el mismo token.

Límites que conviene conocer

  • Un token para todos es sencillo y rotarlo es cambiar un archivo, pero no puedes revocar a una persona sin cambiarlo para todas. Para acceso por usuario, pon delante un proxy de autenticación o da a cada persona su propia ruta y su propio token.
  • El gateway no hace seguro a un servidor. Un servidor MCP que ejecuta comandos de shell sigue siendo una shell para quien tenga el token. Mantén los tokens fuera de configuraciones compartidas y de git.
  • Dimensiona para los servidores, no para el gateway. Caddy usa unas decenas de megabytes. Si uno de tus servidores ejecuta un modelo local o indexa un repositorio grande, es él quien decide el plan, no el proxy.

¿Solo tienes un servidor MCP? VPS para servidores MCP cubre ese caso más simple. ¿Quieres que tus agentes gestionen también el servidor en el que viven? El servidor MCP de EQVPS lo hace (o la API REST, si prefieres scripts), y los planes con IPv4 propia empiezan en $8 al mes.

Preguntas frecuentes

¿Por qué poner los servidores MCP tras un gateway?

Una dirección y un token por equipo en lugar de uno por servidor, TLS en un solo sitio y los servidores escuchando solo en localhost. Añadir un cuarto servidor pasa a ser un cambio de tres líneas en el gateway, no un secreto nuevo para todos.

¿Qué plan necesito?

Uno con IPv4 propia, porque los clientes tienen que llegar a los puertos 80 y 443. Nano-IP ($8/mes, 2 vCPU, 1 GB) basta para un gateway y algunos servidores MCP ligeros. Si un servidor hace trabajo pesado, dimensiona según ese servidor.

¿Funciona el streaming a través de Caddy?

Sí. Streamable HTTP usa peticiones POST normales y server-sent events. Pon flush_interval -1 en el proxy inverso para que los eventos pasen al instante en lugar de quedarse en el búfer.

¿Un token compartido es suficientemente seguro?

Para un equipo pequeño es un buen punto de partida: el token nunca sale de HTTPS y lo rotas cambiando un archivo. Cuando necesites acceso por persona o revocación, pon delante una capa de autenticación de verdad. El token compartido es el suelo, no el techo.

¿Puedo hacerlo en un plan NAT?

No como gateway público. Un plan NAT tiene un puerto reenviado para SSH y nada más de entrada, así que los clientes no llegan al 443. Solo funciona si todos los clientes están en el mismo servidor.

Comentarios

Aún no hay comentarios. Sé el primero.

Deja un comentario

Los comentarios se moderan antes de aparecer.