−25%

su Windows con pagamento annuale, fino al 31/10. Vai ai piani

EQVPS
Inizia

VPS per un gateway MCP: più server MCP dietro un indirizzo e un token

Il tuo team usa tre server MCP self-hosted e ognuno ha tre URL e tre segreti. Mettili dietro un unico indirizzo HTTPS su un piccolo VPS: Caddy controlla un bearer token, instrada per percorso e tiene i server fuori da internet. Una configurazione testata.

I server MCP self-hosted si moltiplicano. Prima ce n'è uno per la documentazione interna, poi uno per il database, poi qualcuno ne scrive uno piccolo per la pipeline di deploy. Ognuno ha la sua porta, il suo URL e il suo segreto, e ogni persona del team li incolla tutti e tre nella configurazione del client. Ruotare un token significa avvisare tutti.

Un gateway trasforma tutto questo in un indirizzo e un token. I client si collegano a https://mcp.example.com/docs/mcp, .../db/mcp e così via. Il gateway controlla il token, manda ogni percorso al server giusto su localhost, e i server non sono mai esposti. Serve pochissimo: un piccolo VPS con IPv4 propria e Caddy.

Abbiamo costruito la configurazione qui sotto su un server di test e l'abbiamo verificata con client reali prima di scriverla.

Cosa ti serve

  • Un VPS con IPv4 pubblica, così i client raggiungono la 443. Nano-IP basta, a meno che i tuoi server MCP non facciano lavoro pesante.
  • Un dominio con un record A che punta al server, qui mcp.example.com.
  • Ubuntu 24.04 o Debian 12/13. Caddy è nei repository standard di entrambi.

1. Eseguire i server MCP su localhost

Ogni server gira come servizio systemd. Qui usiamo due volte il server di riferimento server-everything perché l'esempio sia autosufficiente; sostituiscilo con i tuoi. Richiede Node.js 20 o superiore:

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 seconda copia usa la porta 3102. Poi:

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

Una cosa notata durante i test: questo server ascolta su tutte le interfacce, non solo su 127.0.0.1, e molti altri fanno lo stesso. Il passo 3 se ne occupa.

2. Caddy: controllare il token, poi instradare per percorso

Genera un token e passalo a Caddy tramite un file di 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

Il 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 ottiene da solo il certificato per mcp.example.com.

Il blocco route non è decorativo. La nostra prima versione aveva il controllo del token e i blocchi handle_path affiancati al livello superiore, e le richieste senza token passavano con 200. Caddy ordena le direttive secondo il proprio ordine, e handle_path viene eseguito prima di respond. Dentro route vengono eseguite nell'ordine scritto. Se adatti questa configurazione, prova prima il caso «senza token».

handle_path rimuove il prefisso, quindi /everything/mcp arriva al server come /mcp. flush_interval -1 fa passare gli eventi in streaming appena arrivano.

3. Chiudere tutto tranne SSH e HTTPS

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

Dopo questo, nel nostro test le porte 3101 e 3102 erano irraggiungibili dall'esterno del server, mentre 22 e 443 rispondevano. La porta 80 resta aperta solo perché Caddy possa ottenere e rinnovare i certificati e reindirizzare su HTTPS.

4. Verificare

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 prima richiesta deve stampare 401 e la seconda 200. Nella nostra prova, un token sbagliato e uno troncato hanno ricevuto entrambi 401, e un percorso sconosciuto ha ricevuto 404 anche con il token giusto.

Poi da un client, ad esempio Claude Code:

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

Entrambi i server mostravano ✔ Connected. Lo stesso client con un token sbagliato mostrava ✘ Failed to connect. Altro sulla configurazione dei client in Collegare EQVPS MCP a Claude Code e Codex.

Aggiungere il server successivo

Avvialo su una nuova porta di localhost, aggiungi un blocco handle_path, ricarica Caddy. La configurazione di nessuno cambia, tranne per un nuovo URL sotto lo stesso host e con lo stesso token.

Limiti da conoscere

  • Un token per tutti è semplice e ruotarlo è un file, ma non puoi revocare una persona senza cambiarlo per tutti. Per l'accesso per utente, metti davanti un proxy di autenticazione o dai a ciascuno la propria route e il proprio token.
  • Il gateway non rende sicuro un server. Un server MCP che esegue comandi shell resta una shell per chiunque abbia il token. Tieni i token fuori dalle configurazioni condivise e da git.
  • Dimensiona per i server, non per il gateway. Caddy usa qualche decina di megabyte. Se uno dei tuoi server esegue un modello locale o indicizza un grande repository, è lui a decidere il piano, non il proxy.

Hai un solo server MCP? VPS per server MCP copre quel caso più semplice. Vuoi che i tuoi agenti gestiscano anche il server su cui vivono? Lo fa il server MCP di EQVPS (o l'API REST, se preferisci gli script), e i piani con IPv4 propria partono da $8 al mese.

FAQ

Perché mettere i server MCP dietro un gateway?

Un indirizzo e un token per team invece di uno per server, TLS gestito in un solo punto e server in ascolto solo su localhost. Aggiungere un quarto server diventa una modifica di tre righe sul gateway, non un nuovo segreto per tutti.

Quale piano mi serve?

Un piano con IPv4 propria, perché i client devono raggiungere le porte 80 e 443. Nano-IP ($8/mese, 2 vCPU, 1 GB) basta per un gateway e qualche server MCP leggero. Se un server fa lavoro pesante, dimensiona in base a quello.

Lo streaming passa attraverso Caddy?

Sì. Streamable HTTP usa normali richieste POST più server-sent events. Imposta flush_interval -1 sul reverse proxy così gli eventi passano subito invece di restare nel buffer.

Un token condiviso è abbastanza sicuro?

Per un piccolo team è un buon inizio: il token non esce mai da HTTPS e lo ruoti cambiando un file. Quando ti servono accesso per persona o revoca, metti davanti un vero livello di autenticazione. Il token condiviso è il pavimento, non il soffitto.

Posso farlo su un piano NAT?

Non come gateway pubblico. Un piano NAT ha una porta inoltrata per SSH e nient'altro in ingresso, quindi i client non raggiungono la 443. Funziona solo se tutti i client stanno sullo stesso server.

Commenti

Ancora nessun commento. Sii il primo.

Lascia un commento

I commenti sono moderati prima di comparire.