Local Solutions · Interne Infrastruktur

Design MCP – Dokumentation

Ein zentraler MCP-Server, der Designregeln, Projektkontext und Skills für lokale Agents und Cloud-Chats bereitstellt – mit revisionssicheren Updates, Snapshots und Audit-Log.

Status wird geprüft … Streamable HTTP Bearer-Auth v0.1.0

1Zweck des MCP und Zugangsdaten

Der Local Solutions Design MCP ist die Single Source of Truth für Designregeln, Projektkontext, Cloud-Übergabe und Arbeits-Skills. Jeder Agent – ob Claude Code auf dem Rechner, ein Cloud-Chat in ChatGPT oder Claude – lädt zu Beginn denselben Kontext und kann Änderungen kontrolliert zurückschreiben.

MCP-Endpunkt
<MCP_URL> = https://design-mcp.localsolutions.de/mcp
Transport
MCP Streamable HTTP (Protokoll 2025-06-18), zustandslos, JSON-RPC 2.0 per POST
Authentifizierung
HTTP-Header Authorization: Bearer <MCP_BEARER_TOKEN>
Healthcheck
https://design-mcp.localsolutions.de/health (öffentlich, ohne Token)
Diese Dokumentation
https://design-mcp.localsolutions.de/docs/
Das Bearer-Token ist ein Geheimnis. Es wird nur serverseitig in deploy/.env und in den Client-Konfigurationen der berechtigten Personen abgelegt – nie in Git, Chats, Screenshots oder auf dieser Seite.

2Architekturübersicht

MCP-ClientClaude Code, Claude Desktop, ChatGPT, Codex, Cursor …HTTPS + Bearer-Header
nginx (Reverse Proxy)TLS via Let's Encrypt, /mcp, /health, /docs/Streaming ohne Pufferung, 1 h Timeout
Docker-ContainerNode 24 · @modelcontextprotocol/servernur 127.0.0.1:8788, User node
Volume /dataDokumente, Skills, manifest.json, history/, audit.ndjsonpersistent, Seed nur initial

Beim ersten Start kopiert der Server die Einträge aus seed/ in das Volume. Bereits vorhandene Einträge werden bei späteren Starts nie überschrieben – serverseitige Änderungen bleiben erhalten, neue Seed-Einträge werden ergänzt. Es gibt keine Löschfunktion und keinen Zugriff außerhalb des Datenverzeichnisses; alle Pfade werden gegen /data validiert.

3Verfügbare Ressourcen und Tools

Jeder Eintrag ist zusätzlich als MCP-Ressource unter ls-design://entries/<id> erreichbar.

ToolZweckWichtige ParameterSchreibt
design_list_entriesAlle Dokumente und Skills ohne Inhaltkind (optional: document | skill)nein
design_read_entryVollständiger Eintrag inkl. SHA-256-Revisionidnein
design_searchVolltextsuche mit Fundstellenquery, kind, limit (1–20)nein
design_get_agent_contextGebündelter Startkontext aller Core-EinträgeincludeSkills (Standard true)nein
design_entry_historyFrühere Fassungen eines Eintragsid, limit (1–50)nein
design_update_entryUpdate nur bei passender Revision, legt Snapshot anid, content, expectedRevision, changeSummary, confirmation: "UPDATE_ENTRY"ja
design_create_skillNeuer Skill; vorhandene IDs werden nie überschriebenid, title, description, content, confirmation: "CREATE_SKILL"ja

Seed-Einträge

project-context

Ziele, Technik, Arbeitsweise und Inhaltsregeln der Website.

design-architecture

Visuelle Regeln, Seitenrhythmus, Komponenten- und Interaktionsprinzipien.

cloud-chat-context

Übergabekontext für neue Cloud-Chats und Agents.

21st-design

Maschinenlesbare Design-Tokens und Komponenten-Workflow (JSON).

fluent-snippets

Globale CSS-, JS- und PHP-Assetverwaltung in WordPress.

local-solutions-website (Skill)

Arbeitsablauf für neue oder überarbeitete Seiten.

4Verbindung aus ChatGPT bzw. einem MCP-Client

Jeder Client, der MCP Streamable HTTP mit eigenen HTTP-Headern unterstützt, kann sich verbinden. Benötigt werden nur URL und Header:

URL:     <MCP_URL>
Header:  Authorization: Bearer <MCP_BEARER_TOKEN>

ChatGPT (Connector-Oberfläche)

  1. Einstellungen → Connectors (bzw. Apps & Connectors) → Entwicklermodus aktivieren.
  2. Erstellen → Name Local Solutions Design, MCP-Server-URL <MCP_URL>.
  3. Authentifizierung: Falls die Oberfläche einen statischen API-Schlüssel bzw. eigene Header anbietet, dort Authorization: Bearer <MCP_BEARER_TOKEN> hinterlegen.
Bietet die ChatGPT-Oberfläche nur OAuth oder Keine Authentifizierung an, ist der Weg über die OpenAI Responses API die verlässliche Alternative – dort werden eigene Header unterstützt (Beispiel in Abschnitt 6).

Rohe HTTP-Verbindung (curl)

curl -sS -X POST <MCP_URL> \
  -H "Authorization: Bearer <MCP_BEARER_TOKEN>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

Die Antwort kommt als text/event-stream (eine data:-Zeile mit dem JSON-RPC-Ergebnis). Der Server ist zustandslos – es ist keine Session-ID nötig.

5Verbindung aus Claude Desktop oder Claude Code

Claude Code (CLI)

claude mcp add --transport http local-solutions-design <MCP_URL> \
  --header "Authorization: Bearer <MCP_BEARER_TOKEN>"

Mit --scope user gilt der Server für alle Projekte; ohne Angabe nur für das aktuelle. Prüfen mit claude mcp list, dann im Chat: „Lade den Agent-Kontext über design_get_agent_context.“

Claude Desktop

Claude Desktop verbindet Remote-Server über Einstellungen → Connectors → Custom Connector (OAuth-basiert). Für statische Bearer-Token wird stattdessen die lokale Konfiguration mit mcp-remote als Brücke genutzt (Datei claude_desktop_config.json):

{
  "mcpServers": {
    "local-solutions-design": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "<MCP_URL>",
        "--header", "Authorization: Bearer <MCP_BEARER_TOKEN>"
      ]
    }
  }
}

Pfad der Datei: macOS ~/Library/Application Support/Claude/claude_desktop_config.json, Windows %APPDATA%\Claude\claude_desktop_config.json. Node.js ≥ 20 muss installiert sein.

6Beispielkonfigurationen

Claude Code – .mcp.json im Projekt

{
  "mcpServers": {
    "local-solutions-design": {
      "type": "http",
      "url": "<MCP_URL>",
      "headers": { "Authorization": "Bearer <MCP_BEARER_TOKEN>" }
    }
  }
}

Cursor / Windsurf / generischer Client – mcp.json

{
  "mcpServers": {
    "local-solutions-design": {
      "url": "<MCP_URL>",
      "headers": { "Authorization": "Bearer <MCP_BEARER_TOKEN>" }
    }
  }
}

OpenAI Codex CLI – ~/.codex/config.toml

[mcp_servers.local-solutions-design]
url = "<MCP_URL>"
bearer_token_env_var = "LS_DESIGN_MCP_TOKEN"   # Token nur als Umgebungsvariable setzen

OpenAI Responses API (Python)

from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
    model="gpt-5",
    tools=[{
        "type": "mcp",
        "server_label": "local_solutions_design",
        "server_url": "<MCP_URL>",
        "headers": {"Authorization": "Bearer <MCP_BEARER_TOKEN>"},
        "require_approval": "never",
    }],
    input="Lade den Agent-Kontext und fasse die Hero-Regeln zusammen.",
)
print(resp.output_text)

Anthropic Messages API – MCP-Connector

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-04-04" -H "content-type: application/json" \
  -d '{
    "model": "claude-sonnet-5", "max_tokens": 2000,
    "mcp_servers": [{ "type": "url", "url": "<MCP_URL>",
                      "name": "local-solutions-design",
                      "authorization_token": "<MCP_BEARER_TOKEN>" }],
    "messages": [{ "role": "user", "content": "Rufe design_get_agent_context auf." }]
  }'

7Authentifizierung mit Bearer-Token

Der Server prüft bei jedem Aufruf von /mcp den Header Authorization: Bearer <MCP_BEARER_TOKEN> in konstanter Zeit (timingSafeEqual). Ohne oder mit falschem Token antwortet er mit 401 {"error":"unauthorized"} und WWW-Authenticate: Bearer. Es gibt keine Nutzerkonten, Rollen oder OAuth – ein Token, volle Lese- und Schreibrechte.

  • Token nur über HTTPS senden (HTTP wird von nginx auf HTTPS umgeleitet).
  • Token nie in URLs oder Query-Parametern übergeben – nur im Header.
  • In Client-Configs bevorzugt Umgebungsvariablen verwenden, wo der Client das erlaubt.
  • Bei Verdacht auf Kompromittierung sofort rotieren (Abschnitt 15).

8Erstes Laden des Agent-Kontexts

Jeder neue Agent oder Chat beginnt mit design_get_agent_context. Das Ergebnis bündelt alle Core-Dokumente und Skills als ein Markdown-Dokument und liefert eine Gesamt-Revision, mit der sich später prüfen lässt, ob der Kontext noch aktuell ist.

curl -sS -X POST <MCP_URL> \
  -H "Authorization: Bearer <MCP_BEARER_TOKEN>" \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"design_get_agent_context","arguments":{"includeSkills":true}}}'

Antwort (gekürzt):

{ "result": { "structuredContent": {
    "revision": "9b00cb22…",
    "entries": ["21st-design","cloud-chat-context","fluent-snippets","design-architecture","project-context","local-solutions-website"],
    "content": "# 21st.dev Design-Konfiguration\n\n{ … }\n\n---\n\n# Cloud Chat Context …"
} } }

Empfohlener Einstiegs-Prompt in einem Chat: „Lade zuerst design_get_agent_context und halte dich an die dort enthaltenen Regeln.“

9Lesen und Suchen der Dokumentation

Einträge auflisten

{"jsonrpc":"2.0","id":3,"method":"tools/call",
 "params":{"name":"design_list_entries","arguments":{}}}

Einen Eintrag lesen

{"jsonrpc":"2.0","id":4,"method":"tools/call",
 "params":{"name":"design_read_entry","arguments":{"id":"design-architecture"}}}

Die Antwort enthält content, revision (SHA-256 des Inhalts) und updatedAt. Die Revision ist die Voraussetzung für jedes Update.

Suchen

{"jsonrpc":"2.0","id":5,"method":"tools/call",
 "params":{"name":"design_search","arguments":{"query":"Hero Ticker","kind":"document","limit":5}}}

Die Suche ist akzent- und groß-/kleinschreibungsunabhängig, gewichtet nach Trefferanzahl und liefert je Eintrag einen kurzen Ausschnitt mit Revision.

10Kontrollierte Updates mit SHA-256-Revision

Updates folgen dem Muster lesen → ändern → mit erwarteter Revision schreiben. Stimmt die Revision nicht mehr (weil ein anderer Chat zwischenzeitlich geschrieben hat), lehnt der Server ab und nennt die aktuelle Revision. Nichts wird still überschrieben.

{"jsonrpc":"2.0","id":6,"method":"tools/call",
 "params":{"name":"design_update_entry","arguments":{
   "id": "cloud-chat-context",
   "content": "<vollständiger neuer Inhalt>",
   "expectedRevision": "592ac4787a6875e20066bf60e122bbdbde15fffdb1fe939577ef02afca297b6d",
   "changeSummary": "Abschnitt Übergabe: Hinweis auf Staging-Domain ergänzt",
   "confirmation": "UPDATE_ENTRY"
 }}}
  • content ist immer der gesamte neue Inhalt, kein Diff.
  • expectedRevision ist der 64-stellige Hex-Wert aus design_read_entry.
  • confirmation muss wörtlich UPDATE_ENTRY sein – ein Schutz gegen versehentliche Aufrufe.
  • Bei Konflikt: isError: true, Text Revisionskonflikt. Aktuelle Revision: … → erneut lesen, Änderung zusammenführen, erneut schreiben.
  • Ist LS_DESIGN_WRITES_ENABLED=false, sind alle Schreibtools deaktiviert.

11Snapshots, Historie und Audit-Log

Vor jedem Update wird die bisherige Fassung als Snapshot abgelegt; jeder Schreibvorgang erzeugt außerdem eine Audit-Zeile.

Snapshots
/data/history/<id>/<zeitstempel>-<revision12>.json – enthält revision, savedAt, changeSummary und den vollständigen alten content
Audit-Log
/data/audit.ndjson – eine JSON-Zeile pro Vorgang: at, action (update | create-skill), id, fromRevision, toRevision, changeSummary
{"jsonrpc":"2.0","id":7,"method":"tools/call",
 "params":{"name":"design_entry_history","arguments":{"id":"cloud-chat-context","limit":10}}}

Eine ältere Fassung wird wiederhergestellt, indem ihr content aus dem Snapshot per design_update_entry mit der aktuellen Revision erneut geschrieben wird – so bleibt auch die Wiederherstellung nachvollziehbar. Auf dem Server: docker exec local-solutions-design-mcp tail /data/audit.ndjson.

12Hinzufügen neuer Skills und Dokumente

Skill zur Laufzeit anlegen (per MCP)

{"jsonrpc":"2.0","id":8,"method":"tools/call",
 "params":{"name":"design_create_skill","arguments":{
   "id": "landingpage-review",
   "title": "Landingpage Review",
   "description": "Prüfschritte vor Freigabe einer Landingpage.",
   "content": "# Landingpage Review\n\n1. Hero-Ticker prüfen …",
   "confirmation": "CREATE_SKILL"
 }}}

IDs bestehen aus Kleinbuchstaben, Ziffern und Bindestrichen (max. 81 Zeichen). Der Skill landet unter /data/skills/<id>/SKILL.md und ist sofort als Tool-Eintrag und Ressource verfügbar. Mit includeSkills:true (Standard) nimmt design_get_agent_context alle Skills auf, mit false nur die Core-Dokumente.

Dokument über den Seed hinzufügen (Deployment)

  1. Datei nach seed/documents/ legen und Eintrag in seed/manifest.json ergänzen (id, kind, title, description, mimeType, relativePath, optional core: true).
  2. Image neu bauen und Container neu starten (Abschnitt 13).
  3. Beim Start werden nur fehlende IDs ins Volume übernommen; bestehende Inhalte bleiben unangetastet.
Neue Dokumente (kind document) lassen sich nur über den Seed anlegen – zur Laufzeit gibt es bewusst nur design_create_skill. Inhalte bestehender Dokumente werden per design_update_entry gepflegt.

13Deployment und Aktualisierung

Server
Hetzner, Ubuntu 24.04, Docker 29 / Compose v5
Verzeichnis
/opt/local-solutions-design-mcp
Compose
deploy/compose.yml, Projekt/Container local-solutions-design-mcp
Secrets
deploy/.env (Rechte 600, nicht in Git)
Reverse Proxy
nginx, vHost /etc/nginx/sites-available/design-mcp.localsolutions.de.conf

Code-Update einspielen

cd /opt/local-solutions-design-mcp
# neue Version entpacken/pullen (deploy/.env bleibt unberührt), dann:
docker compose -f deploy/compose.yml up -d --build
docker compose -f deploy/compose.yml ps
curl -s https://design-mcp.localsolutions.de/health

Neustart und Logs

docker compose -f /opt/local-solutions-design-mcp/deploy/compose.yml restart
docker logs -f --tail 100 local-solutions-design-mcp

Details, Sicherheitsgrenzen und Checkliste: OPERATIONS.md im Projektverzeichnis.

14Backup und Wiederherstellung des Docker-Volumes

Alle veränderlichen Daten liegen im Volume local-solutions-design-mcp_design-mcp-data (Host-Pfad /var/lib/docker/volumes/local-solutions-design-mcp_design-mcp-data/_data). Ein Backup ist ein Tar-Archiv dieses Volumes.

Backup erstellen

/opt/local-solutions-design-mcp/deploy/backup-volume.sh
# → /var/backups/local-solutions-design-mcp/design-mcp-data-<datum>.tar.gz

Manuell, ohne Skript:

docker run --rm -v local-solutions-design-mcp_design-mcp-data:/data:ro -v /var/backups/local-solutions-design-mcp:/backup alpine \
  tar czf /backup/design-mcp-data-$(date +%F-%H%M).tar.gz -C /data .

Wiederherstellen

/opt/local-solutions-design-mcp/deploy/restore-volume.sh /var/backups/local-solutions-design-mcp/design-mcp-data-<datum>.tar.gz

Das Skript stoppt den Container, sichert den aktuellen Stand als Sicherheitskopie, leert das Volume, entpackt das Archiv und startet den Container neu. Ablauf und Prüfschritte: BACKUP_RESTORE.md.

15Token-Rotation

  1. Neues Token erzeugen: openssl rand -hex 32 (64 Zeichen).
  2. deploy/.env bearbeiten: MCP_BEARER_TOKEN=<neues Token>; Rechte bleiben 600.
  3. Container neu erstellen: docker compose -f deploy/compose.yml up -d (Env wird nur beim Erstellen gelesen – ein reines restart genügt nicht).
  4. Prüfen: altes Token → 401, neues Token → Initialize erfolgreich.
  5. Alle Client-Konfigurationen aktualisieren (Claude Code, Codex, ChatGPT, …).
cd /opt/local-solutions-design-mcp
NEW=$(openssl rand -hex 32)
sed -i "s/^MCP_BEARER_TOKEN=.*/MCP_BEARER_TOKEN=$NEW/" deploy/.env
docker compose -f deploy/compose.yml up -d
unset NEW

Es gibt genau ein gültiges Token; eine Übergangsphase mit zwei Tokens unterstützt der Server nicht. Rotation daher mit den Nutzern abstimmen.

16Fehlerdiagnose

SymptomUrsacheMaßnahme
401 unauthorizedToken fehlt, falsch oder Header falsch formatiertHeader exakt Authorization: Bearer <token>; Token in deploy/.env vergleichen (nicht ausgeben – Länge/Hash prüfen)
404 not_foundFalscher PfadNur /mcp und /health existieren im Backend
502 Bad GatewayContainer läuft nicht / Port 8788 nicht erreichbardocker ps, docker logs local-solutions-design-mcp, ss -tlnp | grep 8788
Container startet nichtUngültige .env (Token < 32 Zeichen, Boolean falsch)Logmeldung lesen: Umgebungsvariable … fehlt / muss true oder false sein
RevisionskonfliktEintrag wurde zwischenzeitlich geändertEintrag neu lesen, Änderung zusammenführen, mit neuer Revision schreiben
Schreibzugriffe sind serverseitig deaktiviertLS_DESIGN_WRITES_ENABLED=falseWert in deploy/.env auf true setzen, up -d
Client hängt bei langen AntwortenProxy puffert oder Timeoutnginx-Location /mcp: proxy_buffering off, proxy_read_timeout 3600s
TLS-FehlerZertifikat abgelaufencertbot certificates, certbot renew, systemctl reload nginx
# Schnellcheck auf dem Server
docker compose -f /opt/local-solutions-design-mcp/deploy/compose.yml ps
docker logs --tail 50 local-solutions-design-mcp
curl -s http://127.0.0.1:8788/health
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8788/mcp   # erwartet 401
nginx -t && tail -n 30 /var/log/nginx/error.log

17Healthcheck und Betriebsstatus

GET /health ist öffentlich, benötigt kein Token und gibt keine sensiblen Daten preis:

{"status":"ok","entries":6,"writesEnabled":true}
  • Docker-Healthcheck: alle 30 s wget http://127.0.0.1:8788/health; nach 3 Fehlschlägen gilt der Container als unhealthy (docker ps zeigt den Status).
  • Neustartverhalten: restart: unless-stopped – der Container kommt nach Server-Reboot oder Absturz automatisch zurück.
  • Externes Monitoring: HTTPS-Check auf /health mit Erwartung 200 und "status":"ok".

Live-Status wird geladen …