KIMCPPythonDokumentationPerlObsidian

MCP für Code-Dokumentation: Ein Nachschlagewerk für Legacy-Projekte

Wie ich mir eine große Perl-Codebase erschlossen habe

Ich arbeite seit einiger Zeit an einem größeren Projekt. Die Codebase ist in Perl geschrieben, gewachsen über viele Jahre, komplex und umfangreich. Die Herausforderung: Über 1.300 Module, sehr viele Datenbanktabellen, gewachsene Geschäftslogik. So sieht Software aus, die lange lebt und sich bewährt hat. Aber wie arbeitet man sich da ein?

Daher habe ich mir im Lauf der Zeit ein paar Tools gebaut, um diesen Code besser zu verstehen und effektiver mit ihm arbeiten zu können. Und nun verbinde ich diese einzelnen Werkzeuge mit einer KI, um das Optimum aus ihnen herauszuholen.

Das Problem

Die üblichen Wege:

  1. Code lesen – geht, aber bei der Menge verliert man schnell den Überblick
  2. Kollegen fragen – hilft, aber nicht für jede Detailfrage
  3. Dokumentation lesen – existiert teilweise, aber lückenhaft und manchmal veraltet

Was mir fehlte: Ein persönliches Nachschlagewerk. Etwas, das ich selbst aufbaue, in meinen Worten, das mit meinem Verständnis wächst.

Die Idee: Obsidian als Wissensbasis

Ich nutze Obsidian für Notizen. Markdown-Dateien, lokal, vernetzt durch Links. Perfekt für eine Code-Dokumentation:

  • Portabel – nur Textdateien, kein Lock-in
  • Verlinkbar – Module verweisen aufeinander
  • Durchsuchbar – Volltextsuche über alles
  • Versionierbar – kann ins Git

Die Struktur, die mir vorschwebt:

docs/
├── modules/
│   ├── User.md
│   ├── Order.md
│   └── ...
├── tables/
│   ├── users.md
│   ├── orders.md
│   └── ...
├── flows/
│   ├── checkout.md
│   └── ...
└── glossar.md

Jedes Modul bekommt eine Seite. Jede wichtige Tabelle. Wichtige Abläufe als eigene Dokumente. Alles verlinkt.

Warum MCP?

Das händisch zu machen wäre Sisyphusarbeit. Bei 1.300 Modulen würde ich Jahre brauchen – und wäre nie fertig, weil sich der Code weiterentwickelt.

Die Idee: Ein MCP-Tool, das mir hilft. Nicht automatisch generierte Dokumentation (die ist meist wertlos), sondern ein Assistent beim Dokumentieren:

  • Ich frage: “Was macht das Modul Order::Validation?”
  • Claude liest den Code, erklärt ihn mir
  • Ich formuliere das in meinen Worten
  • Das Tool schreibt es als Markdown-Datei

Später kann ich fragen: “Hat sich Order::Validation seit meiner letzten Dokumentation geändert?” – und das Tool vergleicht.

Der erste Ansatz

Ein MCP-Server mit diesen Fähigkeiten:

Code lesen und analysieren

from pathlib import Path

PROJECT_ROOT = Path("/path/to/project")
DOCS_ROOT = Path.home() / "Documents" / "project-docs"

@mcp.tool()
def read_module(module_name: str) -> str:
    """Liest ein Perl-Modul und gibt den Inhalt zurück."""
    # Module::Name -> Module/Name.pm
    path = module_name.replace("::", "/") + ".pm"
    full_path = PROJECT_ROOT / "lib" / path

    if not full_path.exists():
        return f"Modul nicht gefunden: {module_name}"

    content = full_path.read_text()

    # Bei sehr langen Dateien kürzen
    if len(content) > 15000:
        content = content[:15000] + "\n\n... (gekürzt, Datei hat {len(content)} Zeichen)"

    return content


@mcp.tool()
def find_modules(pattern: str) -> str:
    """Findet Module die einem Muster entsprechen."""
    lib_path = PROJECT_ROOT / "lib"
    matches = list(lib_path.rglob(f"*{pattern}*.pm"))

    if not matches:
        return f"Keine Module gefunden für: {pattern}"

    # Pfade zu Modulnamen konvertieren
    modules = []
    for m in sorted(matches)[:30]:
        rel = m.relative_to(lib_path)
        name = str(rel.with_suffix("")).replace("/", "::")
        modules.append(name)

    return "\n".join(modules)


@mcp.tool()
def module_dependencies(module_name: str) -> str:
    """Zeigt welche Module ein Modul verwendet (use/require)."""
    path = module_name.replace("::", "/") + ".pm"
    full_path = PROJECT_ROOT / "lib" / path

    if not full_path.exists():
        return f"Modul nicht gefunden: {module_name}"

    content = full_path.read_text()

    # Einfaches Regex für use/require Statements
    import re
    uses = re.findall(r'^use\s+([\w:]+)', content, re.MULTILINE)
    requires = re.findall(r'^require\s+([\w:]+)', content, re.MULTILINE)

    deps = sorted(set(uses + requires))

    if not deps:
        return "Keine Abhängigkeiten gefunden"

    return "\n".join(deps)

Dokumentation schreiben

@mcp.tool()
def write_doc(doc_type: str, name: str, content: str) -> str:
    """Schreibt eine Dokumentations-Datei.

    doc_type: 'module', 'table', 'flow' oder 'note'
    name: Name der Datei (ohne .md)
    content: Markdown-Inhalt
    """
    valid_types = ["module", "table", "flow", "note"]
    if doc_type not in valid_types:
        return f"Ungültiger Typ. Erlaubt: {valid_types}"

    folder = DOCS_ROOT / f"{doc_type}s"
    folder.mkdir(parents=True, exist_ok=True)

    # Dateiname bereinigen
    safe_name = name.replace("::", "_").replace("/", "_")
    filepath = folder / f"{safe_name}.md"

    filepath.write_text(content)
    return f"Geschrieben: {filepath}"


@mcp.tool()
def read_doc(doc_type: str, name: str) -> str:
    """Liest eine existierende Dokumentations-Datei."""
    safe_name = name.replace("::", "_").replace("/", "_")
    filepath = DOCS_ROOT / f"{doc_type}s" / f"{safe_name}.md"

    if not filepath.exists():
        return f"Dokumentation nicht gefunden: {filepath}"

    return filepath.read_text()


@mcp.tool()
def list_docs(doc_type: str = None) -> str:
    """Listet vorhandene Dokumentation auf."""
    if doc_type:
        folder = DOCS_ROOT / f"{doc_type}s"
        if not folder.exists():
            return f"Keine Dokumentation vom Typ: {doc_type}"
        files = sorted(folder.glob("*.md"))
    else:
        files = sorted(DOCS_ROOT.rglob("*.md"))

    if not files:
        return "Keine Dokumentation vorhanden"

    return "\n".join(f.stem for f in files[:50])

Änderungen erkennen

import hashlib
import json

HASH_FILE = DOCS_ROOT / ".module_hashes.json"

@mcp.tool()
def check_changes(module_name: str) -> str:
    """Prüft ob sich ein Modul seit der letzten Dokumentation geändert hat."""
    path = module_name.replace("::", "/") + ".pm"
    full_path = PROJECT_ROOT / "lib" / path

    if not full_path.exists():
        return f"Modul nicht gefunden: {module_name}"

    # Aktuellen Hash berechnen
    content = full_path.read_text()
    current_hash = hashlib.md5(content.encode()).hexdigest()

    # Gespeicherten Hash laden
    hashes = {}
    if HASH_FILE.exists():
        hashes = json.loads(HASH_FILE.read_text())

    stored_hash = hashes.get(module_name)

    if stored_hash is None:
        return f"{module_name}: Noch nie dokumentiert"
    elif stored_hash != current_hash:
        return f"{module_name}: GEÄNDERT seit letzter Dokumentation"
    else:
        return f"{module_name}: Unverändert"


@mcp.tool()
def mark_documented(module_name: str) -> str:
    """Markiert ein Modul als dokumentiert (speichert Hash)."""
    path = module_name.replace("::", "/") + ".pm"
    full_path = PROJECT_ROOT / "lib" / path

    if not full_path.exists():
        return f"Modul nicht gefunden: {module_name}"

    content = full_path.read_text()
    current_hash = hashlib.md5(content.encode()).hexdigest()

    hashes = {}
    if HASH_FILE.exists():
        hashes = json.loads(HASH_FILE.read_text())

    hashes[module_name] = current_hash
    HASH_FILE.write_text(json.dumps(hashes, indent=2))

    return f"{module_name}: Als dokumentiert markiert"

Der Workflow

So stelle ich mir die tägliche Arbeit vor:

Neues Modul erschließen:

“Zeig mir das Modul Order::Validation

Claude liest den Code und erklärt die Struktur, die wichtigsten Funktionen, die Abhängigkeiten.

“Was macht die Funktion validate_payment?”

Detailliertere Erklärung.

“Schreib das als Dokumentation”

Claude erstellt ein Markdown-Dokument mit Zusammenfassung, wichtigen Funktionen, Abhängigkeiten, Beispielen.

Später, nach Code-Änderungen:

“Welche Module haben sich seit meiner Dokumentation geändert?”

Das Tool vergleicht Hashes und zeigt, wo ich nacharbeiten sollte.

Template für Module

Damit die Dokumentation einheitlich aussieht, ein Template:

# {Modulname}

**Pfad:** `lib/Order/Validation.pm`
**Letzte Dokumentation:** 2025-12-29
**Status:** Aktuell

## Zweck

Kurze Beschreibung, was das Modul macht.

## Wichtige Funktionen

### `validate_order($order)`

Validiert eine Bestellung vor dem Checkout.

- **Parameter:** `$order` - Order-Objekt
- **Rückgabe:** `1` bei Erfolg, `0` bei Fehlern
- **Seiteneffekte:** Setzt `$order->errors` bei Validierungsfehlern

### `validate_payment($payment_data)`

...

## Abhängigkeiten

- [[Order::Base]] - Basisklasse
- [[Payment::Gateway]] - Zahlungsanbindung
- [[Database::Orders]] - Datenbankzugriff

## Verwendet von

- [[Checkout::Process]]
- [[API::Orders]]

## Notizen

- Achtung: Validierung für Sonderfälle (Gutscheine > Warenwert) ist in `_handle_edge_cases`
- Die Funktion `_legacy_validate` wird noch für alte API-Clients gebraucht

Die [[...]] sind Obsidian-Links. So entsteht ein Netz aus verlinkten Dokumenten.

Was noch fehlt

Das ist der erste Ansatz. Was ich noch bauen will:

  • Datenbank-Schema – Tabellen dokumentieren, Beziehungen visualisieren
  • Suche über Dokumentation – “Wo ist die Gutschein-Logik dokumentiert?”
  • Diff-Ansicht – Was genau hat sich geändert?
  • Bulk-Check – Alle Module auf Änderungen prüfen ✓ implementiert

Erfahrungen bisher

Nach den ersten Wochen:

Was funktioniert:

  • Claude versteht Perl erstaunlich gut
  • Die Erklärungen sind meist korrekt und hilfreich
  • Die Verlinkung in Obsidian macht Navigation einfach

Was schwierig ist:

  • Sehr lange Module (>2000 Zeilen) – Context-Limit
  • Implizite Abhängigkeiten (dynamische Modul-Ladung)
  • Business-Kontext – den muss ich selbst einbringen

Wichtig: Claude erklärt den Code, aber ich muss verstehen und formulieren. Die Dokumentation ist meine, nicht generiert. Das Tool hilft, ersetzt aber nicht das eigene Denken.

Der Aufwand für den Server war überschaubar – ein Nachmittag. Der Nutzen ist täglich spürbar. Und das Beste: Die Dokumentation gehört mir, liegt als Markdown lokal, und kann bei Projektende übergeben werden.


Disclaimer: Der Code ist ein Proof of Concept aus meiner täglichen Arbeit. Funktioniert für meine Zwecke, keine Garantie für andere Setups. Die Pfade und Strukturen müssen natürlich angepasst werden.

GitHub: cuber-it/mcp_doku_tool


Aktualisiert: 2025-01-03