Eigenen MCP-Server auf einem VPS bauen und selbst hosten

10 Min. Lesezeit·Matthieu·mcptypescriptnodejsnginxself-hostingai-agents|

Bauen Sie einen TypeScript MCP-Server von Grund auf, stellen Sie ihn auf einem VPS mit systemd bereit und betreiben Sie ihn hinter Nginx mit TLS. Verbinden Sie Claude Desktop, Claude Code und Cursor mit Ihrem selbst gehosteten Server.

Jedes MCP-Server-Tutorial hoert bei "lokal mit stdio ausfuehren" auf. Dieses hier deckt den gesamten Weg ab: einen TypeScript MCP-Server bauen, auf einem VPS hinter Nginx mit TLS bereitstellen, mit Authentifizierung und Firewall-Regeln absichern und MCP-Clients ueber das Internet verbinden.

Am Ende haben Sie einen produktionsfaehigen MCP-Server, den Claude Desktop, Claude Code, Cursor oder jeder andere MCP-kompatible Client aus der Ferne erreichen kann.

Was werden Sie bauen?

MCP-Clients senden JSON-RPC-Anfragen ueber HTTPS. Nginx terminiert TLS und leitet Anfragen an Ihren MCP-Server-Prozess weiter, der auf einem lokalen Port laeuft. Der MCP-Server validiert ein Bearer-Token, verarbeitet die Anfrage und gibt Ergebnisse zurueck.

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

Das Arbeitsbeispiel ist ein Notizen-Server. Er stellt zwei Tools (Notizen suchen, Notiz erstellen) und eine Resource (alle Notizen auflisten) bereit. Einfach genug zum Nachvollziehen, nuetzlich genug zum Erweitern.

Welchen Transport sollten Sie fuer einen Remote-MCP-Server verwenden?

Streamable HTTP ist der aktuelle MCP-Spezifikationsstandard fuer Remote-Deployments. Er hat den veralteten SSE-Transport ersetzt und funktioniert ueber normale HTTP-POST-Anfragen. Stdio bleibt der Transport fuer rein lokale Server, bei denen der Client den Server als Subprozess startet.

Transport Anwendungsfall Remote-faehig Client-Unterstuetzung
stdio Lokale Tools, CLI-Integrationen Nein Alle Clients
SSE (veraltet) Aeltere Remote-Server Ja Die meisten Clients (wird ausgemustert)
Streamable HTTP Remote-Server, Produktion Ja Claude Desktop, Claude Code, Cursor

Streamable HTTP sendet jede Client-Nachricht als HTTP POST an einen einzelnen Endpunkt (z.B. /mcp). Der Server antwortet mit application/json fuer einfache Antworten oder text/event-stream, wenn er mehrere Nachrichten zurueckstreamen muss. Das funktioniert mit Standard-HTTP-Infrastruktur, einschliesslich Reverse Proxies und Load Balancern.

Voraussetzungen

  • Ein VPS mit Debian 12 oder Ubuntu 24.04 (diese Anleitung verwendet Debian 12)
  • Ein Domainname mit DNS, der auf Ihren VPS zeigt (z.B. mcp.example.com)
  • Node.js 20+ auf dem VPS installiert
  • Grundkenntnisse in TypeScript und Linux

Wie richten Sie ein TypeScript-MCP-Server-Projekt ein?

Beginnen Sie auf Ihrem lokalen Rechner oder direkt auf dem VPS. Initialisieren Sie das Projekt, installieren Sie das MCP SDK und richten Sie TypeScript ein.

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

Erstellen Sie die TypeScript-Konfiguration:

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

Speichern Sie dies als tsconfig.json im Projektstammverzeichnis.

Aktualisieren Sie package.json, um die Build- und Start-Skripte hinzuzufuegen und den Modultyp festzulegen:

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

Erstellen Sie das Quellverzeichnis:

mkdir src

Wie implementieren Sie MCP-Tools mit Eingabevalidierung?

Ein MCP-Server stellt Tools, Resources und Prompts fuer KI-Clients bereit. Tools sind Funktionen, die die KI aufrufen kann. Resources sind schreibgeschuetzte Daten, auf die die KI zugreifen kann. Wir implementieren beides.

Erstellen Sie src/index.ts mit dem Datenbank-Setup und einer Factory-Funktion, die fuer jede Anfrage einen neuen MCP-Server erstellt. Zustandsloses Streamable HTTP erfordert eine neue McpServer-Instanz pro Anfrage, da das SDK es nicht erlaubt, denselben Server mit einem anderen Transport erneut zu verbinden.

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

Registrieren Sie nun die Tools innerhalb der Factory. Jedes Tool verwendet Zod fuer die Eingabevalidierung. Das SDK validiert Eingaben automatisch, bevor Ihr Handler ausgefuehrt wird.

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

Jede Eingabe wird von Zod validiert, bevor sie den Handler erreicht. Wenn ein Client { query: "" } sendet, gibt das SDK automatisch einen Validierungsfehler zurueck. Kein manuelles Parsen noetig.

Wie fuegen Sie Resources zu einem MCP-Server hinzu?

Resources geben KI-Clients schreibgeschuetzten Zugriff auf Daten. Sie werden ueber URI referenziert. Fuegen Sie eine Resource hinzu, die alle Notizen auflistet, weiterhin innerhalb der 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 koennen diese Resource entdecken und lesen, ohne ein Tool aufzurufen. Claude Desktop zeigt Resources im Anhaenge-Menue an. Claude Code stellt sie ueber @-Erwahnungen bereit.

Wie sichern Sie einen MCP-Server mit Token-Authentifizierung ab?

Ein produktiver MCP-Server braucht Authentifizierung. Ohne sie kann jeder, der Ihren Endpunkt entdeckt, Ihre Tools aufrufen. Verwenden Sie ein Bearer-Token, das in einer Express-Middleware geprueft wird, bevor Anfragen den MCP-Transport erreichen.

Generieren Sie ein starkes Token:

openssl rand -base64 32

Speichern Sie die Ausgabe. Sie benoetigen sie sowohl fuer die Server- als auch fuer die Client-Konfiguration.

Fuegen Sie die Authentifizierungs-Middleware und Express-Routen zu src/index.ts hinzu:

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

Die Middleware wird vor jeder Anfrage an /mcp ausgefuehrt. Sie prueft auf ein Bearer-Token im Authorization-Header und lehnt unautorisierte Anfragen mit einer JSON-RPC-Fehlerantwort ab.

Wie richten Sie den Streamable-HTTP-Transport ein?

Fuegen Sie das Transport-Setup und die Route-Handler hinzu. Jede Anfrage erstellt einen neuen McpServer aus der Factory und eine neue Transport-Instanz:

// --- 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 erstellt einen zustandslosen Server. Jede Anfrage ist unabhaengig, mit eigenem McpServer und Transport-Paar. Das ist einfacher zu deployen und funktioniert gut hinter einem Reverse Proxy ohne Sticky Sessions. Die Datenbankverbindung (db) lebt auf Modulebene und wird anfrageuebergreifend geteilt, sodass Daten zwischen Aufrufen bestehen bleiben.

Der Server bindet an 127.0.0.1, nicht an 0.0.0.0. Er akzeptiert nur Verbindungen von localhost. Nginx ist der einzige Weg, ihn von aussen zu erreichen.

Bauen und lokal testen:

npm run build

Pruefen Sie, ob der Build ohne Fehler durchlaeuft.

Wie testen Sie einen MCP-Server lokal?

Der MCP Inspector ist das offizielle Test-Tool. Er verbindet sich mit Ihrem Server und laesst Sie Tools interaktiv aufrufen.

Starten Sie Ihren Server in einem Terminal:

MCP_AUTH_TOKEN=test-token-local npm start

In einem anderen Terminal mit curl testen:

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

Sie sollten eine SSE-Antwort sehen (event: message gefolgt von einer data:-Zeile), die die Faehigkeiten des Servers enthaelt, einschliesslich der tools- und resources-Objekte. Das bestaetigt, dass der Server Verbindungen annimmt und auf den MCP-Initialisierungs-Handshake antwortet.

Testen Sie das 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
  }'

Die Antwort sollte "Note created with id: ..." enthalten. Testen Sie nun das Such-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": "search_notes",
      "arguments": { "query": "Test" }
    },
    "id": 3
  }'

Sie sollten die gerade erstellte Notiz in den Ergebnissen sehen. Pruefen Sie, ob eine Anfrage ohne Authorization-Header einen 401-Fehler zurueckgibt:

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

Das sollte 401 ausgeben. Wenn ja, funktioniert Ihre Auth-Middleware.

Sie koennen auch den MCP Inspector fuer interaktives Testen mit Web-UI verwenden:

npx @modelcontextprotocol/inspector

Der Inspector oeffnet eine Browser-Oberflaeche, in der Sie sich mit Ihrem Server verbinden, Tools und Resources durchsuchen und Aufrufe testen koennen.

Wie stellen Sie einen MCP-Server auf einem VPS bereit?

Uebertragen Sie Ihr Projekt mit rsync, scp oder pushen Sie es in ein Git-Repository und klonen Sie es auf dem Server. Die folgenden Schritte setzen voraus, dass Sie auf dem VPS eingeloggt sind.

Dedizierten Benutzer erstellen

Fuehren Sie Anwendungsprozesse nie als root aus. Erstellen Sie einen Dienstbenutzer:

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

Node.js installieren

Falls Node.js 20+ noch nicht installiert ist:

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

Der erste Befehl laedt das Setup-Skript in eine Datei herunter, damit Sie es vor der Ausfuehrung pruefen koennen. Die Ausgabe sollte v20.x.x oder hoeher fuer Node.js und 10.x.x fuer npm anzeigen. Das MCP SDK benoetigt Node.js 18+, aber 20 LTS wird fuer den Produktionseinsatz empfohlen.

Projektverzeichnis einrichten

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

Eigentuemer pruefen:

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

Alle Dateien sollten mcpserver:mcpserver gehoeren.

Umgebungsdatei erstellen

Speichern Sie das Auth-Token in einer geschuetzten Umgebungsdatei:

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

Lesen Sie das generierte Token aus und speichern Sie es sicher. Sie benoetigen es fuer die Client-Konfiguration:

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

systemd-Service erstellen

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 laedt MCP_AUTH_TOKEN aus der geschuetzten Datei. Der Hardening-Abschnitt begrenzt, was der Prozess tun kann. NoNewPrivileges verhindert Rechteeskalation. ProtectSystem=strict macht das Dateisystem schreibgeschuetzt, ausser fuer Pfade in ReadWritePaths. ProtectHome blockiert den Zugriff auf Home-Verzeichnisse.

Aktivieren und starten Sie den Service:

sudo systemctl enable --now mcp-notes-server

enable sorgt dafuer, dass der Service beim Booten startet. --now startet ihn sofort.

Status pruefen:

sudo systemctl status mcp-notes-server

Sie sollten active (running) sehen. Falls der Service fehlgeschlagen ist, pruefen Sie die Logs:

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

Pruefen Sie, ob der Server lokal antwortet:

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

Das sollte {"status":"ok"} zurueckgeben.

Wie betreiben Sie einen MCP-Server hinter Nginx mit TLS?

Nginx terminiert TLS und leitet Anfragen an den MCP-Server weiter. Der MCP-Server behandelt TLS nie direkt. Das ist das Standardmuster fuer produktive Node.js-Deployments.

Nginx und Certbot installieren

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

Nginx-Serverblock konfigurieren

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

Ersetzen Sie mcp.example.com durch Ihre tatsaechliche Domain.

Aktivieren Sie die Seite und holen Sie ein TLS-Zertifikat:

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 aendert die Konfiguration, um TLS-Einstellungen und eine HTTP-zu-HTTPS-Weiterleitung hinzuzufuegen. Aktualisieren Sie nun die Konfiguration fuer den Reverse Proxy zum MCP-Endpunkt:

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

Die wichtigen Direktiven fuer MCP-Kompatibilitaet:

  • proxy_buffering off verhindert, dass Nginx SSE-Antworten puffert. Ohne diese Einstellung haengen Streaming-Antworten, bis die Verbindung geschlossen wird.
  • proxy_cache off stellt sicher, dass dynamische Antworten nicht gecacht werden.
  • chunked_transfer_encoding off vermeidet Chunking-Probleme bei SSE-Event-Streams.
  • Connection "" haelt die Verbindung offen, ohne WebSocket-Upgrade-Verhalten auszuloesen.
  • proxy_read_timeout 300s erlaubt Tool-Aufrufe, die bis zu 5 Minuten dauern. Die Standard-60s sind fuer aufwendige Operationen zu kurz.

Testen und neu laden:

sudo nginx -t
sudo systemctl reload nginx

Von Ihrem lokalen Rechner (nicht dem Server) pruefen:

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

Das sollte {"status":"ok"} zurueckgeben. Wenn ja, leitet Nginx korrekt an Ihren MCP-Server ueber TLS weiter.

Firewall-Regeln

Blockieren Sie direkten Zugriff auf Port 3000 von aussen. Nur Nginx (auf localhost) sollte den MCP-Server erreichen:

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

Pruefen Sie, ob Port 3000 von aussen nicht erreichbar ist:

# Von Ihrem lokalen Rechner, das sollte einen Timeout ergeben oder abgelehnt werden:
curl -s --connect-timeout 5 http://YOUR_VPS_IP:3000/health

Die Verbindung sollte fehlschlagen. Ihr MCP-Server ist nur ueber Nginx auf Port 443 erreichbar.

Wie verbinden Sie MCP-Clients mit einem Remote-Server?

Nachdem der Server hinter Nginx mit TLS laeuft, konfigurieren Sie Ihre MCP-Clients fuer die Verbindung.

Claude Code

Fuegen Sie den Remote-Server ueber die CLI hinzu:

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

Ersetzen Sie YOUR_TOKEN_HERE durch das Token aus /etc/mcp-notes-server/env.

Verbindung pruefen:

claude mcp list

In Claude Code tippen Sie /mcp, um den Serverstatus zu pruefen. Der notes-server sollte als verbunden angezeigt werden, mit den Tools search_notes und create_note.

Claude Desktop

Claude Desktop verbindet sich ueber die Einstellungs-UI mit Remote-MCP-Servern. Gehen Sie zu Settings > Connectors und fuegen Sie einen neuen Remote-Server mit der URL https://mcp.example.com/mcp hinzu.

Wenn Sie das Bearer-Token uebergeben muessen, koennen Sie es auch ueber eine lokale .mcp.json-Datei oder den oben gezeigten claude mcp add-CLI-Befehl konfigurieren und den Server in Claude Desktop importieren:

claude mcp add-from-claude-desktop

Cursor

Oeffnen Sie die Cursor-Einstellungen und navigieren Sie zur MCP-Konfiguration. Fuegen Sie einen neuen Server in der mcp.json-Datei hinzu (oder ueber die Cursor-Einstellungs-UI):

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

Cursor versucht bei einer URL-Verbindung zuerst Streamable HTTP, daher ist keine zusaetzliche Transport-Konfiguration noetig.

End-to-End-Verbindung pruefen

Bitten Sie die KI von jedem verbundenen Client aus:

  1. Eine Notiz erstellen: "Create a note titled 'Server Test' with content 'Connected successfully'"
  2. Danach suchen: "Search my notes for 'Server Test'"

Wenn beide Operationen Ergebnisse liefern, funktioniert die gesamte Kette: Client zu Nginx zu MCP-Server zu SQLite und zurueck.

MCP-Primitives-Referenz

Primitive Zweck Beispiel
Tools Funktionen, die die KI mit strukturierten Eingaben aufrufen kann search_notes, create_note
Resources Schreibgeschuetzte Daten, auf die die KI zugreifen kann notes://all Notizliste
Prompts Vorgefertigte Templates fuer haeufige Aufgaben In diesem Tutorial nicht verwendet

Tools sind das am haeufigsten implementierte Primitive. Resources eignen sich, um Referenzdaten ohne Berechnung bereitzustellen. Prompts erstellen wiederverwendbare Templates, die die KI durch bestimmte Workflows fuehren.

Etwas funktioniert nicht?

Server startet nicht: Pruefen Sie das Journal auf Fehler:

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

Haeufige Ursachen: fehlende Umgebungsvariable MCP_AUTH_TOKEN, falsche Dateiberechtigungen auf notes.db, zu alte Node.js-Version.

Nginx gibt 502 Bad Gateway zurueck: Der MCP-Server laeuft nicht oder hoert nicht auf Port 3000. Pruefen Sie:

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

Client bekommt 401 Unauthorized: Das Token in der Client-Konfiguration stimmt nicht mit dem Token auf dem Server ueberein. Pruefen Sie:

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

Streaming-Antworten laufen in einen Timeout: Erhoehen Sie proxy_read_timeout in der Nginx-Konfiguration. Die Standard-300s reichen fuer die meisten Faelle, aber lang laufende Tools brauchen moeglicherweise mehr.

Zertifikatsfehler: Pruefen Sie, ob das Zertifikat gueltig ist und Ihre Domain abdeckt:

sudo certbot certificates

Falls das Zertifikat abgelaufen ist, erneuern Sie es:

sudo certbot renew

Database-locked-Fehler: SQLite im WAL-Modus verarbeitet gleichzeitige Lesezugriffe gut, aber gleichzeitige Schreibzugriffe koennen kollidieren. Fuer Server mit hohem Verkehrsaufkommen sollten Sie PostgreSQL in Betracht ziehen.

Permission denied bei notes.db: Der Benutzer mcpserver braucht Schreibzugriff auf das Arbeitsverzeichnis fuer SQLite. Pruefen Sie:

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

Die Datei sollte mcpserver:mcpserver gehoeren. Falls sie beim Testen von root erstellt wurde, korrigieren Sie das:

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

Client zeigt "connection refused" oder "timeout": Pruefen Sie, ob Ihr DNS-A-Record auf die VPS-IP zeigt und die Ports 80 und 443 in der Firewall geoeffnet sind:

sudo ufw status
dig +short mcp.example.com

Die dig-Ausgabe sollte Ihre VPS-IP-Adresse anzeigen. Falls nicht, aktualisieren Sie Ihre DNS-Eintraege und warten Sie auf die Propagierung.

Naechste Schritte

Ihr MCP-Server ist live und abgesichert. Moegliche Erweiterungen:

  • Weitere Tools hinzufuegen, die sich mit Ihren Datenbanken, APIs oder Dateisystemen verbinden
  • OAuth 2.1 statt Bearer-Tokens fuer Mehrbenutzerzugriff implementieren
  • Rate Limiting in Express oder Nginx zum Schutz vor Missbrauch einrichten
  • Monitoring mit journalctl -u mcp-notes-server -f und Alerting aufsetzen
  • Mehrere MCP-Server hinter derselben Nginx-Instanz auf verschiedenen Pfaden bereitstellen

Die offizielle MCP-SDK-Dokumentation behandelt fortgeschrittene Muster wie zustandsbehaftete Sitzungen, Fortschrittsberichte und strukturierte Ausgabe-Schemas.


Copyright 2026 Virtua.Cloud. Alle Rechte vorbehalten. Dieser Inhalt ist ein Originalwerk des Virtua.Cloud-Teams. Vervielfaeltigung, Wiederveroeffentlichung oder Weiterverbreitung ohne schriftliche Genehmigung ist untersagt.

Bereit, es selbst auszuprobieren?

Stellen Sie Ihren eigenen Server in Sekunden bereit. Linux, Windows oder FreeBSD.

VPS-Angebote ansehen
MCP-Server auf VPS bauen und mit TLS bereitstellen