flowki@club:~$ Coding, Automation & Security — auf Deutsch
FlowKI Club

Deine KI. Deine Community. Deine Vorteile.

  • KI Know-how
  • Prompts & Tools
  • Security & Privacy
  • Community Support
  • Exklusive Vorteile
Werde Teil der Community

Dein erstes MCP-Server in 60 Minuten — Schritt für Schritt ohne Framework-Frust

MCP-Server klingen technisch. Sind sie nicht. In einer Stunde hast du einen funktionierenden Server der Claude Code um deine eigenen Tools erweitert. Hier der Rezept-Artikel — keine Theorie, nur Code.

Dein erstes MCP-Server in 60 Minuten — Schritt für Schritt ohne Framework-Frust

Dieser Beitrag wurde mit KI-Unterstützung aus der angegebenen Quelle erstellt und vor der Veröffentlichung automatisch gegen sie abgeglichen. Nicht jeder Beitrag wird zusätzlich von Hand gelesen — wir prüfen stichprobenweise nach und kennzeichnen Korrekturen. Beruht ein Artikel auf einem selbst durchgeführten Test, weisen wir das ausdrücklich aus.

Was du am Ende hast

Nach 60 Minuten hast du einen Python-MCP-Server der eine echte Funktion hat: Er liest SQLite-Datenbanken und erlaubt Claude Code darüber Queries zu machen. Du startest claude in einem Projekt, und Claude hat plötzlich ein neues Tool namens query_sqlite zur Verfügung.

Das ist simpel, aber es ist der Baustein. Wenn du das hier verstehst, kannst du jeden internen API-Zugriff, jede Datenbank, jeden Custom-Workflow für dich als Tool bereitstellen.

Keine Theorie. Code und Schritte.

Voraussetzungen (5 Minuten)

Python 3.10 oder neuer. Ein Terminal. Claude Code installiert und funktionierend. Wenn du das noch nicht hast, schau in den Claude-Code-Guide — dort steht das Setup.

Projekt-Ordner aufsetzen:

mkdir mcp-sqlite-tutorial
cd mcp-sqlite-tutorial
python3 -m venv venv
source venv/bin/activate
pip install mcp

Das war der gesamte Setup. Das MCP-Python-SDK ist schlank, keine weiteren Dependencies.

Schritt 1 — Der Minimal-Server (15 Minuten)

Wir fangen mit dem einfachsten denkbaren MCP-Server an. Der tut noch nichts Sinnvolles aber zeigt die Struktur.

server.py anlegen:

import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

app = Server("sqlite-query-server")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="hello",
            description="Sagt Hallo, zum Testen",
            inputSchema={
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Dein Name"}
                },
                "required": ["name"],
            },
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "hello":
        return [TextContent(type="text", text=f"Hallo, {arguments['name']}!")]
    raise ValueError(f"Unknown tool: {name}")

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write, app.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

Das sind die beiden MCP-Primitives: list_tools beschreibt was dein Server kann, call_tool führt es aus. Der Rest ist Boilerplate.

Test: python server.py. Das Terminal scheint zu hängen. Das ist richtig — der Server wartet auf JSON-RPC-Nachrichten auf stdin. Mit Ctrl+C wieder raus.

Schritt 2 — An Claude Code anbinden (10 Minuten)

Claude Code muss wissen dass dein Server existiert. Das geht über eine Config-Datei.

Datei mcp_config.json im Projekt-Root:

{
  "mcpServers": {
    "sqlite-query": {
      "command": "python",
      "args": ["/absoluter/pfad/zu/deinem/server.py"],
      "env": {}
    }
  }
}

Den absoluten Pfad zu deiner server.py einsetzen. Claude Code starten mit dieser Config:

claude --mcp-config mcp_config.json

Im Claude Code wird angezeigt dass der MCP-Server sqlite-query geladen wurde. Du kannst das testen: "Benutze das hello-Tool und sag Hallo zu Rafi."

Claude ruft dein Tool auf, bekommt die Antwort zurück, zeigt sie dir. Das ist der vollständige Round-Trip.

Schritt 3 — Echte Funktionalität — SQLite-Queries (25 Minuten)

Jetzt machen wir den Server nützlich. Wir erweitern ihn um ein Tool das SQLite-Datenbanken abfragt.

Den bestehenden server.py erweitern:

import asyncio
import sqlite3
import json
from pathlib import Path
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

app = Server("sqlite-query-server")

@app.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="list_databases",
            description="Listet alle SQLite-Dateien im aktuellen Ordner",
            inputSchema={"type": "object", "properties": {}, "required": []},
        ),
        Tool(
            name="describe_schema",
            description="Zeigt Tabellen und Spalten einer SQLite-DB",
            inputSchema={
                "type": "object",
                "properties": {"db_path": {"type": "string"}},
                "required": ["db_path"],
            },
        ),
        Tool(
            name="query_sqlite",
            description="Führt eine SELECT-Query auf einer SQLite-DB aus. NUR SELECT erlaubt.",
            inputSchema={
                "type": "object",
                "properties": {
                    "db_path": {"type": "string"},
                    "query": {"type": "string"},
                },
                "required": ["db_path", "query"],
            },
        ),
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "list_databases":
        dbs = [str(p) for p in Path(".").glob("**/*.db") if p.is_file()]
        return [TextContent(type="text", text=json.dumps(dbs, indent=2))]

    elif name == "describe_schema":
        db_path = arguments["db_path"]
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
        tables = [row[0] for row in cursor.fetchall()]
        schema = {}
        for table in tables:
            cursor.execute(f"PRAGMA table_info({table})")
            schema[table] = [
                {"name": col[1], "type": col[2], "nullable": not col[3]}
                for col in cursor.fetchall()
            ]
        conn.close()
        return [TextContent(type="text", text=json.dumps(schema, indent=2))]

    elif name == "query_sqlite":
        db_path = arguments["db_path"]
        query = arguments["query"].strip()
        if not query.lower().startswith("select"):
            return [TextContent(
                type="text",
                text="FEHLER: Nur SELECT-Queries erlaubt."
            )]
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        try:
            cursor.execute(query)
            rows = cursor.fetchmany(100)  # max 100 rows pro Response
            cols = [d[0] for d in cursor.description]
            result = [dict(zip(cols, row)) for row in rows]
            return [TextContent(type="text", text=json.dumps(result, indent=2))]
        except Exception as e:
            return [TextContent(type="text", text=f"FEHLER: {str(e)}")]
        finally:
            conn.close()

    raise ValueError(f"Unknown tool: {name}")

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write, app.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

Drei Tools: Datenbanken im Ordner finden, Schema einer DB anzeigen, Query ausführen. Die Query ist beschränkt auf SELECT — kein DELETE, kein DROP. Das ist ein erster Sicherheits-Filter. In Produktion müsstest du mehr machen.

Test-DB erzeugen um es zu probieren:

python -c "
import sqlite3
conn = sqlite3.connect('test.db')
conn.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)')
conn.execute('INSERT INTO users (name, email) VALUES (?, ?)', ('Rafi', 'rafi@example.com'))
conn.execute('INSERT INTO users (name, email) VALUES (?, ?)', ('Anna', 'anna@example.com'))
conn.commit()
conn.close()
"

Claude Code mit der MCP-Config neu starten:

claude --mcp-config mcp_config.json

Und dann: "Welche SQLite-Datenbanken liegen hier im Ordner?"

Claude ruft list_databases auf, findet test.db, zeigt die Liste. Danach: "Zeig mir das Schema von test.db." Claude nutzt describe_schema. Dann: "Wie heißt der User mit ID 2?" Claude baut eine SELECT-Query, ruft query_sqlite auf, zeigt das Ergebnis.

Das ist die komplette Kette. Von deinem Python-Code bis zu Claude-generiertem SQL.

Schritt 4 — Stolperfallen die mir begegnet sind (5 Minuten)

Drei Sachen wo ich hängen geblieben bin, damit du Zeit sparst:

Erste: MCP-Logs gehen auf stderr. Wenn dein Server crasht, siehst du in Claude Code nur "Server nicht antwortend". Füge am Anfang deines Servers hinzu:

import logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    stream=sys.stderr,  # wichtig — nicht stdout
)

Claude Code zeigt stderr-Logs in seinem Debug-Output an.

Zweite: Absolute Pfade im Config-File. Relative Pfade funktionieren nicht. Klingt dumm, ist aber oft der Grund warum "es geht auf einmal nicht."

Dritte: Der Server muss ein Prozess sein. Wenn du ihn zur Entwicklung neu startest, musst du Claude Code neu starten damit die Verbindung frisch ist. Mache ich inzwischen automatisch.

Was du jetzt damit machen kannst

Der SQLite-Server ist ein Beispiel. Die echten Anwendungen ergeben sich aus deinem Kontext:

  • Postgres-Query-Tool für euer internes Backend. Claude kann direkt gegen Prod-Reports querien (read-only, mit sauberer Rechtekontrolle).
  • Jira-Ticket-Tool das Tickets liest und zusammenfasst. Spart dir tägliche Status-Updates.
  • Internal-API-Wrapper der Claude Zugriff auf deinen CRM-Endpoint gibt.
  • Repo-Analyse-Tool das git-Statistiken berechnet und sie Claude verfügbar macht.

Die Architektur ist immer gleich: list_tools beschreibt, call_tool führt aus. Was drin passiert ist dein Code.

Sicherheit — bevor du das in Produktion nutzt

Drei Regeln die ich einhalte wenn ich MCP-Server in produktiven Setups einsetze:

  • Read-only by default. Schreibende Operationen erfordern einen expliziten Boolean-Parameter im Input und einen Confirmation-Schritt.
  • Queries allowlistet. Statt "jede SQL-Query" nur ausgewählte Query-Types zulassen. "SELECT COUNT" ja, beliebiges JOIN nein.
  • Logging aller Aufrufe. Welches Tool wann mit welchen Argumenten aufgerufen wurde. Für Audit und Debug.

Der Tutorial-Server hier ist bewusst simpel. In einem echten Setup würden noch zwei-drei Layer Sicherheit dazu kommen.

Weiterlesen

Für das Konzept dahinter siehe MCP-Server auf Deutsch. Den Überblick mit Checkliste, Sprachwahl und Sicherheits-Minimum findest du unter MCP-Server bauen. Für die Tool-Auswahl siehe Cursor vs. Claude Code.

Eigenen MCP-Server gebaut, Stolperfallen getroffen, Success-Stories? Im Discord Zone "Coding & Projekte" — dort sammeln wir Patterns und teilen Server die für andere nützlich sein könnten.

TeilenXLinkedInWhatsApp
FAQ

Häufige Fragen

Wie lange dauert es, einen eigenen MCP-Server zu bauen?

Rund 60 Minuten bis zu einem funktionierenden Server. Die Anleitung teilt die Zeit auf: 5 Minuten Setup, 15 Minuten für den Minimal-Server mit einem hello-Tool, 10 Minuten für die Anbindung an Claude Code, 25 Minuten für die drei SQLite-Tools und 5 Minuten für die typischen Stolperfallen. Danach kann Claude Code über deinen Server Abfragen auf SQLite-Datenbanken ausführen.

Was brauche ich, um einen MCP-Server in Python zu schreiben?

Python 3.10 oder neuer, ein Terminal und eine funktionierende Claude-Code-Installation. Mehr nicht. Du legst einen Projektordner an, erstellst ein virtuelles Environment und installierst das Paket mcp per pip. Das MCP-Python-SDK ist schlank und zieht keine weiteren Dependencies nach. Das gesamte Setup dauert etwa fünf Minuten.

Wie binde ich einen MCP-Server an Claude Code an?

Über eine Datei mcp_config.json im Projekt-Root. Dort trägst du unter mcpServers einen Eintrag mit command (python) und args (absoluter Pfad zu deiner server.py) ein. Danach startest du Claude Code mit dem Schalter --mcp-config und dem Pfad zur Config. Claude Code zeigt an, dass der Server geladen wurde, und du kannst seine Tools direkt im Chat ansprechen.

Welche Funktionen muss ein MCP-Server mindestens haben?

Zwei: list_tools und call_tool. list_tools beschreibt, was dein Server kann, samt Name, Beschreibung und JSON-Schema für die Eingaben. call_tool führt den Aufruf aus und gibt das Ergebnis als TextContent zurück. Alles andere ist Boilerplate, vor allem eine main-Funktion, die den Server über stdio_server an stdin und stdout hängt.

Warum antwortet mein MCP-Server in Claude Code nicht?

Drei Ursachen decken die meisten Fälle ab. Erstens relative Pfade in der Config, denn MCP braucht absolute Pfade. Zweitens fehlendes Logging: MCP-Logs laufen über stderr, nicht über stdout. Konfigurierst du logging auf stderr, zeigt Claude Code die Ausgabe im Debug-Output. Drittens ein alter Prozess, denn nach jeder Änderung am Server musst du Claude Code neu starten.

Ist ein selbstgebauter MCP-Server sicher genug für den Produktivbetrieb?

Der Tutorial-Server nicht, er ist bewusst simpel gehalten. Sein einziger Schutz ist ein Filter, der nur SELECT-Abfragen durchlässt. Für produktive Setups gelten drei Regeln: read-only als Standard, wobei schreibende Aufrufe einen expliziten Parameter und einen Bestätigungsschritt brauchen, eine Allowlist erlaubter Query-Typen statt beliebigem SQL, und Logging jedes Tool-Aufrufs für Audit und Debugging.

Weiterlesen

Mehr aus Kategorie Coding mit KI

Alle Artikel der Kategorie →
MODELLE

Ollama, MLX, llama.cpp — welches Setup für welches Budget

5 min · 16. Apr.

CODING

MCP-Server auf Deutsch — was es ist und warum du es brauchst

5 min · 14. Apr.

CODING

Cursor vs Claude Code: Welches KI-Coding-Tool passt zu dir?

11 min · 13. Aug.