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.
Commenti
Ancora nessun commento. Sii il primo.