−25%

sur Windows en paiement annuel, jusqu'au 31/10. Voir les offres

EQVPS
Commencer

VPS pour une passerelle MCP : plusieurs serveurs MCP derrière une adresse et un jeton

Votre équipe utilise trois serveurs MCP auto-hébergés et chacun a trois URL et trois secrets. Placez-les derrière une seule adresse HTTPS sur un petit VPS : Caddy vérifie un jeton bearer, route par chemin et garde les serveurs hors d'Internet. Une configuration testée.

Les serveurs MCP auto-hébergés se multiplient. D'abord un pour la documentation interne, puis un pour la base de données, puis quelqu'un en écrit un petit pour le pipeline de déploiement. Chacun a son port, son URL et son secret, et chaque membre de l'équipe colle les trois dans la configuration de son client. Faire tourner un jeton, c'est prévenir tout le monde.

Une passerelle ramène tout cela à une adresse et un jeton. Les clients se connectent à https://mcp.example.com/docs/mcp, .../db/mcp et ainsi de suite. La passerelle vérifie le jeton, envoie chaque chemin au bon serveur sur localhost, et les serveurs eux-mêmes ne sont jamais exposés. Il faut très peu de chose : un petit VPS avec IPv4 propre et Caddy.

Nous avons monté la configuration ci-dessous sur un serveur de test et l'avons vérifiée avec de vrais clients avant de l'écrire.

Ce qu'il vous faut

  • Un VPS avec IPv4 publique, pour que les clients joignent le 443. Nano-IP suffit, sauf si vos serveurs MCP font eux-mêmes un travail lourd.
  • Un domaine avec un enregistrement A pointant vers le serveur, ici mcp.example.com.
  • Ubuntu 24.04 ou Debian 12/13. Caddy est dans les dépôts standard des deux.

1. Faire tourner les serveurs MCP sur localhost

Chaque serveur tourne comme service systemd. Ici, nous utilisons deux fois le serveur de référence server-everything pour que l'exemple soit autonome ; remplacez-le par les vôtres. Il faut Node.js 20 ou plus récent :

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

Une seconde copie utilise le port 3102. Ensuite :

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

Une chose remarquée pendant les tests : ce serveur écoute sur toutes les interfaces, pas seulement sur 127.0.0.1, et beaucoup d'autres font de même. L'étape 3 règle ce point.

2. Caddy : vérifier le jeton, puis router par chemin

Générez un jeton et transmettez-le à Caddy via un fichier d'environnement :

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

Le 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 obtient tout seul le certificat de mcp.example.com.

Le bloc route n'est pas décoratif. Notre première version plaçait la vérification du jeton et les blocs handle_path côte à côte au niveau supérieur, et les requêtes sans jeton passaient avec un 200. Caddy trie les directives dans son propre ordre, et handle_path s'exécute avant respond. Dans route, elles s'exécutent dans l'ordre écrit. Si vous adaptez cette configuration, testez d'abord le cas « sans jeton ».

handle_path retire le préfixe, donc /everything/mcp arrive au serveur sous la forme /mcp. flush_interval -1 transmet les événements en streaming dès leur arrivée.

3. Tout fermer sauf SSH et HTTPS

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

Après cela, dans notre test, les ports 3101 et 3102 étaient injoignables depuis l'extérieur du serveur, tandis que 22 et 443 répondaient. Le port 80 reste ouvert uniquement pour que Caddy obtienne et renouvelle ses certificats et redirige vers HTTPS.

4. Vérifier

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 première requête doit afficher 401 et la seconde 200. Lors de notre essai, un jeton erroné et un jeton tronqué ont tous deux obtenu 401, et un chemin inconnu a obtenu 404 même avec le bon jeton.

Ensuite depuis un client, par exemple Claude Code :

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

Les deux serveurs affichaient ✔ Connected. Le même client avec un mauvais jeton affichait ✘ Failed to connect. Pour la configuration des clients, voir Connecter EQVPS MCP à Claude Code et Codex.

Ajouter le serveur suivant

Lancez-le sur un nouveau port localhost, ajoutez un bloc handle_path, rechargez Caddy. La configuration de personne ne change, hormis une nouvelle URL sous le même hôte et le même jeton.

Les limites à connaître

  • Un jeton pour tous, c'est simple et le faire tourner tient en un fichier, mais on ne peut pas retirer l'accès à une seule personne sans le changer pour tous. Pour un accès par utilisateur, placez un proxy d'authentification devant ou donnez à chacun sa route et son jeton.
  • La passerelle ne rend pas un serveur sûr. Un serveur MCP qui exécute des commandes shell reste un shell pour quiconque détient le jeton. Gardez les jetons hors des configurations partagées et de git.
  • Dimensionnez pour les serveurs, pas pour la passerelle. Caddy n'utilise que quelques dizaines de mégaoctets. Si l'un de vos serveurs fait tourner un modèle local ou indexe un gros dépôt, c'est lui qui décide de l'offre, pas le proxy.

Un seul serveur MCP ? VPS pour serveurs MCP traite ce cas plus simple. Vous voulez que vos agents gèrent aussi le serveur sur lequel ils vivent ? Le serveur MCP d'EQVPS le permet (ou l'API REST si vous préférez les scripts), et les offres avec IPv4 propre commencent à $8 par mois.

FAQ

Pourquoi placer des serveurs MCP derrière une passerelle ?

Une adresse et un jeton par équipe au lieu d'un par serveur, le TLS géré à un seul endroit, et des serveurs qui n'écoutent que sur localhost. Ajouter un quatrième serveur devient une modification de trois lignes sur la passerelle, pas un nouveau secret pour tout le monde.

Quelle offre me faut-il ?

Une offre avec IPv4 propre, car les clients doivent joindre les ports 80 et 443. Nano-IP ($8/mois, 2 vCPU, 1 Go) suffit pour une passerelle et quelques serveurs MCP légers. Si un serveur fait lui-même un travail lourd, dimensionnez pour lui.

Le streaming passe-t-il par Caddy ?

Oui. Le Streamable HTTP utilise des requêtes POST classiques et des server-sent events. Réglez flush_interval -1 sur le reverse proxy pour que les événements passent immédiatement au lieu d'être mis en tampon.

Un jeton partagé est-il assez sûr ?

Pour une petite équipe, c'est un bon début : le jeton ne quitte jamais HTTPS, et on le fait tourner en modifiant un fichier. Quand il vous faut un accès par personne ou une révocation, placez une vraie couche d'authentification devant. Le jeton partagé est le plancher, pas le plafond.

Puis-je le faire sur une offre NAT ?

Pas comme passerelle publique. Une offre NAT n'a qu'un port redirigé pour SSH et rien d'autre en entrée, les clients ne peuvent donc pas joindre le 443. Cela ne fonctionne que si tous les clients sont sur le même serveur.

Commentaires

Pas encore de commentaires. Soyez le premier.

Laisser un commentaire

Les commentaires sont modérés avant leur publication.