Regeln für Python-MCP-Server

Diese Seite fasst zusammen, wie wir auf ki.karlkratz.com MCP-Server in Python bauen. Grundlage sind die Architektur-Prinzipien aus /var/www/code/projektcharakteristika/ (DDD, Hexagonal, SRP, SoC, DRY, SOLID, Clean Code, Max-LoC), übersetzt auf die Eigenheiten des Model-Context-Protocol-stdio-Transports.

1. SDK und Transport

  1. Verwende das offizielle PyPI-Paket mcp, in der Praxis die FastMCP-Klasse für deklarative Tool-Registrierung.
  2. Default-Transport ist stdio, eine Subprocess-Instanz pro Client. HTTP-/SSE-Transport nur, wenn Remote-Zugriff explizit gewünscht ist.
  3. SDK-Version in einer Lockfile fixieren, keine "latest"-Floats in Production.

2. Goldene stdio-Regel

Keine ungewollten Schreibvorgänge auf stdout. Der stdio-Transport nutzt stdout exklusiv für JSON-RPC-Frames; jedes print() oder unbuffered Logging zerstört das Protokoll. Konsequenz:

3. Tool-Definition

Jedes Tool ist eine Funktion mit dem mcp.tool()-Decorator. Vier Pflichten:

  1. Name in snake_case, fachlich gewählt, ohne technische Präfixe (kein get_, do_).
  2. Docstring, der das Tool für das LLM beschreibt. Erster Satz ist die Kurzfassung; danach Argumentbedingungen, Wertgrenzen, Fehlersignale.
  3. Type-Hints für alle Parameter und den Return-Typ. Daraus generiert das SDK das JSON-Schema, das die KI zur Argument-Erzeugung sieht.
  4. Determinismus dokumentiert: List/Get sind seiteneffektfrei; Schreibvorgänge sind im Docstring klar als solche markiert.

4. Schichten: DDD und Hexagonal

Wie in /var/www/code/projektcharakteristika/architecture_layers.yaml beschrieben, übertragen auf Python:

SchichtInhaltDarf NICHT enthalten
domain/Dataclasses (frozen, slots), Domain-Errors, Ports als typing.ProtocolHTTP-Calls, DB-Treiber, JSON-Parsing, mcp-SDK-Imports
application/Use Cases, orchestrieren Ports, kein I/Ohttpx, psycopg, Dateipfade hartcodiert
infrastructure/Adapter: HTTP-Clients, DB-Connections, Filesystem, externe APIs, Config-Loaderfachliche Regeln, Validierungs-Algorithmen
presentation/server.py, Tool-Funktionen, JSON-Mapperfachliche Wahrheit, Domain-Regeln

Abhängigkeiten zeigen nur nach innen, Infrastructure kennt Application, Application kennt Domain. Niemals umgekehrt.

5. Quality Principles (aus quality_principles.yaml)

6. Maximale Dateigröße (hart)

Datei-TypHartes Limit (LoC)
domain (Dataclasses, Errors, Ports)80
application (Use Cases)120
infrastructure (Adapter)200
server.py (Composition + Tools)150
Test-Dateien300

Überschreitung ist Blocker, bevor das Limit angehoben wird, prüfen ob die Datei mehrere Verantwortungen mischt.

7. Forbidden Dependencies

8. Sicherheit und Sandbox

  1. Niemals Pfade aus Tool-Argumenten ohne Whitelist auf Dateioperationen anwenden, Path-Traversal-Schutz via realpath-Vergleich gegen einen erlaubten Root.
  2. Niemals subprocess oder eval mit ungeprüftem User-Input.
  3. Secrets liegen in /var/www/_credentials/<dienst>/credentials.json oder in ENV-Variablen, niemals im Code.
  4. Destruktive Tools (delete_, drop_, exec_) verlangen explizite Argumente, keine "all"-Variante per Default, kein implizites Cascade.

9. Fehlerbehandlung

Use Cases werfen Domain-Errors. Die Tool-Funktion fängt sie ab und mappt sie auf MCP-Fehlerantworten. Eine generische except Exception ist nur an genau einer Stelle erlaubt: am äußersten Rand. Stack-Traces gehen auf stderr, NICHT in die Tool-Antwort, damit das LLM keine internen Pfade sieht.

10. Schema-Akkuratesse

Das vom SDK aus den Type-Hints generierte JSON-Schema wird dem LLM gezeigt und definiert, welche Argumente es schicken darf. Konsequenz:

11. Tests

  1. Use Cases werden mit Fake-Adaptern getestet, keine echten HTTP- oder DB-Calls.
  2. Schema-Tests prüfen, dass tools/list das erwartete Schema liefert.
  3. Integration-Test fährt den Server als Subprocess hoch und schickt einen echten initialize+tools/call-Roundtrip.

12. Verzeichnis-Layout (Referenz)

PfadInhalt
server.pyComposition-Root: instanziiert Config, Adapter, Use Cases; registriert Tools
domain/Dataclasses, Errors, Ports (typing.Protocol)
application/Use Cases, dünne Orchestrierung
infrastructure/HTTP-Clients, DB-Adapter, Config-Loader
tests/pytest mit Fakes
pyproject.tomlMetadaten, Versionen via Lockfile fixiert

13. Quellen