Creare e ospitare un server MCP personalizzato su un VPS

11 min di lettura·Matthieu|

Crea un server MCP in TypeScript da zero, distribuiscilo su un VPS con systemd e mettilo dietro Nginx con TLS. Collega Claude Desktop, Claude Code e Cursor al tuo server self-hosted.

Ogni tutorial sui server MCP si ferma a "esegui in locale con stdio." Questo copre il percorso completo: creare un server MCP in TypeScript, distribuirlo su un VPS dietro Nginx con TLS, bloccarlo con autenticazione e regole firewall, e collegare i client MCP via internet.

Alla fine avrai un server MCP in produzione raggiungibile da Claude Desktop, Claude Code, Cursor o qualsiasi client compatibile con MCP.

Cosa costruirai?

I client MCP inviano richieste JSON-RPC via HTTPS. Nginx termina il TLS e fa da proxy al processo del server MCP in ascolto su una porta locale. Il server MCP valida un bearer token, elabora la richiesta e restituisce i risultati.

MCP Client (Claude, Cursor, ...)
        |
        | HTTPS (port 443)
        v
   Nginx (TLS termination + reverse proxy)
        |
        | HTTP (port 3000, localhost only)
        v
   MCP Server (Node.js + Express)
        |
        v
   SQLite database

L'esempio funzionante e un server di note. Espone due tool (ricerca note, crea nota) e una risorsa (elenco di tutte le note). Abbastanza semplice da seguire, abbastanza utile da estendere.

Quale transport usare per un server MCP remoto?

Streamable HTTP e lo standard attuale della specifica MCP per i deployment remoti. Ha sostituito il transport SSE (deprecato) e funziona tramite normali richieste HTTP POST. Stdio resta il transport per server solo locali dove il client avvia il server come sottoprocesso.

Transport Caso d'uso Supporto remoto Supporto client
stdio Tool locali, integrazioni CLI No Tutti i client
SSE (deprecato) Server remoti legacy Si La maggior parte dei client (in fase di dismissione)
Streamable HTTP Server remoti, produzione Si Claude Desktop, Claude Code, Cursor

Streamable HTTP invia ogni messaggio del client come HTTP POST a un singolo endpoint (es. /mcp). Il server risponde con application/json per risposte semplici o text/event-stream quando serve inviare piu messaggi. Questo significa che funziona con l'infrastruttura HTTP standard, inclusi reverse proxy e load balancer.

Prerequisiti

  • Un VPS con Debian 12 o Ubuntu 24.04 (questa guida usa Debian 12)
  • Un nome di dominio con DNS che punta al VPS (es. mcp.example.com)
  • Node.js 20+ installato sul VPS
  • Familiarita di base con TypeScript e Linux

Come si configura un progetto MCP server in TypeScript?

Inizia sulla tua macchina locale o direttamente sul VPS. Inizializza il progetto, installa l'SDK MCP e configura TypeScript.

mkdir mcp-notes-server && cd mcp-notes-server
npm init -y
npm install @modelcontextprotocol/sdk zod express better-sqlite3
npm install -D typescript @types/node @types/express @types/better-sqlite3

Crea la configurazione TypeScript:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Salva come tsconfig.json nella root del progetto.

Aggiorna package.json per aggiungere gli script di build e start e impostare il tipo di modulo:

{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  }
}

Crea la directory dei sorgenti:

mkdir src

Come si implementano i tool MCP con validazione degli input?

Un server MCP espone tool, risorse e prompt ai client AI. I tool sono funzioni che l'AI puo chiamare. Le risorse sono dati in sola lettura che l'AI puo consultare. Implementeremo entrambi.

Crea src/index.ts con il setup del database e una funzione factory che costruisce un server MCP nuovo per ogni richiesta. Lo Streamable HTTP stateless richiede una nuova istanza di McpServer per ogni richiesta perche l'SDK non permette di riconnettere lo stesso server a un transport diverso.

import express from "express";
import { randomUUID } from "crypto";
import Database from "better-sqlite3";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

// --- Database setup ---

const db = new Database("notes.db");
db.pragma("journal_mode = WAL");
db.exec(`
  CREATE TABLE IF NOT EXISTS notes (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

// --- MCP Server factory ---
// Each stateless request needs its own McpServer + transport pair.
// The SDK binds a server to a single transport on connect() and
// throws if you try to reuse it. A factory function keeps it clean.

function createServer(): McpServer {
  const server = new McpServer({
    name: "notes-server",
    version: "1.0.0",
  });

Ora registra i tool nella factory. Ogni tool usa Zod per la validazione degli input. L'SDK valida automaticamente gli input prima che il tuo handler venga eseguito.

  // --- Tools ---

  server.tool(
    "search_notes",
    "Search notes by keyword in title or content",
    {
      query: z.string().min(1).describe("Search keyword"),
    },
    async ({ query }) => {
      const stmt = db.prepare(
        "SELECT id, title, content, created_at FROM notes WHERE title LIKE ? OR content LIKE ? ORDER BY created_at DESC LIMIT 20"
      );
      const rows = stmt.all(`%${query}%`, `%${query}%`);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    }
  );

  server.tool(
    "create_note",
    "Create a new note with a title and content",
    {
      title: z.string().min(1).max(200).describe("Note title"),
      content: z.string().min(1).describe("Note content"),
    },
    async ({ title, content }) => {
      const id = randomUUID();
      const stmt = db.prepare(
        "INSERT INTO notes (id, title, content) VALUES (?, ?, ?)"
      );
      stmt.run(id, title, content);
      return {
        content: [
          {
            type: "text" as const,
            text: `Note created with id: ${id}`,
          },
        ],
      };
    }
  );

Ogni input viene validato da Zod prima di raggiungere l'handler. Se un client invia { query: "" }, l'SDK restituisce automaticamente un errore di validazione. Nessun parsing manuale necessario.

Come si aggiungono risorse a un server MCP?

Le risorse danno ai client AI accesso in sola lettura ai dati. Sono referenziate tramite URI. Aggiungi una risorsa che elenca tutte le note, sempre dentro la factory createServer:

  // --- Resources ---

  server.resource(
    "all-notes",
    "notes://all",
    {
      description: "List all notes in the database",
      mimeType: "application/json",
    },
    async (uri) => {
      const rows = db.prepare(
        "SELECT id, title, created_at FROM notes ORDER BY created_at DESC LIMIT 100"
      ).all();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    }
  );

  return server;
}

I client MCP possono scoprire questa risorsa e leggerla senza invocare un tool. Claude Desktop mostra le risorse nel menu degli allegati. Claude Code le espone tramite le menzioni con @.

Come si protegge un server MCP con autenticazione token?

Un server MCP in produzione ha bisogno di autenticazione. Senza, chiunque scopra il tuo endpoint puo invocare i tuoi tool. Usa un bearer token controllato nel middleware Express prima che le richieste raggiungano il transport MCP.

Genera un token sicuro:

openssl rand -base64 32

Salva l'output. Ti servira sia per la configurazione del server che per quella del client.

Aggiungi il middleware di autenticazione e le route Express a src/index.ts:

// --- Auth middleware ---

const AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
if (!AUTH_TOKEN) {
  console.error("MCP_AUTH_TOKEN environment variable is required");
  process.exit(1);
}

function authenticateRequest(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): void {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    res.status(401).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Missing or invalid authorization" },
      id: null,
    });
    return;
  }
  const token = authHeader.slice(7);
  if (token !== AUTH_TOKEN) {
    res.status(403).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Invalid token" },
      id: null,
    });
    return;
  }
  next();
}

// --- Express app ---

const app = express();
app.use(express.json());
app.use("/mcp", authenticateRequest);

Il middleware viene eseguito prima di ogni richiesta a /mcp. Controlla la presenza di un token Bearer nell'header Authorization e rifiuta le richieste non autorizzate con una risposta di errore JSON-RPC.

Come si configura il transport Streamable HTTP?

Aggiungi il setup del transport e gli handler delle route. Ogni richiesta crea un McpServer nuovo dalla factory e una nuova istanza di transport:

// --- Streamable HTTP transport ---

app.post("/mcp", async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  try {
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error("MCP request error:", error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: "2.0",
        error: { code: -32603, message: "Internal server error" },
        id: null,
      });
    }
  }
});

app.get("/mcp", (_req, res) => {
  res.status(405).json({
    jsonrpc: "2.0",
    error: { code: -32000, message: "Method not allowed. Use POST." },
    id: null,
  });
});

app.delete("/mcp", (_req, res) => {
  res.status(405).json({
    jsonrpc: "2.0",
    error: { code: -32000, message: "Method not allowed." },
    id: null,
  });
});

// --- Health check ---

app.get("/health", (_req, res) => {
  res.json({ status: "ok" });
});

// --- Start ---

const PORT = parseInt(process.env.PORT || "3000", 10);
app.listen(PORT, "127.0.0.1", () => {
  console.log(`MCP server listening on http://127.0.0.1:${PORT}/mcp`);
});

Impostare sessionIdGenerator: undefined crea un server stateless. Ogni richiesta e indipendente, con la propria coppia McpServer e transport. Questo semplifica il deployment e funziona dietro un reverse proxy senza bisogno di sticky session. La connessione al database (db) vive a livello di modulo ed e condivisa tra le richieste, quindi i dati persistono tra le chiamate.

Il server si lega a 127.0.0.1, non a 0.0.0.0. Accetta connessioni solo da localhost. Nginx sara l'unico modo per raggiungerlo dall'esterno.

Compila e testa in locale:

npm run build

Verifica che la build riesca senza errori.

Come si testa un server MCP in locale?

MCP Inspector e lo strumento ufficiale di test. Si connette al tuo server e ti permette di invocare i tool in modo interattivo.

Avvia il server in un terminale:

MCP_AUTH_TOKEN=test-token-local npm start

In un altro terminale, testa con curl:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": { "name": "test-client", "version": "1.0.0" }
    },
    "id": 1
  }'

Dovresti vedere una risposta SSE (event: message seguita da una riga data:) contenente le capability del server, inclusi gli oggetti tools e resources. Questo conferma che il server accetta connessioni e risponde all'handshake di inizializzazione MCP.

Testa il tool create_note:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "create_note",
      "arguments": { "title": "Test Note", "content": "Hello from curl" }
    },
    "id": 2
  }'

La risposta dovrebbe contenere "Note created with id: ...". Ora testa il tool di ricerca:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "search_notes",
      "arguments": { "query": "Test" }
    },
    "id": 3
  }'

Dovresti vedere la nota appena creata nei risultati. Verifica che l'invio di una richiesta senza l'header Authorization restituisca un errore 401:

curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

Dovrebbe stampare 401. Se lo fa, il middleware di autenticazione funziona.

Puoi anche usare MCP Inspector per test interattivi con interfaccia web:

npx @modelcontextprotocol/inspector

L'Inspector apre un'interfaccia nel browser dove puoi connetterti al server, esplorare tool e risorse, e testare le invocazioni.

Come si distribuisce un server MCP su un VPS?

Trasferisci il progetto sul VPS usando rsync, scp, oppure pusha su un repo Git e clona sul server. I passi seguenti assumono che sei loggato sul VPS.

Crea un utente dedicato

Non eseguire mai processi applicativi come root. Crea un utente di servizio:

sudo useradd -r -m -s /usr/sbin/nologin mcpserver

Installa Node.js

Se Node.js 20+ non e gia installato:

curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt-get install -y nodejs
node --version
npm --version

Il primo comando scarica lo script di setup in un file cosi puoi ispezionarlo prima di eseguirlo. Verifica che l'output mostri v20.x.x o superiore per Node.js e 10.x.x per npm. L'SDK MCP richiede Node.js 18+ ma la 20 LTS e raccomandata per la produzione.

Configura la directory del progetto

sudo mkdir -p /opt/mcp-notes-server
sudo cp -r /path/to/your/project/* /opt/mcp-notes-server/
cd /opt/mcp-notes-server
sudo npm ci --omit=dev
sudo npm run build
sudo chown -R mcpserver:mcpserver /opt/mcp-notes-server

Verifica la proprieta dei file:

ls -la /opt/mcp-notes-server/

Tutti i file dovrebbero essere di proprieta di mcpserver:mcpserver.

Crea il file di ambiente

Salva il token di autenticazione in un file di ambiente con permessi limitati:

sudo mkdir -p /etc/mcp-notes-server
echo "MCP_AUTH_TOKEN=$(openssl rand -base64 32)" | sudo tee /etc/mcp-notes-server/env > /dev/null
sudo chmod 600 /etc/mcp-notes-server/env
sudo chown mcpserver:mcpserver /etc/mcp-notes-server/env

Leggi il token generato e salvalo in un posto sicuro. Ti servira per la configurazione dei client:

sudo cat /etc/mcp-notes-server/env

Crea il servizio systemd

sudo tee /etc/systemd/system/mcp-notes-server.service > /dev/null << 'EOF'
[Unit]
Description=MCP Notes Server
After=network.target

[Service]
Type=simple
User=mcpserver
Group=mcpserver
WorkingDirectory=/opt/mcp-notes-server
EnvironmentFile=/etc/mcp-notes-server/env
ExecStart=/usr/bin/node build/index.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/mcp-notes-server
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

EnvironmentFile carica MCP_AUTH_TOKEN dal file con permessi limitati. La sezione hardening limita quello che il processo puo fare. NoNewPrivileges impedisce l'escalation dei privilegi. ProtectSystem=strict rende il filesystem in sola lettura tranne i percorsi elencati in ReadWritePaths. ProtectHome blocca l'accesso alle home directory degli utenti.

Abilita e avvia il servizio:

sudo systemctl enable --now mcp-notes-server

enable fa partire il servizio al boot. --now lo avvia immediatamente.

Controlla lo stato:

sudo systemctl status mcp-notes-server

Dovresti vedere active (running). Se il servizio ha fallito, controlla i log:

journalctl -u mcp-notes-server -n 50 --no-pager

Verifica che il server risponda in locale:

TOKEN=$(sudo grep MCP_AUTH_TOKEN /etc/mcp-notes-server/env | cut -d= -f2)
curl -s http://127.0.0.1:3000/health

Dovrebbe restituire {"status":"ok"}.

Come si esegue un server MCP dietro Nginx con TLS?

Nginx termina il TLS e fa da proxy al server MCP. Il server MCP non gestisce mai il TLS direttamente. Questo e il pattern standard per i deployment Node.js in produzione.

Installa Nginx e Certbot

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Configura il server block di Nginx

sudo tee /etc/nginx/sites-available/mcp.example.com.conf > /dev/null << 'NGINX'
server {
    listen 80;
    listen [::]:80;
    server_name mcp.example.com;

    # Certbot will add the redirect to HTTPS
    location / {
        return 444;
    }
}
NGINX

Sostituisci mcp.example.com con il tuo dominio reale.

Abilita il sito e ottieni un certificato TLS:

sudo ln -s /etc/nginx/sites-available/mcp.example.com.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d mcp.example.com

Certbot modifica la configurazione per aggiungere le impostazioni TLS e un redirect da HTTP a HTTPS. Ora aggiorna la configurazione per aggiungere il reverse proxy per l'endpoint MCP:

sudo tee /etc/nginx/sites-available/mcp.example.com.conf > /dev/null << 'NGINX'
server {
    listen 80;
    listen [::]:80;
    server_name mcp.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name mcp.example.com;

    ssl_certificate /etc/letsencrypt/live/mcp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Hide Nginx version
    server_tokens off;

    # MCP endpoint
    location /mcp {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Required for Streamable HTTP / SSE responses
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding off;

        # Keep connection open for streaming responses
        proxy_set_header Connection "";

        # Extended timeouts for long-running tool calls
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;

        # Forward client info
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Pass through Authorization header
        proxy_set_header Authorization $http_authorization;
    }

    # Health check (no auth required)
    location /health {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }

    # Block everything else
    location / {
        return 444;
    }
}
NGINX

Le direttive chiave per la compatibilita MCP:

  • proxy_buffering off impedisce a Nginx di bufferizzare le risposte SSE. Senza, le risposte in streaming restano in attesa fino alla chiusura della connessione.
  • proxy_cache off assicura che nessuna risposta dinamica venga messa in cache.
  • chunked_transfer_encoding off evita problemi di chunking con gli stream di eventi SSE.
  • Connection "" mantiene la connessione attiva senza attivare il comportamento di upgrade WebSocket.
  • proxy_read_timeout 300s permette ai tool che impiegano fino a 5 minuti di completarsi. Il default di 60s e troppo breve per operazioni complesse.

Testa e ricarica:

sudo nginx -t
sudo systemctl reload nginx

Verifica dalla tua macchina locale (non dal server):

curl -s https://mcp.example.com/health

Dovrebbe restituire {"status":"ok"}. Se lo fa, Nginx sta facendo proxy correttamente al tuo server MCP via TLS.

Regole firewall

Blocca l'accesso diretto alla porta 3000 dall'esterno. Solo Nginx (su localhost) deve raggiungere il server MCP:

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Verifica che la porta 3000 non sia accessibile dall'esterno:

# From your local machine, this should time out or be refused:
curl -s --connect-timeout 5 http://YOUR_VPS_IP:3000/health

La connessione dovrebbe fallire. Il tuo server MCP e raggiungibile solo tramite Nginx sulla porta 443.

Come si collegano i client MCP a un server remoto?

Con il server in esecuzione dietro Nginx con TLS, configura i tuoi client MCP per la connessione.

Claude Code

Aggiungi il server remoto usando la CLI:

claude mcp add --transport http \
  --header "Authorization: Bearer YOUR_TOKEN_HERE" \
  notes-server https://mcp.example.com/mcp

Sostituisci YOUR_TOKEN_HERE con il token da /etc/mcp-notes-server/env.

Verifica la connessione:

claude mcp list

Dentro Claude Code, digita /mcp per controllare lo stato del server. Il notes-server dovrebbe risultare connesso con i tool search_notes e create_note disponibili.

Claude Desktop

Claude Desktop si connette ai server MCP remoti tramite l'interfaccia Impostazioni. Vai su Settings > Connectors e aggiungi un nuovo server remoto con l'URL https://mcp.example.com/mcp.

Se devi passare il bearer token, puoi anche configurarlo usando un file .mcp.json locale o il comando CLI claude mcp add mostrato sopra e importare il server in Claude Desktop:

claude mcp add-from-claude-desktop

Cursor

Apri le impostazioni di Cursor e vai alla configurazione MCP. Aggiungi un nuovo server nel file mcp.json (o tramite l'interfaccia impostazioni di Cursor):

{
  "mcpServers": {
    "notes-server": {
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_HERE"
      }
    }
  }
}

Cursor prova prima Streamable HTTP quando si connette a un URL, quindi non serve nessuna configurazione extra per il transport.

Verifica la connessione end-to-end

Da qualsiasi client connesso, chiedi all'AI di:

  1. Creare una nota: "Crea una nota con titolo 'Server Test' e contenuto 'Connesso con successo'"
  2. Cercarla: "Cerca nelle mie note 'Server Test'"

Se entrambe le operazioni restituiscono risultati, l'intera catena funziona: client, Nginx, server MCP, SQLite e ritorno.

Riferimento dei primitivi MCP

Primitivo Scopo Esempio
Tools Funzioni che l'AI puo chiamare con input strutturati search_notes, create_note
Resources Dati in sola lettura che l'AI puo consultare Elenco note notes://all
Prompts Template predefiniti per task comuni Non usati in questo tutorial

I tool sono il primitivo implementato piu di frequente. Le risorse sono utili per esporre dati di riferimento senza computazione. I prompt permettono di creare template riutilizzabili che guidano l'AI attraverso workflow specifici.

Qualcosa non funziona?

Il server non si avvia: Controlla il journal per gli errori:

journalctl -u mcp-notes-server -n 50 --no-pager

Cause comuni: variabile d'ambiente MCP_AUTH_TOKEN mancante, permessi errati su notes.db, versione di Node.js troppo vecchia.

Nginx restituisce 502 Bad Gateway: Il server MCP non e in esecuzione o non e in ascolto sulla porta 3000. Verifica:

sudo systemctl status mcp-notes-server
curl -s http://127.0.0.1:3000/health

Il client riceve 401 Unauthorized: Il token nella configurazione del client non corrisponde al token sul server. Ricontrolla:

sudo cat /etc/mcp-notes-server/env

Le risposte in streaming vanno in timeout: Aumenta proxy_read_timeout nella configurazione di Nginx. Il default di 300s copre la maggior parte dei casi, ma tool di lunga durata potrebbero avere bisogno di piu tempo.

Errori del certificato: Verifica che il certificato sia valido e copra il tuo dominio:

sudo certbot certificates

Se il certificato e scaduto, rinnovalo:

sudo certbot renew

Errori di database bloccato: La modalita WAL di SQLite gestisce bene le letture concorrenti, ma le scritture concorrenti possono generare conflitti. Per server ad alto traffico, considera il passaggio a PostgreSQL.

Permesso negato su notes.db: L'utente mcpserver ha bisogno di accesso in scrittura alla directory di lavoro per SQLite. Controlla:

ls -la /opt/mcp-notes-server/notes.db

Il file dovrebbe essere di proprieta di mcpserver:mcpserver. Se e stato creato da root durante i test, correggi:

sudo chown mcpserver:mcpserver /opt/mcp-notes-server/notes.db
sudo chmod 640 /opt/mcp-notes-server/notes.db

Il client mostra "connection refused" o "timeout": Verifica che il record DNS A punti all'IP del VPS e che le porte 80 e 443 siano aperte nel firewall:

sudo ufw status
dig +short mcp.example.com

L'output di dig dovrebbe mostrare l'indirizzo IP del tuo VPS. Se non lo fa, aggiorna i record DNS e attendi la propagazione.

Prossimi passi

Il tuo server MCP e attivo e blindato. Dove andare da qui:

  • Aggiungi altri tool che si collegano ai tuoi database, API o file system
  • Implementa OAuth 2.1 al posto dei bearer token per l'accesso multi-utente
  • Aggiungi rate limiting in Express o Nginx per proteggerti dagli abusi
  • Configura il monitoraggio con journalctl -u mcp-notes-server -f e gli alert
  • Distribuisci piu server MCP dietro la stessa istanza Nginx su percorsi diversi

La documentazione ufficiale dell'SDK MCP copre pattern avanzati incluse sessioni stateful, progress reporting e schemi di output strutturati.


Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto e un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta e vietata.

Pronto a provare?

Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.

Vedi piani VPS