Construire et auto-heberger un serveur MCP sur un VPS

12 min de lecture·Matthieu|

Construisez un serveur MCP en TypeScript, deployez-le sur un VPS avec systemd derriere Nginx et TLS. Connectez Claude Desktop, Claude Code et Cursor a votre serveur auto-heberge.

Tous les tutoriels MCP s'arretent a « lancer en local avec stdio ». Celui-ci couvre le parcours complet : construire un serveur MCP en TypeScript, le deployer sur un VPS derriere Nginx avec TLS, le securiser avec authentification et regles de pare-feu, et connecter des clients MCP via internet.

A la fin, vous aurez un serveur MCP en production accessible depuis Claude Desktop, Claude Code, Cursor ou tout client compatible MCP.

Que construisez-vous ?

Les clients MCP envoient des requetes JSON-RPC via HTTPS. Nginx termine le TLS et proxifie les requetes vers le processus MCP qui tourne sur un port local. Le serveur MCP valide un bearer token, traite la requete et renvoie le resultat.

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'exemple est un serveur de notes. Il expose deux tools (rechercher des notes, creer une note) et une resource (lister toutes les notes). Assez simple pour suivre, assez utile pour etre etendu.

Quel transport utiliser pour un serveur MCP distant ?

Streamable HTTP est le standard actuel de la specification MCP pour les deploiements distants. Il remplace le transport SSE deprecie et fonctionne avec des requetes HTTP POST classiques. Stdio reste le transport pour les serveurs locaux ou le client lance le serveur en sous-processus.

Transport Cas d'usage Compatible distant Support client
stdio Outils locaux, integrations CLI Non Tous les clients
SSE (deprecie) Serveurs distants legacy Oui La plupart (en cours d'abandon)
Streamable HTTP Serveurs distants, production Oui Claude Desktop, Claude Code, Cursor

Streamable HTTP envoie chaque message client en HTTP POST vers un endpoint unique (ex : /mcp). Le serveur repond en application/json pour les reponses simples ou en text/event-stream quand il doit streamer plusieurs messages. Cela fonctionne avec l'infrastructure HTTP standard, y compris les reverse proxies et les load balancers.

Prerequis

  • Un VPS sous Debian 12 ou Ubuntu 24.04 (ce guide utilise Debian 12)
  • Un nom de domaine avec un enregistrement DNS pointant vers votre VPS (ex : mcp.example.com)
  • Node.js 20+ installe sur le VPS
  • Connaissance de base de TypeScript et Linux

Comment configurer un projet de serveur MCP en TypeScript ?

Commencez sur votre machine locale ou directement sur le VPS. Initialisez le projet, installez le SDK MCP et configurez 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

Creez la configuration TypeScript :

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

Enregistrez ce fichier sous tsconfig.json a la racine du projet.

Mettez a jour package.json pour ajouter les scripts de build et de demarrage et definir le type de module :

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

Creez le repertoire source :

mkdir src

Comment implementer des tools MCP avec validation des entrees ?

Un serveur MCP expose des tools, des resources et des prompts aux clients IA. Les tools sont des fonctions que l'IA peut appeler. Les resources sont des donnees en lecture seule que l'IA peut consulter. Nous allons implementer les deux.

Creez src/index.ts avec la configuration de la base de donnees et une fonction factory qui construit un nouveau serveur MCP pour chaque requete. Le Streamable HTTP stateless necessite une nouvelle instance McpServer par requete car le SDK ne permet pas de reconnecter le meme serveur a un transport different.

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",
  });

Enregistrez maintenant les tools dans la factory. Chaque tool utilise Zod pour la validation des entrees. Le SDK valide automatiquement les inputs avant l'execution du handler.

  // --- 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}`,
          },
        ],
      };
    }
  );

Chaque entree est validee par Zod avant d'atteindre le handler. Si un client envoie { query: "" }, le SDK retourne automatiquement une erreur de validation. Pas besoin de parsing manuel.

Comment ajouter des resources a un serveur MCP ?

Les resources donnent aux clients IA un acces en lecture seule aux donnees. Elles sont referencees par URI. Ajoutez une resource qui liste toutes les notes, toujours dans 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;
}

Les clients MCP peuvent decouvrir cette resource et la lire sans appeler un tool. Claude Desktop affiche les resources dans le menu des pieces jointes. Claude Code les expose via les mentions @.

Comment securiser un serveur MCP avec un token d'authentification ?

Un serveur MCP en production necessite une authentification. Sans elle, quiconque decouvre votre endpoint peut appeler vos tools. Utilisez un bearer token verifie dans un middleware Express avant que les requetes n'atteignent le transport MCP.

Generez un token solide :

openssl rand -base64 32

Conservez le resultat. Vous en aurez besoin pour la configuration du serveur et des clients.

Ajoutez le middleware d'authentification et les routes Express dans 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);

Le middleware s'execute avant chaque requete vers /mcp. Il verifie la presence d'un token Bearer dans le header Authorization et rejette les requetes non autorisees avec une erreur JSON-RPC.

Comment configurer le transport Streamable HTTP ?

Ajoutez la configuration du transport et les handlers de routes. Chaque requete cree un nouveau McpServer depuis la factory et une nouvelle instance de 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`);
});

sessionIdGenerator: undefined cree un serveur stateless. Chaque requete est independante, avec sa propre paire McpServer + transport. C'est plus simple a deployer et fonctionne bien derriere un reverse proxy sans session sticky. La connexion a la base de donnees (db) est au scope du module et partagee entre les requetes, donc les donnees persistent entre les appels.

Le serveur ecoute sur 127.0.0.1, pas 0.0.0.0. Il n'accepte que les connexions locales. Nginx sera le seul moyen d'y acceder depuis l'exterieur.

Compilez et testez en local :

npm run build

Verifiez que le build reussit sans erreur.

Comment tester un serveur MCP en local ?

MCP Inspector est l'outil de test officiel. Il se connecte a votre serveur et vous permet d'appeler les tools de facon interactive.

Demarrez votre serveur dans un terminal :

MCP_AUTH_TOKEN=test-token-local npm start

Dans un autre terminal, testez avec 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
  }'

Vous devriez voir une reponse SSE (event: message suivie d'une ligne data:) contenant les capacites du serveur, y compris les objets tools et resources. Cela confirme que le serveur accepte les connexions et repond au handshake d'initialisation MCP.

Testez le 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 reponse devrait contenir "Note created with id: ...". Testez maintenant le tool de recherche :

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
  }'

Vous devriez voir la note que vous venez de creer dans les resultats. Verifiez qu'une requete sans header Authorization retourne une erreur 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}'

Cela devrait afficher 401. Si c'est le cas, votre middleware d'authentification fonctionne.

Vous pouvez aussi utiliser le MCP Inspector pour des tests interactifs avec une interface web :

npx @modelcontextprotocol/inspector

L'Inspector ouvre une interface dans le navigateur ou vous pouvez vous connecter au serveur, parcourir les tools et resources, et tester les appels.

Comment deployer un serveur MCP sur un VPS ?

Transferez votre projet sur le VPS avec rsync, scp, ou poussez vers un depot Git et clonez sur le serveur. Les etapes suivantes supposent que vous etes connecte au VPS.

Creer un utilisateur dedie

Ne lancez jamais de processus applicatifs en root. Creez un utilisateur de service :

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

Installer Node.js

Si Node.js 20+ n'est pas deja installe :

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

La premiere commande telecharge le script d'installation dans un fichier pour que vous puissiez l'inspecter avant de l'executer. Verifiez que la sortie affiche v20.x.x ou superieur pour Node.js et 10.x.x pour npm. Le SDK MCP necessite Node.js 18+ mais la version 20 LTS est recommandee en production.

Configurer le repertoire du projet

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

Verifiez les droits :

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

Tous les fichiers doivent appartenir a mcpserver:mcpserver.

Creer le fichier d'environnement

Stockez le token d'authentification dans un fichier d'environnement restreint :

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

Lisez le token genere et conservez-le en lieu sur. Vous en aurez besoin pour la configuration des clients :

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

Creer le service 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 charge MCP_AUTH_TOKEN depuis le fichier restreint. La section hardening limite les actions du processus. NoNewPrivileges empeche l'escalade de privileges. ProtectSystem=strict rend le systeme de fichiers en lecture seule sauf pour les chemins listes dans ReadWritePaths. ProtectHome bloque l'acces aux repertoires home des utilisateurs.

Activez et demarrez le service :

sudo systemctl enable --now mcp-notes-server

enable fait demarrer le service au boot. --now le lance immediatement.

Verifiez le statut :

sudo systemctl status mcp-notes-server

Vous devriez voir active (running). Si le service a echoue, consultez les logs :

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

Verifiez que le serveur repond en local :

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

Cela devrait retourner {"status":"ok"}.

Comment faire tourner un serveur MCP derriere Nginx avec TLS ?

Nginx termine le TLS et proxifie les requetes vers le serveur MCP. Le serveur MCP ne gere jamais le TLS directement. C'est le schema standard pour les deploiements Node.js en production.

Installer Nginx et Certbot

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

Configurer le server block 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

Remplacez mcp.example.com par votre domaine reel.

Activez le site et obtenez un certificat 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 modifie la configuration pour ajouter les parametres TLS et une redirection HTTP vers HTTPS. Mettez maintenant a jour la configuration pour ajouter le reverse proxy vers 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

Les directives cles pour la compatibilite MCP :

  • proxy_buffering off empeche Nginx de mettre en tampon les reponses SSE. Sans cela, les reponses en streaming restent bloquees jusqu'a la fermeture de la connexion.
  • proxy_cache off garantit qu'aucune reponse dynamique n'est mise en cache.
  • chunked_transfer_encoding off evite les problemes de chunking avec les flux SSE.
  • Connection "" maintient la connexion ouverte sans declencher de comportement d'upgrade WebSocket.
  • proxy_read_timeout 300s autorise les appels de tools a prendre jusqu'a 5 minutes. Le defaut de 60s est trop court pour des operations longues.

Testez et rechargez :

sudo nginx -t
sudo systemctl reload nginx

Verifiez depuis votre machine locale (pas le serveur) :

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

Cela devrait retourner {"status":"ok"}. Si c'est le cas, Nginx proxifie correctement vers votre serveur MCP avec TLS.

Regles de pare-feu

Bloquez l'acces direct au port 3000 depuis l'exterieur. Seul Nginx (sur localhost) doit atteindre le serveur 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

Verifiez que le port 3000 n'est pas accessible depuis l'exterieur :

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

La connexion devrait echouer. Votre serveur MCP n'est accessible que via Nginx sur le port 443.

Comment connecter des clients MCP a un serveur distant ?

Avec le serveur en fonctionnement derriere Nginx avec TLS, configurez vos clients MCP.

Claude Code

Ajoutez le serveur distant via le CLI :

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

Remplacez YOUR_TOKEN_HERE par le token de /etc/mcp-notes-server/env.

Verifiez la connexion :

claude mcp list

Dans Claude Code, tapez /mcp pour verifier le statut du serveur. Le notes-server devrait apparaitre comme connecte avec les tools search_notes et create_note disponibles.

Claude Desktop

Claude Desktop se connecte aux serveurs MCP distants via l'interface Settings. Allez dans Settings > Connectors et ajoutez un nouveau serveur distant avec l'URL https://mcp.example.com/mcp.

Si vous devez passer le bearer token, vous pouvez aussi le configurer via un fichier .mcp.json local ou la commande CLI claude mcp add montree ci-dessus et importer le serveur dans Claude Desktop :

claude mcp add-from-claude-desktop

Cursor

Ouvrez les parametres de Cursor et naviguez vers la configuration MCP. Ajoutez un nouveau serveur dans le fichier mcp.json (ou via l'interface des parametres Cursor) :

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

Cursor tente Streamable HTTP en premier lors de la connexion a une URL, donc aucune configuration de transport supplementaire n'est necessaire.

Verifier la connexion de bout en bout

Depuis n'importe quel client connecte, demandez a l'IA de :

  1. Creer une note : « Cree une note intitulee 'Server Test' avec le contenu 'Connected successfully' »
  2. La rechercher : « Cherche dans mes notes 'Server Test' »

Si les deux operations retournent des resultats, la chaine complete fonctionne : client vers Nginx vers serveur MCP vers SQLite et retour.

Reference des primitives MCP

Primitive Fonction Exemple
Tools Fonctions que l'IA peut appeler avec des entrees structurees search_notes, create_note
Resources Donnees en lecture seule que l'IA peut consulter listing de notes notes://all
Prompts Templates pre-ecrits pour des taches courantes Non utilise dans ce tutoriel

Les tools sont la primitive la plus souvent implementee. Les resources servent a exposer des donnees de reference sans calcul. Les prompts permettent de creer des templates reutilisables qui guident l'IA a travers des workflows specifiques.

Quelque chose ne fonctionne pas ?

Le serveur ne demarre pas : Verifiez le journal pour les erreurs :

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

Causes frequentes : variable d'environnement MCP_AUTH_TOKEN manquante, permissions incorrectes sur notes.db, version de Node.js trop ancienne.

Nginx retourne 502 Bad Gateway : Le serveur MCP ne tourne pas ou n'ecoute pas sur le port 3000. Verifiez :

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

Le client recoit 401 Unauthorized : Le token dans la configuration du client ne correspond pas au token sur le serveur. Verifiez :

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

Les reponses en streaming expirent : Augmentez proxy_read_timeout dans la configuration Nginx. Les 300s par defaut couvrent la plupart des cas, mais des tools longs peuvent necessiter plus.

Erreurs de certificat : Verifiez que le certificat est valide et couvre votre domaine :

sudo certbot certificates

Si le certificat a expire, renouvelez-le :

sudo certbot renew

Erreurs de base de donnees verrouillee : Le mode WAL de SQLite gere bien les lectures concurrentes, mais les ecritures concurrentes peuvent entrer en conflit. Pour les serveurs a fort trafic, envisagez PostgreSQL.

Permission refusee sur notes.db : L'utilisateur mcpserver a besoin d'un acces en ecriture au repertoire de travail pour SQLite. Verifiez :

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

Le fichier doit appartenir a mcpserver:mcpserver. S'il a ete cree par root lors des tests, corrigez :

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

Le client affiche « connection refused » ou « timeout » : Verifiez que votre enregistrement DNS A pointe vers l'IP du VPS et que les ports 80 et 443 sont ouverts dans le pare-feu :

sudo ufw status
dig +short mcp.example.com

La sortie de dig doit afficher l'adresse IP de votre VPS. Si ce n'est pas le cas, mettez a jour vos enregistrements DNS et attendez la propagation.

Prochaines etapes

Votre serveur MCP est en ligne et securise. Pistes pour la suite :

  • Ajouter des tools qui se connectent a vos bases de donnees, API ou systemes de fichiers
  • Implementer OAuth 2.1 au lieu des bearer tokens pour un acces multi-utilisateurs
  • Ajouter du rate limiting dans Express ou Nginx pour proteger contre les abus
  • Mettre en place du monitoring avec journalctl -u mcp-notes-server -f et de l'alerting
  • Deployer plusieurs serveurs MCP derriere la meme instance Nginx sur des chemins differents

La documentation officielle du SDK MCP couvre les patterns avances, y compris les sessions stateful, le reporting de progression et les schemas de sortie structures.


Copyright 2026 Virtua.Cloud. Tous droits reserves. Ce contenu est une creation originale de l'equipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation ecrite est interdite.

Prêt à essayer ?

Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.

Voir les offres VPS