MCP-Server Guardrails: Read-only, Masking, Write-Proposals
Wie du einen MCP-Server für Datenbank, CI oder Messaging so baust, dass der Agent lesen darf, aber nichts ohne dich schreibt: Muster aus Spec und Praxis.
- Server verbindet sich mit einem read-only DB-User oder einer read-only Session
- Harte Limits pro Call: Zeilen, Laufzeit, keine Full Scans großer Tabellen
- Freigaben für Tabellen und Connections laufen nach einer Frist ab
- Sensible Spalten maskiert, auch gegen Raten über Filter wie LIKE
- Writes nur als Proposal mit Dry-Run, Commit durch einen Menschen außerhalb des Modells
- Dedupe-Key, damit ein wiederholter Tool-Call nicht doppelt ausführt
- Rate Limits pro Stunde und Empfänger im Server, nicht im Prompt
- Jeder Call mit Argumenten im Audit-Log
Grafik: Vibecoding Pro · Grundlage: die Quellen unten
Einen MCP-Server für Jenkins, die Datenbank oder Teams baut dir der Agent inzwischen aus einem Prompt. Das ist nicht mehr der aufwendige Teil. Der aufwendige Teil ist die Frage, was dieser Server darf, wenn das Modell ihn mit einem halb verstandenen Auftrag aufruft. Die meisten Setups lösen das mit einem Connection-String und Hoffnung.
Zwei Diskussionen auf r/mcp zeigen gerade sehr konkret, wie es besser geht, und die MCP-Spezifikation (Stand der Tools-Seite: Version 2026-07-28) liefert den normativen Rahmen dazu.
Die Spec schiebt die Verantwortung zu dir
Tools sind in MCP ausdrücklich model-controlled: Das Modell entdeckt und ruft sie selbstständig auf. Wie Bestätigungen aussehen, schreibt das Protokoll nicht vor. Was es vorschreibt, steht in den Security Considerations:
- Server müssen Inputs validieren, Zugriffskontrollen umsetzen, Aufrufe rate-limitieren und Outputs bereinigen.
- Clients sollen sensible Operationen bestätigen lassen, Tool-Inputs vor dem Call anzeigen, Ergebnisse vor der Weitergabe ans Modell validieren, Timeouts setzen und Aufrufe für Audits loggen.
Das „soll” auf Client-Seite ist der Haken. Ob Claude Code, Cursor oder VS Code Copilot dir vor jedem Call wirklich die Argumente zeigt, hängt vom Client und deiner Konfiguration ab. Was der Server selbst erzwingt, gilt dagegen in jedem Client. Deshalb gehören die harten Grenzen in den Server.
Ein Detail für Scoping: Die Tool-Liste darf laut Spec je nach Autorisierung variieren, also nur die Tools enthalten, die die Scopes des Aufrufers erlauben. Sie darf aber nicht pro Verbindung oder als Nebeneffekt anderer Requests schwanken. Und eine deterministische Reihenfolge der Tools hilft dem Client beim Caching und verbessert Prompt-Cache-Treffer.
Lesen: read-only, begrenzt, befristet
Der Datenbank-Server Rowkery (Open Source, Apache-2.0, nach eigener Aussage Alpha, Mac only, Postgres und SQLite, läuft als Rowkery --mcp über stdio und ist auf rmcp gebaut) zeigt, wie das aussieht:
- Jeder Call ist ein einzelnes
SELECTin einer read-only Session, begrenzt auf 1.000 Zeilen und 10 Sekunden. Full Scans großer Tabellen werden abgelehnt. - Der Agent sieht nur die Connections und Tabellen, die du freigibst, und die Freigabe läuft nach einer selbst gewählten Frist ab, etwa nach einer Stunde oder zum Feierabend.
- Die Prüfungen sind einfache Rust-Funktionen, die ohne Datenbank unit-getestet werden.
Im zweiten Thread läuft es auf dasselbe hinaus: Datenbank und Jenkins standardmäßig read-only, für die Prod-DB eine read-only Rolle, Jenkins-Tokens prüfen. Ein Kommentator berichtet, dass sein Agent mit Schreibrechten auf Staging ungefragt Testdaten „aufgeräumt” hat. Ein anderer würde Jenkins und DB an die kürzeste Leine nehmen, weil ein Agent, der Builds auslösen und schreiben kann, in einem Zug ausliefern und kaputt machen kann.
Maskieren: auch gegen Raten
Sensible Spalten kommen bei Rowkery als Platzhalter zurück. Das allein reicht nicht, denn der Agent könnte den Wert über Filter eingrenzen: erst LIKE 'a%', dann 'ab%'. Deshalb prüft der Server den Postgres-Query-Plan und lehnt jede Abfrage ab, die eine maskierte Spalte für etwas anderes verwendet als Ausgeben oder Zählen. Das ist der Unterschied zwischen kosmetischer und wirksamer Maskierung.
Schreiben: Proposal statt Write
Das stärkste Muster aus beiden Threads: Der Agent darf INSERT, UPDATE oder DELETE nur vorschlagen. Der Server macht einen Dry-Run, zeigt Vorher und Nachher, und geschrieben wird erst, wenn ein Mensch in der App auf Commit drückt. Es gibt also schlicht kein Tool, mit dem das Modell selbst committet.
Ein Entwickler, der einen MCP-Server für WhatsApp-Nachrichten betreibt, überträgt das auf irreversible Aktionen:
- Entwurf und Versand trennen, ein Mensch bestätigt vor dem Senden.
- In der Bestätigung den exakten Empfänger und den finalen Text zeigen, keine Zusammenfassung.
- Über einen Dedupe-Key verhindern, dass ein wiederholter Tool-Call doppelt sendet.
- Ein hartes Limit pro Stunde und pro Empfänger, durchgesetzt außerhalb des Modells.
- Jeden Call mit seinen Argumenten loggen.
Offen bleibt ein Edge Case: Was, wenn ein Proposal stundenlang liegt und sich die Tabelle darunter ändert? Rowkery führt es so aus, wie es geschrieben wurde. Wer sichergehen will, macht vor dem Commit einen neuen Dry-Run. Für deinen eigenen Server ist das eine Designentscheidung, die du bewusst treffen solltest, statt sie dem Zufall zu überlassen.
Was das für deinen Workflow bedeutet
Behandle jeden selbst gebauten MCP-Server wie eine API, die ein ungeduldiger Praktikant mit Root-Rechten benutzt. Die Grenzen gehören in drei Schichten: in die Credentials (read-only User, eng gescopte Tokens), in den Server (Limits, Maskierung, Proposals, Dedupe, Log) und erst zuletzt in den Client. Auf Client-Seite kannst du in Claude Code MCP-Tools zusätzlich über Permission-Regeln in der settings.json einschränken und mit einem PreToolUse-Hook prüfen. Beides ersetzt aber keinen Server, der gefährliche Operationen gar nicht erst anbietet.
Zwei Punkte aus der Praxis solltest du nicht unterschätzen: Firmenrichtlinien können verbieten, Kundendaten überhaupt an ein LLM zu schicken. Dann ist Maskierung keine Kür, sondern Voraussetzung. Und dokumentiere, welche Server mit welchen Tools und Limits laufen, sonst weiß in drei Monaten niemand mehr, warum der Jenkins-Token Deploy-Rechte hat.
Der Prompt, der dir einen MCP-Server generiert, ist in Minuten geschrieben. Die Guardrails darin sind der eigentliche Engineering-Aufwand.
Du willst wissen, ob deine .mcp.json und die Server dahinter Schreibrechte haben, die sie nicht brauchen? Fordere ein Agent-Setup-Review an: MCP-Konfiguration, Permissions und Hooks deines Repos, schriftlich mit priorisierten Änderungsvorschlägen.
Häufige Fragen
Was verlangt die MCP-Spezifikation von Servern bei der Sicherheit?
Laut Security Considerations der Spec müssen Server alle Tool-Inputs validieren, Zugriffskontrollen umsetzen, Tool-Aufrufe rate-limitieren und Tool-Outputs bereinigen. Clients sollen sensible Operationen bestätigen lassen, Inputs vor dem Call zeigen, Timeouts setzen und Aufrufe protokollieren.
Reicht ein read-only Prompt, damit ein Agent keine Daten ändert?
Nein. Eine Anweisung im Prompt ist eine Bitte an das Modell. Durchsetzen musst du es im Server oder in der Datenbank, etwa mit einem read-only DB-User oder einer read-only Session, sodass ein Write technisch gar nicht durchgeht.
Wie verhindere ich, dass ein Agent maskierte Spalten über Filter errät?
Maskieren allein reicht nicht, weil der Agent über schrittweise LIKE-Filter den Wert eingrenzen kann. Ein Ansatz aus der Praxis prüft den Query-Plan und lehnt jede Abfrage ab, die eine maskierte Spalte für etwas anderes nutzt als Ausgeben oder Zählen.
Was passiert mit einem Write-Proposal, wenn sich die Daten bis zur Freigabe ändern?
Das musst du im Design entscheiden. Im besprochenen Datenbank-Server läuft ein freigegebenes Proposal so, wie es geschrieben wurde, ein erneuter Dry-Run zeigt vorher den Stand mit aktuellen Daten.