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.
Comentarios
Aún no hay comentarios. Sé el primero.