Troubleshooting: Wenn's mal wieder brennt

Ich habe eine schlechte und eine gute Nachricht. Die schlechte: Es wird schiefgehen. Die gute: Mit System findest Du jeden Fehler. Wenn man sich lange genug mit Entwicklung beschäftigt, sammeln sich einige Troubleshooting-Strategien an. Damit Du das Rad nicht komplett neu erfinden musst, teile ich hier meine Erfahrungen aus vielen Jahren "Warum geht das nicht?"

Die häufigsten Probleme (und ihre Lösungen)

Problem 1: Claude findet den Server nicht

Symptome:

Die systematische Lösung:

# 1. Prüfe ob mcp.json existiert und richtig ist
cat ~/.claude/mcp.json

# Sollte so aussehen:
{
  "mcpServers": {
    "content-metadata": {
      "command": "/var/www/mcp/content-metadata-server/venv/bin/python",
      "args": ["/var/www/mcp/content-metadata-server/server.py"],
      "env": {
        "DATENBANK_CONFIG_DIR": "/var/www/mcp/content-metadata-server/config"
      }
    }
  }
}

# 2. Teste ob der Server direkt startet
cd /var/www/mcp/content-metadata-server
source venv/bin/activate
python server.py

# 3. Prüfe Pfade (müssen absolut sein!)
ls -la /var/www/mcp/content-metadata-server/server.py
ls -la /var/www/mcp/content-metadata-server/venv/bin/python

# 4. Claude neu starten
# Komplett schließen und neu öffnen, nicht nur reload!

Die Pfade in mcp.json müssen ABSOLUT sein. Keine ~, keine relativen Pfade. Das vergesse ich selbst immer wieder.

Problem 2: Database Connection Failed

Der Klassiker. Hier meine Checkliste:

# debug_connection.py
#!/usr/bin/env python3
"""
Systematischer Connection-Debug
"""

import pymysql
import json
import sys

def debug_connection():
    print("Database Connection Debugger\n")
    print("="*40 + "\n")

    # 1. Config laden
    try:
        with open('config/credentials.json', 'r') as f:
            creds = json.load(f)
        print("[OK] Credentials geladen")
    except Exception as e:
        print(f"[FEHLER] Kann credentials.json nicht laden: {e}")
        return

    # 2. MariaDB läuft?
    import subprocess
    result = subprocess.run(
        ['systemctl', 'status', 'mariadb'],
        capture_output=True,
        text=True
    )
    if 'active (running)' in result.stdout:
        print("[OK] MariaDB läuft")
    else:
        print("[FEHLER] MariaDB läuft nicht!")
        print("   Fix: sudo systemctl start mariadb")
        return

    # 3. Port offen?
    import socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    result = sock.connect_ex(('127.0.0.1', 3306))
    if result == 0:
        print("[OK] Port 3306 erreichbar")
    else:
        print("[FEHLER] Port 3306 nicht erreichbar")
        print("   Check: sudo netstat -tlnp | grep 3306")
        return
    sock.close()

    # 4. User existiert?
    for user_type in ['content_reader', 'content_writer']:
        user_data = creds['database_users'][user_type]

        try:
            # Als root prüfen ob User existiert
            root_conn = pymysql.connect(
                host='localhost',
                user='root',
                password='YOUR_ROOT_PASSWORD'
            )
            with root_conn.cursor() as cursor:
                cursor.execute(
                    "SELECT User FROM mysql.user WHERE User = %s",
                    (user_type,)
                )
                if cursor.fetchone():
                    print(f"[OK] User {user_type} existiert")
                else:
                    print(f"[FEHLER] User {user_type} existiert nicht!")
                    print(f"   Fix: CREATE USER '{user_type}'@'localhost' IDENTIFIED BY 'password';")
            root_conn.close()
        except Exception as e:
            print(f"[WARNUNG] Kann User {user_type} nicht prüfen: {e}")

    # 5. Connection testen
    for user_type in ['content_reader', 'content_writer']:
        user_data = creds['database_users'][user_type]

        print(f"\nTeste {user_type}...")

        for host in ['localhost', '127.0.0.1', '::1']:
            try:
                conn = pymysql.connect(
                    host=host,
                    user=user_type,
                    password=user_data['password'],
                    database='karlkratz_de'
                )
                print(f"   [OK] {host}: Verbindung OK")
                conn.close()
                break
            except pymysql.Error as e:
                print(f"   [FEHLER] {host}: {e.args[1]}")

    print("\nZusammenfassung:")
    print("="*40)
    print("   Wenn immer noch Fehler: Check die Passwörter!")
    print("   Tipp: Passwörter mit Sonderzeichen in Anführungszeichen")

if __name__ == "__main__":
    debug_connection()

Problem 3: Rate Limit Exceeded

Zu viele Requests? So findest Du's raus:

# Check wer wie viele Requests macht
grep "Rate limit exceeded" logs/operations.log | \
    awk '{print $NF}' | sort | uniq -c | sort -rn

# Rate Limits temporär erhöhen (config/security.json)
{
  "rate_limiting": {
    "queries_per_minute": 200,  // War 50
    "burst_size": 50,           // War 20
    "cooldown_seconds": 30      // Neu: Cooldown-Zeit
  }
}

# Rate Limit Cache leeren (wenn's hakt)
redis-cli FLUSHDB  # Falls Du Redis verwendest

# Oder in Python:
from modules.security import SecurityValidator
validator = SecurityValidator('./config')
validator.query_timestamps = {}  # Reset
print("Rate limits zurückgesetzt")

Problem 4: Permissions Denied

Die ewige Permission-Hölle:

# permission_fixer.sh
#!/bin/bash

echo "Fixing MCP Server Permissions..."
echo "===================================="

MCP_DIR="/var/www/mcp/content-metadata-server"

# 1. Owner setzen
sudo chown -R www-data:www-data "$MCP_DIR"
echo "[OK] Owner gesetzt"

# 2. Directories
find "$MCP_DIR" -type d -exec chmod 755 {} \;
echo "[OK] Directory permissions"

# 3. Files
find "$MCP_DIR" -type f -exec chmod 644 {} \;
echo "[OK] File permissions"

# 4. Executables
chmod 755 "$MCP_DIR/server.py"
chmod 755 "$MCP_DIR/venv/bin/python"
echo "[OK] Executables"

# 5. Sensible Files
chmod 600 "$MCP_DIR/config/credentials.json"
chmod 600 "$MCP_DIR/config/database.json"
echo "[OK] Sensitive files protected"

# 6. Logs
chmod 755 "$MCP_DIR/logs"
chmod 666 "$MCP_DIR/logs/*.log" 2>/dev/null || true
echo "[OK] Log permissions"

# 7. SELinux (falls aktiv)
if command -v getenforce &> /dev/null && [ "$(getenforce)" != "Disabled" ]; then
    sudo semanage fcontext -a -t httpd_sys_rw_content_t "$MCP_DIR/logs(/.*)?"
    sudo restorecon -Rv "$MCP_DIR"
    echo "[OK] SELinux context"
fi

echo "[OK] All permissions fixed!"
echo "===================================="

Problem 5: Memory Leaks

Server frisst immer mehr RAM? Memory Leak hunting:

# memory_profiler.py
import tracemalloc
import psutil
import gc
import time

class MemoryMonitor:
    """Memory Leak Detector"""

    def __init__(self):
        tracemalloc.start()
        self.snapshots = []

    def take_snapshot(self, label=""):
        """Snapshot vom aktuellen Memory"""
        snapshot = tracemalloc.take_snapshot()
        self.snapshots.append((label, snapshot))

        # Process Memory Info
        process = psutil.Process()
        mem_info = process.memory_info()

        print(f"\nMemory Snapshot: {label}")
        print("-" * 40)
        print(f"   RSS: {mem_info.rss / 1024 / 1024:.2f} MB")
        print(f"   VMS: {mem_info.vms / 1024 / 1024:.2f} MB")

        # Top 10 Memory-Fresser
        if len(self.snapshots) > 1:
            prev_snapshot = self.snapshots[-2][1]
            top_stats = snapshot.compare_to(prev_snapshot, 'lineno')

            print("\n   Top 10 Änderungen:")
            for stat in top_stats[:10]:
                print(f"   {stat}")

    def find_leaks(self):
        """Sucht nach nicht freigegebenen Objekten"""
        gc.collect()

        print("\nLeak Detection:")
        print("-" * 40)

        # Objekte zählen
        objects = gc.get_objects()
        type_count = {}

        for obj in objects:
            obj_type = type(obj).__name__
            type_count[obj_type] = type_count.get(obj_type, 0) + 1

        # Top 20 Objekttypen
        sorted_types = sorted(
            type_count.items(),
            key=lambda x: x[1],
            reverse=True
        )

        print("   Top 20 Objekttypen:")
        for obj_type, count in sorted_types[:20]:
            print(f"   {obj_type}: {count}")

        # Connections prüfen
        connections = [
            obj for obj in objects
            if 'connection' in str(type(obj)).lower()
        ]
        print(f"\n   Offene Connections: {len(connections)}")

        return type_count

# Verwendung
monitor = MemoryMonitor()

# Baseline
monitor.take_snapshot("Start")

# Nach 100 Queries
for _ in range(100):
    db.select(limit=10)
monitor.take_snapshot("Nach 100 Queries")

# Nach 1000 Queries
for _ in range(900):
    db.select(limit=10)
monitor.take_snapshot("Nach 1000 Queries")

# Leak-Analyse
monitor.find_leaks()

Problem 6: Slow Queries

Queries dauern ewig? Zeit für Profiling:

# slow_query_analyzer.py
import time
import cProfile
import pstats
from io import StringIO

class QueryProfiler:
    """Findet langsame Queries"""

    def __init__(self, db_manager):
        self.db = db_manager
        self.slow_queries = []

    def profile_query(self, query_func, *args, **kwargs):
        """Profiled eine einzelne Query"""

        # Zeit messen
        start = time.perf_counter()

        # Profiling
        profiler = cProfile.Profile()
        profiler.enable()

        try:
            result = query_func(*args, **kwargs)
        finally:
            profiler.disable()

        end = time.perf_counter()
        duration = end - start

        # Wenn zu langsam, speichern
        if duration > 0.1:  # > 100ms
            s = StringIO()
            ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
            ps.print_stats(10)

            self.slow_queries.append({
                'query': str(query_func),
                'args': args,
                'duration': duration,
                'profile': s.getvalue()
            })

            print(f"[WARNUNG] Slow Query: {duration*1000:.2f}ms")
            print(f"   Function: {query_func.__name__}")

        return result

    def analyze_slow_queries(self):
        """Analysiert alle langsamen Queries"""

        if not self.slow_queries:
            print("[OK] Keine langsamen Queries gefunden!")
            return

        print(f"\n{len(self.slow_queries)} langsame Queries gefunden:\n")

        for i, sq in enumerate(self.slow_queries, 1):
            print(f"{i}. Query: {sq['query']}")
            print(f"   Duration: {sq['duration']*1000:.2f}ms")
            print(f"   Profile:\n{sq['profile']}")
            print("-" * 50)

        # Empfehlungen
        print("\nEmpfehlungen:")
        print("-" * 40)
        print("   1. Index prüfen: EXPLAIN SELECT ...")
        print("   2. Query optimieren (weniger JOINs?)")
        print("   3. Caching einbauen")
        print("   4. Connection Pooling nutzen")

Der systematische Debug-Prozess

Mein bewährter 7-Schritte-Prozess:

  1. Reproduzieren

    # Kann ich den Fehler reproduzieren?
    while true; do
        python test_query.py
        if [ $? -ne 0 ]; then
            echo "Fehler reproduziert!"
            break
        fi
        sleep 1
    done
  2. Isolieren

    # Minimales Beispiel erstellen
    # minimal_test.py
    from modules.database import DatabaseManager
    db = DatabaseManager('./config')
    db.connect()
    # Nur die eine Zeile die knallt
    result = db.select(where="id = ?", params=[999999])
  3. Loggen

    # Logging auf DEBUG
    import logging
    logging.basicConfig(level=logging.DEBUG)
    
    # Oder gezielt
    logger = logging.getLogger('modules.database')
    logger.setLevel(logging.DEBUG)
  4. Hypothese

    # Was könnte es sein?
    HYPOTHESEN = [
        "Connection timeout?",
        "Falscher Datentyp?",
        "Race Condition?",
        "Memory Leak?",
        "Permission Problem?"
    ]
    
    for h in HYPOTHESEN:
        print(f"Teste: {h}")
        # Spezifischer Test
  5. Testen

    # Fix anwenden und testen
    python -m pytest tests/test_specific_issue.py -v
    
    # Regression testen
    python -m pytest tests/ -v
  6. Dokumentieren

    # known_issues.md
    ## Issue: Connection Timeout bei großen Queries
    **Symptom**: Timeout nach 30 Sekunden
    **Ursache**: Default read_timeout zu klein
    **Lösung**: read_timeout in database.json erhöhen
    **Datum**: 2024-01-15
    **Gefixed in**: v1.2.3
  7. Vorbeugen

    # Test schreiben der das verhindert
    def test_large_query_timeout():
        """Regression Test für Timeout-Issue"""
        # Query die vorher timeout hatte
        result = db.select(limit=10000)
        assert result is not None

Emergency Fixes

Wenn's brennt und schnell gehen muss:

# emergency_fix.sh
#!/bin/bash

echo "EMERGENCY FIX PROTOCOL"
echo "========================"

# 1. Backup JETZT
echo "1. Creating emergency backup..."
tar -czf /tmp/mcp_emergency_$(date +%s).tar.gz /var/www/mcp/

# 2. Server stoppen
echo "2. Stopping server..."
sudo systemctl stop mcp-content-metadata

# 3. Rollback zur letzten funktionierenden Version
echo "3. Rolling back..."
cd /var/www/mcp/content-metadata-server
git stash  # Aktuelle Änderungen sichern
git checkout HEAD~1  # Eine Version zurück

# 4. Config zurücksetzen
echo "4. Resetting configs..."
cp config/database.json.backup config/database.json
cp config/security.json.backup config/security.json

# 5. Cache leeren
echo "5. Clearing caches..."
rm -rf __pycache__
rm -rf modules/__pycache__
find . -name "*.pyc" -delete

# 6. Server neu starten
echo "6. Restarting server..."
sudo systemctl start mcp-content-metadata

# 7. Health Check
echo "7. Health check..."
sleep 5
curl -f http://localhost:8080/health || {
    echo "[FEHLER] Server still broken!"
    echo "Rolling back further..."
    git checkout HEAD~2
    sudo systemctl restart mcp-content-metadata
}

echo "[OK] Emergency fix complete!"
echo "[WARNUNG] Remember to investigate root cause!"

Log-Analyse Tools

Logs sind Gold wert, wenn Du sie lesen kannst:

# log_analyzer.py
#!/usr/bin/env python3
"""
MCP Log Analyzer - Findet Muster in Logs
"""

import re
from collections import Counter, defaultdict
from datetime import datetime
import json

class LogAnalyzer:
    def __init__(self, log_file):
        self.log_file = log_file
        self.errors = []
        self.warnings = []
        self.patterns = defaultdict(int)

    def analyze(self):
        """Hauptanalyse"""

        with open(self.log_file, 'r') as f:
            for line in f:
                self.parse_line(line)

        self.print_report()

    def parse_line(self, line):
        """Eine Zeile parsen"""

        # JSON Logs
        if line.startswith('{'):
            try:
                data = json.loads(line)
                if data.get('level') == 'ERROR':
                    self.errors.append(data)
                elif data.get('level') == 'WARNING':
                    self.warnings.append(data)
            except:
                pass

        # Text Logs
        else:
            if 'ERROR' in line:
                self.errors.append(line)
            elif 'WARNING' in line:
                self.warnings.append(line)

        # Patterns suchen
        patterns = [
            r'timeout',
            r'connection.*failed',
            r'rate.*limit',
            r'permission.*denied',
            r'sql.*injection',
            r'memory',
            r'disk.*full'
        ]

        for pattern in patterns:
            if re.search(pattern, line, re.IGNORECASE):
                self.patterns[pattern] += 1

    def print_report(self):
        """Report ausgeben"""

        print("LOG ANALYSIS REPORT")
        print("=" * 50)

        print(f"\nErrors: {len(self.errors)}")
        if self.errors:
            # Top 5 Errors
            error_messages = [str(e) for e in self.errors[-5:]]
            for i, err in enumerate(error_messages, 1):
                print(f"   {i}. {err[:100]}...")

        print(f"\nWarnings: {len(self.warnings)}")

        print(f"\nPattern Matches:")
        for pattern, count in sorted(
            self.patterns.items(),
            key=lambda x: x[1],
            reverse=True
        ):
            print(f"   {pattern}: {count}x")

        # Zeitanalyse
        print(f"\nTemporal Analysis:")
        self.temporal_analysis()

    def temporal_analysis(self):
        """Wann treten Fehler auf?"""

        hour_counts = Counter()

        for error in self.errors:
            # Timestamp extrahieren
            if isinstance(error, dict):
                timestamp = error.get('timestamp', '')
            else:
                # Regex für Timestamp
                match = re.search(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', error)
                timestamp = match.group() if match else ''

            if timestamp:
                try:
                    dt = datetime.fromisoformat(timestamp)
                    hour_counts[dt.hour] += 1
                except:
                    pass

        if hour_counts:
            print("   Fehler nach Stunde:")
            for hour, count in sorted(hour_counts.items()):
                bar = '#' * (count // 2)
                print(f"   {hour:02d}:00  {bar} {count}")

# Verwendung
if __name__ == "__main__":
    analyzer = LogAnalyzer('/var/log/mcp/content-metadata.log')
    analyzer.analyze()

Die ultimative Troubleshooting-Checkliste

Wenn gar nichts mehr geht:

  1. Neustart: Seriously, hast Du's versucht?
  2. Logs lesen: ALLE Logs, nicht nur einen
  3. Configs prüfen: Tippfehler? JSON valid?
  4. Permissions: Linux favorite problem
  5. Disk voll? df -h
  6. RAM voll? free -h
  7. Port belegt? netstat -tlnp
  8. Firewall? ufw status
  9. SELinux? getenforce
  10. DNS? It's always DNS
  11. Zeit sync? date
  12. SSL Cert? openssl x509 -in cert.pem -noout -dates

Was ich gelernt habe

Nach tausenden Debug-Sessions:

Der beste Bug ist der, den Du durch gutes Design von vornherein vermeidest.

Zum Abschluss habe ich noch etwas Praktisches für Dich: Eine vollständige Code-Referenz mit allen Tools, Funktionen und Parametern. Das ist Dein Nachschlagewerk, wenn Du mal wieder vergessen hast, welche Parameter wohin gehören. Mit Quick Reference Cards und allen wichtigen Befehlen auf einen Blick. Zur vollständigen Code-Referenz und API-Dokumentation