Een eigen MCP-server bouwen en hosten op een VPS

10 min leestijd·Matthieu|

Bouw een TypeScript MCP-server vanaf nul, deploy deze op een VPS met systemd en draai hem achter Nginx met TLS. Verbind Claude Desktop, Claude Code en Cursor met je zelfgehoste server.

Elke MCP-servertutorial stopt bij "draai lokaal met stdio." Deze gaat het volledige pad af: bouw een TypeScript MCP-server, deploy op een VPS achter Nginx met TLS, beveilig met authenticatie en firewallregels, en verbind MCP-clients via het internet.

Aan het einde heb je een productie-MCP-server die Claude Desktop, Claude Code, Cursor of elke MCP-compatibele client op afstand kan bereiken.

Wat ga je bouwen?

MCP-clients sturen JSON-RPC-verzoeken over HTTPS. Nginx termineert TLS en proxied verzoeken naar je MCP-serverproces op een lokale poort. De MCP-server valideert een bearer token, verwerkt het verzoek en stuurt resultaten terug.

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

Het werkende voorbeeld is een notitieserver. Deze stelt twee tools beschikbaar (notities zoeken, notitie aanmaken) en een resource (alle notities weergeven). Simpel genoeg om te volgen, nuttig genoeg om uit te breiden.

Welk transport gebruik je voor een remote MCP-server?

Streamable HTTP is de huidige MCP-specificatiestandaard voor remote deployments. Het vervangt het verouderde SSE-transport en werkt via gewone HTTP POST-verzoeken. Stdio blijft het transport voor lokale servers waar de client het serverproces als subprocess start.

Transport Gebruik Remote mogelijk Clientondersteuning
stdio Lokale tools, CLI-integraties Nee Alle clients
SSE (verouderd) Legacy remote servers Ja De meeste clients (wordt uitgefaseerd)
Streamable HTTP Remote servers, productie Ja Claude Desktop, Claude Code, Cursor

Streamable HTTP stuurt elk clientbericht als HTTP POST naar een enkel endpoint (bijv. /mcp). De server antwoordt met application/json voor eenvoudige antwoorden of text/event-stream wanneer meerdere berichten teruggestuurd moeten worden. Dit betekent dat het werkt met standaard HTTP-infrastructuur, inclusief reverse proxies en load balancers.

Vereisten

  • Een VPS met Debian 12 of Ubuntu 24.04 (deze handleiding gebruikt Debian 12)
  • Een domeinnaam met DNS gericht op je VPS (bijv. mcp.example.com)
  • Node.js 20+ geinstalleerd op de VPS
  • Basiskennis van TypeScript en Linux

Hoe zet je een TypeScript MCP-serverproject op?

Begin op je lokale machine of direct op de VPS. Initialiseer het project, installeer de MCP SDK en stel TypeScript in.

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

Maak de TypeScript-configuratie:

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

Sla dit op als tsconfig.json in de project-root.

Werk package.json bij om de build- en startscripts toe te voegen en het moduletype in te stellen:

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

Maak de broncodemap aan:

mkdir src

Hoe implementeer je MCP-tools met inputvalidatie?

Een MCP-server stelt tools, resources en prompts beschikbaar aan AI-clients. Tools zijn functies die de AI kan aanroepen. Resources zijn alleen-lezen data waar de AI naar kan verwijzen. We implementeren beide.

Maak src/index.ts aan met de database-setup en een factory-functie die voor elk verzoek een nieuwe MCP-server bouwt. Stateless Streamable HTTP vereist een nieuwe McpServer-instantie per verzoek, omdat de SDK niet toestaat dezelfde server aan een ander transport te koppelen.

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

Registreer nu de tools in de factory. Elke tool gebruikt Zod voor inputvalidatie. De SDK valideert inputs automatisch voordat je handler wordt aangeroepen.

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

Elke input wordt door Zod gevalideerd voordat deze de handler bereikt. Als een client { query: "" } stuurt, retourneert de SDK automatisch een validatiefout. Handmatig parsen is niet nodig.

Hoe voeg je resources toe aan een MCP-server?

Resources geven AI-clients alleen-lezen toegang tot data. Ze worden aangesproken via URI. Voeg een resource toe die alle notities weergeeft, nog steeds in de createServer-factory:

  // --- 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;
}

MCP-clients kunnen deze resource ontdekken en lezen zonder een tool aan te roepen. Claude Desktop toont resources in het bijlagemenu. Claude Code stelt ze beschikbaar via @-vermeldingen.

Hoe beveilig je een MCP-server met tokenauthenticatie?

Een productie-MCP-server heeft authenticatie nodig. Zonder authenticatie kan iedereen die je endpoint ontdekt je tools aanroepen. Gebruik een bearer token dat gecontroleerd wordt in Express-middleware voordat verzoeken het MCP-transport bereiken.

Genereer een sterk token:

openssl rand -base64 32

Bewaar de output. Je hebt het nodig voor zowel de serverconfiguratie als de clientconfiguratie.

Voeg de authenticatie-middleware en Express-routes toe aan 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);

De middleware draait voor elk verzoek naar /mcp. Het controleert op een Bearer-token in de Authorization-header en weigert ongeautoriseerde verzoeken met een JSON-RPC-foutrespons.

Hoe stel je Streamable HTTP-transport in?

Voeg de transport-setup en route-handlers toe. Elk verzoek maakt een nieuwe McpServer via de factory en een nieuwe transport-instantie:

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

Het instellen van sessionIdGenerator: undefined maakt een stateless server. Elk verzoek is onafhankelijk, met een eigen McpServer- en transport-paar. Dit is eenvoudiger te deployen en werkt goed achter een reverse proxy zonder sticky sessions. De databaseverbinding (db) leeft op module-scope en wordt gedeeld tussen verzoeken, zodat data behouden blijft tussen aanroepen.

De server bindt aan 127.0.0.1, niet aan 0.0.0.0. Het accepteert alleen verbindingen van localhost. Nginx is de enige manier om de server van buitenaf te bereiken.

Bouw en test lokaal:

npm run build

Controleer dat de build slaagt zonder fouten.

Hoe test je een MCP-server lokaal?

De MCP Inspector is de officiele testtool. Het verbindt met je server en laat je tools interactief aanroepen.

Start je server in een terminal:

MCP_AUTH_TOKEN=test-token-local npm start

Test in een andere terminal met 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
  }'

Je zou een SSE-respons moeten zien (event: message gevolgd door een data:-regel) met de capabilities van de server, inclusief de tools- en resources-objecten. Dit bevestigt dat de server verbindingen accepteert en reageert op de MCP-initialisatie-handshake.

Test de create_note-tool:

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

De respons moet "Note created with id: ..." bevatten. Test nu de zoektool:

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

Je zou de zojuist aangemaakte notitie in de resultaten moeten zien. Verifieer dat een verzoek zonder Authorization-header een 401-fout retourneert:

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

Dit zou 401 moeten tonen. Als dat zo is, werkt je auth-middleware.

Je kunt ook de MCP Inspector gebruiken voor interactief testen met een web-UI:

npx @modelcontextprotocol/inspector

De Inspector opent een browserinterface waar je verbinding kunt maken met je server, tools en resources kunt bekijken en invocations kunt testen.

Hoe deploy je een MCP-server op een VPS?

Breng je project over naar de VPS met rsync, scp, of push naar een Git-repo en clone op de server. De volgende stappen gaan ervan uit dat je bent ingelogd op de VPS.

Maak een dedicated gebruiker aan

Draai applicatieprocessen nooit als root. Maak een servicegebruiker aan:

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

Installeer Node.js

Als Node.js 20+ nog niet geinstalleerd is:

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

Het eerste commando downloadt het setup-script naar een bestand zodat je het kunt inspecteren voor uitvoering. Verifieer dat de output v20.x.x of hoger toont voor Node.js en 10.x.x voor npm. De MCP SDK vereist Node.js 18+, maar 20 LTS wordt aanbevolen voor productie.

Stel de projectmap in

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

Verifieer eigenaarschap:

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

Alle bestanden moeten eigendom zijn van mcpserver:mcpserver.

Maak het omgevingsbestand aan

Sla het auth-token op in een beveiligd omgevingsbestand:

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

Lees het gegenereerde token en bewaar het op een veilige plek. Je hebt het nodig voor de clientconfiguratie:

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

Maak de systemd-service aan

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 laadt MCP_AUTH_TOKEN uit het beveiligde bestand. De hardening-sectie beperkt wat het proces kan doen. NoNewPrivileges voorkomt privilege-escalatie. ProtectSystem=strict maakt het bestandssysteem alleen-lezen behalve paden in ReadWritePaths. ProtectHome blokkeert toegang tot thuismappen van gebruikers.

Activeer en start de service:

sudo systemctl enable --now mcp-notes-server

enable zorgt dat de service start bij het opstarten. --now start hem direct.

Controleer de status:

sudo systemctl status mcp-notes-server

Je zou active (running) moeten zien. Als de service gefaald is, bekijk de logs:

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

Verifieer dat de server lokaal reageert:

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

Dit zou {"status":"ok"} moeten retourneren.

Hoe draai je een MCP-server achter Nginx met TLS?

Nginx termineert TLS en proxied verzoeken naar de MCP-server. De MCP-server handelt nooit zelf TLS af. Dit is het standaardpatroon voor productie-Node.js-deployments.

Installeer Nginx en Certbot

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

Configureer het Nginx-serverblok

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

Vervang mcp.example.com door je daadwerkelijke domein.

Activeer de site en verkrijg een TLS-certificaat:

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 past de configuratie aan om TLS-instellingen en een HTTP-naar-HTTPS-redirect toe te voegen. Werk nu de configuratie bij om de reverse proxy voor het MCP-endpoint toe te voegen:

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

De belangrijkste directives voor MCP-compatibiliteit:

  • proxy_buffering off voorkomt dat Nginx SSE-antwoorden buffert. Zonder deze instelling hangen streaming-antwoorden tot de verbinding sluit.
  • proxy_cache off zorgt dat dynamische antwoorden niet gecacht worden.
  • chunked_transfer_encoding off voorkomt chunking-problemen met SSE-event-streams.
  • Connection "" houdt de verbinding open zonder WebSocket-upgradegedrag te triggeren.
  • proxy_read_timeout 300s staat tool-aanroepen toe die tot 5 minuten duren. De standaard 60s is te kort voor complexe operaties.

Test en herlaad:

sudo nginx -t
sudo systemctl reload nginx

Verifieer vanaf je lokale machine (niet de server):

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

Dit zou {"status":"ok"} moeten retourneren. Als dat zo is, proxied Nginx correct naar je MCP-server via TLS.

Firewallregels

Blokkeer directe toegang tot poort 3000 van buitenaf. Alleen Nginx (op localhost) mag de MCP-server bereiken:

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

Verifieer dat poort 3000 niet bereikbaar is van buitenaf:

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

De verbinding zou moeten falen. Je MCP-server is alleen bereikbaar via Nginx op poort 443.

Hoe verbind je MCP-clients met een remote server?

Met de server draaiend achter Nginx met TLS, configureer je je MCP-clients om verbinding te maken.

Claude Code

Voeg de remote server toe via de CLI:

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

Vervang YOUR_TOKEN_HERE door het token uit /etc/mcp-notes-server/env.

Verifieer de verbinding:

claude mcp list

Typ in Claude Code /mcp om de serverstatus te controleren. De notes-server zou als verbonden moeten verschijnen met de tools search_notes en create_note beschikbaar.

Claude Desktop

Claude Desktop verbindt met remote MCP-servers via de instellingen-UI. Ga naar Settings > Connectors en voeg een nieuwe remote server toe met de URL https://mcp.example.com/mcp.

Als je het bearer token moet meegeven, kun je het ook configureren via een lokaal .mcp.json-bestand of het claude mcp add-CLI-commando hierboven en de server importeren in Claude Desktop:

claude mcp add-from-claude-desktop

Cursor

Open de Cursor-instellingen en navigeer naar de MCP-configuratie. Voeg een nieuwe server toe in het mcp.json-bestand (of via de Cursor-instellingen-UI):

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

Cursor probeert eerst Streamable HTTP bij het verbinden met een URL, dus extra transport-configuratie is niet nodig.

Verifieer de end-to-end-verbinding

Vraag vanuit een verbonden client de AI om:

  1. Een notitie aan te maken: "Create a note titled 'Server Test' with content 'Connected successfully'"
  2. Ernaar te zoeken: "Search my notes for 'Server Test'"

Als beide operaties resultaten retourneren, werkt de volledige keten: client naar Nginx naar MCP-server naar SQLite en terug.

MCP-primitieven referentie

Primitief Doel Voorbeeld
Tools Functies die de AI kan aanroepen met gestructureerde inputs search_notes, create_note
Resources Alleen-lezen data waar de AI naar kan verwijzen notes://all notitielijst
Prompts Voorgeschreven templates voor veelvoorkomende taken Niet gebruikt in deze tutorial

Tools zijn het meest geimplementeerde primitief. Resources zijn handig om referentiedata beschikbaar te stellen zonder berekening. Prompts laten je herbruikbare templates maken die de AI door specifieke workflows begeleiden.

Iets fout gegaan?

Server start niet: Controleer het journal op fouten:

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

Veelvoorkomende oorzaken: ontbrekende MCP_AUTH_TOKEN-omgevingsvariabele, verkeerde bestandsrechten op notes.db, te oude Node.js-versie.

Nginx retourneert 502 Bad Gateway: De MCP-server draait niet of luistert niet op poort 3000. Verifieer:

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

Client krijgt 401 Unauthorized: Het token in de clientconfiguratie komt niet overeen met het token op de server. Controleer:

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

Streaming-antwoorden geven een timeout: Verhoog proxy_read_timeout in de Nginx-configuratie. De standaard 300s dekt de meeste gevallen, maar langlopende tools hebben mogelijk meer nodig.

Certificaatfouten: Verifieer dat het certificaat geldig is en je domein dekt:

sudo certbot certificates

Als het certificaat verlopen is, vernieuw het:

sudo certbot renew

Database locked-fouten: SQLite WAL-modus gaat goed om met gelijktijdige leesbewerkingen, maar gelijktijdige schrijfbewerkingen kunnen conflicteren. Voor servers met veel verkeer kun je overstappen op PostgreSQL.

Permission denied op notes.db: De mcpserver-gebruiker heeft schrijftoegang nodig tot de werkmap voor SQLite. Controleer:

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

Het bestand moet eigendom zijn van mcpserver:mcpserver. Als het door root is aangemaakt tijdens het testen, corrigeer het:

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

Client toont "connection refused" of "timeout": Controleer of je DNS A-record naar het VPS-IP wijst en of poorten 80 en 443 open staan in de firewall:

sudo ufw status
dig +short mcp.example.com

De dig-output zou je VPS-IP-adres moeten tonen. Als dat niet zo is, werk je DNS-records bij en wacht op propagatie.

Volgende stappen

Je MCP-server is live en beveiligd. Waar je hierna naartoe kunt:

  • Voeg meer tools toe die verbinden met je databases, API's of bestandssystemen
  • Implementeer OAuth 2.1 in plaats van bearer tokens voor multi-user-toegang
  • Voeg rate limiting toe in Express of Nginx ter bescherming tegen misbruik
  • Stel monitoring in met journalctl -u mcp-notes-server -f en alerting
  • Deploy meerdere MCP-servers achter dezelfde Nginx-instantie op verschillende paden

De officiele MCP SDK-documentatie behandelt geavanceerde patronen waaronder stateful sessions, voortgangsrapportage en gestructureerde output-schema's.


Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.

Klaar om het zelf te proberen?

Deploy uw eigen server in seconden. Linux, Windows of FreeBSD.

Bekijk VPS-aanbod
MCP-server bouwen en deployen op een VPS