Von null auf produktiv: MCP-Grundlagen, Python-Implementierung, Orchestrierung und Praxis-Projekte
- 0–5 Min: Ziele, Setup-Check, Kontext
- 5–20 Min: Teil 1 – MCP-Grundlagen (Konzepte, Nachrichtenfluss)
- 20–35 Min: Teil 2 – Server entwickeln (Minimal-Server + PDF-Tools)
- 35–45 Min: Teil 3 – Client bauen (Single/Multi-Server, Fehlerbehandlung)
- 45–55 Min: Teil 4 – LLM-Orchestrierung (Entscheidungs-Loop, Parser)
- 55–60 Min: Teil 5 – Fallstricke, Capstone-Preview, Q&A
- ✅ MCP-Konzepte verstehen und einordnen
- ✅ MCP-Server mit Python entwickeln
- ✅ MCP-Clients implementieren
- ✅ LLM-gesteuerte Orchestrierung nutzen
Wir nutzen ein praxisnahes Uni-Szenario: 5 Notenmeldungen verschiedener Kurse liegen als PDFs lokal vor und bleiben vertraulich. Das LLM (z. B. Ollama lokal) hat keinen direkten Zugriff auf diese Daten. Wenn ein Dozent oder Studierender eine Frage stellt (z. B. "Welche Studierenden haben Datenbanken bestanden?"), ruft das LLM dynamisch passende MCP-Tools auf (Discovery → Auswahl → Ausführung), um lokal Notendaten zu laden und die Antwort zu bilden. So zeigen wir Datenschutz, lokalen Bezug und schlanke Orchestrierung ohne überflüssige Implementierungsdetails.
{{1-3}}
KI-Perspektive: Warum keine RAG Lösung? Wir würden außerhalb des LLM einen Vektorstore mit Embeddings der PDFs pflegen, eine Suchfunktion implementieren und bei Fragen relevante Passagen extrahieren. Das LLM würde dann nur diese Passagen sehen. MCP ermöglicht hingegen direkten, strukturierten Zugriff auf die Originaldokumente via Tools/Resources, ohne Vektorstore oder Retrieval-Logik.
{{2-3}}
Softwareengeniering-Perspektive: Warum kann ich aus dem LLM nicht einfach irgendeine API nutzen? Die Bandbreite der APIs ist beliebig groß und damit nicht pauschal abdeckbar. MCP bietet eine einheitliche Schnittstelle mit klaren Contracts (Schemas), Tool-Discovery und strukturierter Kommunikation, die speziell für die Interaktion zwischen LLMs und externen Systemen entwickelt wurde.
Das Model Context Protocol (MCP) ist ein offener Standard und Open-Source-Framework, das vom US-Unternehmen Anthropic entwickelt wurde, um die Integration und den Datenaustausch zwischen künstlicher Intelligenz (KI), insbesondere großen Sprachmodellen (LLMs), und externen Tools, Systemen sowie Datenquellen zu standardisieren.
MCP wurde offiziell am 25. November 2024 vorgestellt und als Open-Source-Projekt veröffentlicht.
https://www.anthropic.com/news/model-context-protocol
Seit ein paar Tagen (Dez. 2025) "gehört" MCP der neu gegründeten Agentic AI Foundation, als Teil der Linux Foundation Link
- Reproduzierbarkeit: Gleiche Eingaben → gleiche Tool-Aufrufe → determinierbare Outputs (JSON, Schemas).
- Governance & Audit: Jeder Tool-Call ist nachvollziehbar (Name, Schema, Parameter). Ermöglicht Audit-Trails und Compliance bei vertraulichen Daten.
- Kapselung & Wiederverwendung: Einmal implementierte Fähigkeiten (z. B. PDF-Extraktion) werden von mehreren Clients/LLMs genutzt, ohne Code-Duplikate.
- Testbarkeit: Tools sind kleine, isolierte Einheiten mit klaren Contracts → Unit-/Integrationstests werden einfach.
- Sicherheit: Eingaben werden gegen
inputSchemavalidiert; Server kontrolliert Dateizugriffe/Whitelists, verhindert Rohdaten-Leaks. - Portabilität: Transport-agnostisch (stdio/HTTP/WebSocket); Server können lokal, on-prem, oder im Cluster laufen.
- Kosten- & Latenzkontrolle: LLM-Aufrufe nur, wenn nötig; teure Schritte (LLM) von günstigen (Parsing, I/O) trennen.
- Versionierung: Tool-APIs können versioniert werden (
v1,v2) bei gleichzeitiger Koexistenz. - Capability-Discovery: LLMs entscheiden zur Laufzeit, welche Tools nötig sind; neue Tools werden automatisch sichtbar.
- Orchestrierbarkeit: Ermöglicht Planen, Retries, Alternativen (Fallback-Tools), ohne monolithische Pipelines.
- Monetarisierung: Tools/Resources können als eigenständige Services angeboten werden (ähnlich wie APIs).
https://www.aibase.com/de/news/18202
MCP definiert, wie Client und Server kommunizieren:
- Standardisierte Nachrichten (JSON-RPC)
- Klare Schnittstellen (Tools, Resources, Prompts)
- Transport-unabhängig (stdio, HTTP, WebSocket)
MCP wird gern als USB- oder HTTP-Standard für LLMs bezeichnet, da es eine einheitliche Schnittstelle für die Kommunikation zwischen LLMs und externen Systemen bietet.
Frage: Welche Parallelen sehen Sie zu ROS2 und ROS1 in Bezug auf Protokolle und Nachrichtenformate?
- MCP Host: KI-Anwendung, die mehrere MCP-Clients verwaltet (z. B. IDE, Desktop-App).
- MCP Client: 1:1-Verbindung zu genau einem Server; führt Listen-/Aufrufe aus.
- MCP Server: Stellt Kontext und Fähigkeiten bereit (Tools, Resources, Prompts). Lokal (STDIO) oder remote (HTTP/SSE).
graph LR
subgraph AI_Host["AI Host<br/>(Claude, ChatGPT, Cursor)"]
AI[AI]
MCP_Client[MCP Client]
AI -->|uses| MCP_Client
end
subgraph MCP_Server["MCP Server<br/>(Adapter for Data Source)"]
SERVER[SERVER]
end
subgraph Data_Source["Data Source<br/>(Files, Databases, APIs)"]
DB[(Database)]
FILE[📄 PDF<br/>File]
end
MCP_Client -->|Request via MCP<br/>'Give me file report.pdf'| SERVER
SERVER -->|Response via MCP| MCP_Client
SERVER -->|Fetch/Execute| Data_Source
Data_Source -->|Return Data| SERVER
classDef hostStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef serverStyle fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef dataStyle fill:#fff3e0,stroke:#f57c00,stroke-width:2px
class AI_Host hostStyle
class MCP_Server serverStyle
class Data_Source dataStyle
Frage: Welche Varianten können Sie sich vorstellen, um den MCP-Client und -Server zu implementieren?
MCP trennt bewusst zwischen Was (Datenebene) und Wie (Transportebene).
Data Layer (Inhaltsebene) - "Was macht das Tool?"
- Definiert die Bedeutung der Nachrichten: Welche Strukturen gibt es (Tools, Resources, Prompts)?
- Regelt den Ablauf: Initialisierung, Capability Negotiation, Tool-Aufrufe, Benachrichtigungen.
- Wichtig: Ist unabhängig davon, wie die Daten übertragen werden.
Praxis-Beispiel: Ein get_grades Tool bleibt identisch, egal ob per STDIO oder HTTP aufgerufen.
Transport Layer (Übertragungsebene) - "Wie erreiche ich den Server?"
- Kümmert sich um die Mechanik der Kommunikation: Wie werden Nachrichten gesendet, empfangen, gesichert?
- Regelt Authentifizierung, Verschlüsselung, Verbindungsaufbau, Fehlerbehandlung.
Praxis-Beispiel: Der gleiche Server kann lokal (STDIO) für Entwicklung oder remote (HTTP) für Produktion laufen.
Analogie zu ROS: Ähnlich wie ROS-Messages (
sensor_msgs) über DDS, TCP oder WebSocket übertragen werden können, funktionieren MCP-Tools über verschiedene Transporte.
Praktischer Nutzen:
# Server-Code (bleibt gleich):
@app.call_tool()
async def call_tool(name, args):
return get_grades(args["course_id"])
# Transport wechseln (nur 1 Zeile):
stdio_server() # Lokal für Tests
# oder
sse_server() # Remote für ProduktionWir unterschieden drei Haupttypen von MCP-Primitiven:
- Tools
- Resources
- Prompts
Im folgenden werden diese Primitiven im Detail erklärt.
Tools sind Funktionen, die spezifische Aufgaben ausführen. Jedes Tool hat:
- Discovery:
tools/list - Aufruf:
tools/callmitnameundarguments(validiert gegen das JSON SchemainputSchema).
{
"name": "extract_pdf_text",
"description": "Extrahiert Text aus PDF",
"inputSchema": {
"type": "object",
"properties": {
"file_path": {"type": "string"}
}
}
}- Validierung: Server prüft Eingaben, bevor Code ausgeführt wird
- Dokumentation: LLM weiß, welche Parameter möglich sind
- Sicherheit: Verhindert ungültige/gefährliche Eingaben
- Typsicherheit: String, Number, Boolean, Object, Array
- UI-Generierung: Tools können automatisch Formulare erzeugen
Tool(
name="get_grades",
description="Ruft Notenliste eines Kurses ab",
inputSchema={
"type": "object",
"properties": {
"course_id": {
"type": "string",
"description": "ID des Kurses (z.B. 'datenbanken_ws2024')",
"pattern": "^[a-z_0-9]+$" # Nur Kleinbuchstaben, Unterstriche, Zahlen
}
},
"required": ["course_id"] # Pflichtparameter
}
)Was passiert bei Tool-Aufruf?
sequenceDiagram
participant C as Client
participant S as Server
C->>S: tools/call("get_grades", {"course_id": "datenbanken_ws2024"})
Note over S: 1. Validiere gegen inputSchema
Note over S: ✓ course_id ist string
Note over S: ✓ course_id ist required
Note over S: ✓ Pattern matched
Note over S: 2. Führe Tool aus
S-->>C: {success: true, data: [...]}
C->>S: tools/call("get_grades", {"course": 123})
Note over S: 1. Validiere gegen inputSchema
Note over S: ✗ "course" existiert nicht
Note over S: ✗ "course_id" fehlt (required)
S-->>C: {error: "Missing required parameter: course_id"}
Best Practices:
- ✅ Beschreibungen für alle Properties (LLM-Verständnis)
- ✅ Patterns für Strings (Sicherheit)
- ✅ Min/Max für Numbers (Plausibilitätsprüfung)
- ✅ Enums statt Freitext (weniger Fehler)
- ✅ Required vs. Optional klar trennen
- ❌ Keine komplexen Verschachtelungen (LLM-Verwirrrung)
- ❌ Keine zu restriktiven Patterns (Flexibilität)
Resources sind lesbare Datenquellen, die LLMs als Kontext nutzen können. Im Gegensatz zu Tools (Aktionen) sind Resources passive Datenprovider.
Unterschied: Tools vs. Resources
| Aspekt | Tools (Aktionen) | Resources (Daten) |
|---|---|---|
| Zweck | Aktion ausführen | Daten bereitstellen |
| Beispiel | calculate_gpa() |
courses://list |
| Zustand | Kann ändern | Read-only |
| Caching | Nein | Ja |
| Komplexität | Logik ausführen | Daten liefern |
Wann Resources statt Tools?
✅ Resource verwenden wenn:
- Statische/semi-statische Daten (z.B. Kursliste, Schema)
- Kontext für LLM-Entscheidungen (z.B. verfügbare Optionen)
- Wiederholter Zugriff auf gleiche Daten (Caching!)
- Read-only Zugriff ausreichend
❌ Tool verwenden wenn:
- Komplexe Berechnungen nötig (z.B. GPA berechnen)
- Zustand ändert sich (z.B. Note hinzufügen)
- Parametrisierte Abfragen (z.B. Noten filtern nach Kurs)
- Externe API-Aufrufe
Praxisbeispiel aus mcp_grades:
# Resource: Kursliste (statisch, cacheable)
Resource(
uri="courses://all",
name="Verfügbare Kurse",
description="Liste aller Kurse im System",
mimeType="application/json"
)
# LLM fragt: "Welche Kurse gibt es?"
# → Direkter Zugriff ohne Tool-Call!
# vs. Tool: Noten abrufen (dynamisch, parametrisiert)
Tool(
name="get_grades",
description="Ruft Noten eines spezifischen Kurses ab",
inputSchema={
"properties": {"course_id": {"type": "string"}}
}
)
# LLM fragt: "Zeige Noten von Datenbanken"
# → Muss Tool aufrufen mit ParameterDiscovery und Zugriff:
- Discovery:
resources/list - Lesen:
resources/read(uri)
WICHTIG: MCP-Prompts sind NICHT die Prompts, die Sie an ein LLM schicken! Es sind wiederverwendbare Prompt-Templates, die der Server bereitstellt.
Was sind MCP-Prompts?
MCP-Prompts sind vordefinierte Interaktionsmuster, die Clients vom Server abrufen können. Sie funktionieren wie Makros oder Vorlagen für häufige Aufgaben.
Kernidee: Prompts sind Orchestrierungs-Pattern, die Tools und Resources zusammenführen:
Resources (Daten) + Tools (Aktionen) + Prompts (Workflow) = Komplette Lösung
Analogie: Kochen
- Resources = Zutaten (Daten wie Kurslisten, Notendaten)
- Tools = Küchengeräte (Funktionen wie
calculate_gpa,get_grades) - Prompts = Rezept (Schritt-für-Schritt Anleitung, die Zutaten + Geräte kombiniert)
Ein Prompt sagt dem LLM: "Hole diese Daten (Resource), verarbeite sie mit diesem Tool, kombiniere die Ergebnisse so-und-so."
Unterschied zu LLM-Prompts:
| Aspekt | LLM-Prompt (gewöhnlich) | MCP-Prompt (Template) |
|---|---|---|
| Was ist das? | Text, den Sie an LLM senden | Wiederverwendbare Vorlage vom Server |
| Wo definiert? | Client-Code oder User-Input | Server als MCP-Primitive |
| Zweck | Instruktion für LLM | Standardisierte Interaktionsmuster |
| Beispiel | "Analysiere dieses Dokument" | Template: "Analysiere {doc_type} mit Fokus auf {aspect}" |
Wann MCP-Prompts verwenden?
✅ Sinnvoll für:
- Wiederkehrende Analyseaufgaben mit Variationen
- Standardisierte Workflows (z.B. "Code Review", "Bug Report")
- Vorlagen für verschiedene Dokumenttypen
- Best-Practice-Patterns für bestimmte Domänen
❌ Nicht sinnvoll für:
- Einmalige, freie Konversationen
- Dynamisch generierte Prompts
- Einfache Tool-Aufrufe
Praxisbeispiel aus Notenverwaltung:
# MCP-Prompt (Template vom Server):
Prompt(
name="course_analysis",
description="Analysiert einen Kurs nach verschiedenen Kriterien",
arguments=[
{"name": "course_id", "description": "ID des Kurses", "required": True},
{"name": "focus", "description": "Analysefokus", "required": False}
],
template="""Analysiere den Kurs {course_id}.
Fokus: {focus}
Führe folgende Schritte aus:
1. Rufe Notendaten mit get_grades(course_id="{course_id}") ab
2. Berechne Statistiken (Durchschnitt, Median, Bestehensquote)
3. Identifiziere Ausreißer (sehr gute/schlechte Noten)
4. Erstelle Zusammenfassung mit Empfehlungen
Ausgabeformat: Strukturierter Report als Markdown."""
)
# Client fragt: "Analysiere Datenbanken"
# → MCP Host ruft Prompt ab und ersetzt Variablen:
# course_id="datenbanken_ws2024", focus="Bestehensquote"
# → Sendet gefülltes Template an LLM
# → LLM folgt den Schritten im TemplateDiscovery und Nutzung:
# Discovery
prompts = await session.list_prompts()
# → [{"name": "course_analysis", "description": "...", "arguments": [...]}]
# Abrufen mit Argumenten
prompt = await session.get_prompt(
name="course_analysis",
arguments={
"course_id": "datenbanken_ws2024",
"focus": "Bestehensquote"
}
)
# → Gibt gefülltes Template zurück
# An LLM senden
response = llm.chat(messages=[
{"role": "system", "content": prompt.messages[0].content}
])Workflow-Beispiel:
sequenceDiagram
participant U as User
participant C as Client
participant S as Server
participant L as LLM
U->>C: "Analysiere Kurs Datenbanken"
C->>S: prompts/get("course_analysis", {course_id: "datenbanken"})
S-->>C: Template mit gefüllten Variablen
C->>L: Template als System-Prompt senden
Note over L: LLM folgt Schritten im Template
L->>C: Tools aufrufen (get_grades, calculate_stats)
C->>S: tools/call(...)
S-->>C: Notendaten
C->>L: Ergebnisse zurück an LLM
L-->>C: Strukturierter Report
C-->>U: Analyse-Ergebnis
Best Practices:
✅ Gute MCP-Prompts:
- Klare Schrittfolgen
- Parametrisierbar (Variablen)
- Domänen-spezifisch
- Wiederverwendbar
❌ Schlechte MCP-Prompts:
- Zu allgemein ("Analysiere etwas")
- Keine Variablen (nicht parametrisierbar)
- Zu komplex (über 1000 Zeichen)
- Einmalige Nutzung
Wann Prompts NICHT verwenden:
In vielen Fällen sind Tools direkter und besser:
# ❌ Kompliziert: Prompt + LLM + Tool
Prompt("analyze_course", template="...")
→ LLM interpretiert
→ Ruft Tools auf
# ✅ Einfacher: Direkt Tool mit klarem Zweck
Tool("analyze_course", inputSchema={...})
→ Direkte AusführungDieser Ablauf fasst den MCP‑Verbindungslebenszyklus zusammen:
- Handshake:
initializeverhandelt Protokollversion und Fähigkeiten (capabilities), inkl. Identität (clientInfo,serverInfo). - Discovery:
tools/listliefert verfügbare Funktionen und Metadaten für dynamische Tool‑Auswahl. - Execution:
tools/callmit schema‑validiertenargumentserzeugt strukturiertecontent‑Antworten. - Notifications: Server teilt Änderungen mit (z. B.
notifications/tools/list_changed), Client aktualisiert per erneutemtools/list. - Termination: Sauberes Schließen verhindert offene Sessions und inkonsistente Zustände.
sequenceDiagram
actor U as Nutzer/LLM-App
participant C as MCP Client
participant S as MCP Server
U->>C: Verbinde zu Server
Note over C,S: Handshake
C->>S: initialize(protocolVersion, capabilities, clientInfo)
S-->>C: initialize result(serverInfo, capabilities)
C-->>S: notifications/initialized
Note over C,S: Discovery
C->>S: tools/list
S-->>C: tools = [name, description, inputSchema]
Note over C,S: Execution
C->>S: tools/call(name, arguments)
Note over S: Validierung gegen inputSchema
S-->>C: content = [{type: "text", text: JSON}]
Note over C,S: Notifications
S-->>C: notifications/tools/list_changed
C->>S: tools/list (Refresh)
Note over C,S: Termination
U->>C: Sitzung beenden
C-->>S: Termination/Close Connection
-
Ohne MCP: Der Nutzer fragt direkt das LLM.
- Frage: "Welche Farbe hat der Himmel?"
- Verhalten: Das LLM beantwortet aus allgemeinem Weltwissen, kein Tool‑Aufruf nötig.
-
Mit MCP: Der Nutzer stellt eine universitätsspezifische Frage, die lokale Notendaten braucht.
- Frage: "Welche Studierenden haben im Kurs Datenbanken eine 1.0 erreicht?"
- Verhalten: Das LLM erkennt fehlendes Wissen und nutzt MCP‑Tools, um lokal Notendaten zu laden und zu analysieren.
sequenceDiagram
actor U as Nutzer
participant H as MCP Host (KI‑App)
participant C as MCP Client
participant S as MCP Server
rect rgb(240,240,240)
Note over U,H: Beispiel 1 – Ohne MCP
U->>H: "Welche Farbe hat der Himmel?"
H-->>U: "Blau" (direktes LLM‑Wissen)
end
rect rgb(230,245,255)
U->>H: "Welche Studierenden haben Datenbanken mit 1.0 bestanden?"
H->>C: Starte Client / Verbindung
C->>S: initialize()
S-->>C: capabilities
H->>C: Discovery anstoßen
C->>S: tools/list
S-->>C: [list_courses, get_grades, get_student_transcript, calculate_gpa]
C-->>H: Tools registrieren (Name, Beschreibung, Schema)
H->>C: Tool‑Aufruf vorbereiten
C->>S: tools/call("get_grades", {course_id: "datenbanken_ws2024"})
Note over S: Noten aus PDF extrahieren
S-->>C: {success: true, students: [{matrikel: "12345", name: "Max Mustermann", grade: 1.0}, …]}
C-->>H: Ergebnis an Host/LLM weiterreichen
H->>C: Folgeaktion (Filterung)
Note over H: LLM filtert Studierende mit Note 1.0
C-->>H: Kontext aktualisieren und Antwort formulieren
H-->>U: "Folgende Studierende haben eine 1.0: Max Mustermann (12345)"
end
- Strukturierte Outputs: Immer valides JSON liefern; reine Textantworten erschweren Parsing. Best Practice: Output-Schema in Tool-Description dokumentieren (siehe
get_gradesin mcp_grades als Beispiel). JSON ermöglicht: Maschinenlesbarkeit, Typsicherheit, Erweiterbarkeit, automatische Validierung. - Klare
inputSchema: Fehlende/optionale Felder sauber definieren, Defaults dokumentieren. - Tool-Discovery testen:
list_toolsmuss konsistent sein, sonst scheitert die Orchestrierung. - Lokale Pfade: Absolute vs. relative Pfade konsistent halten; Zugriff auf vertrauliche Daten absichern.
- LLM-Grenzen: Prompt-Länge begrenzen; bei Unsicherheit dynamisch MCP verwenden
- Fehlerbehandlung: Eindeutige Fehlermeldungen und
success: falsemit Ursache liefern. - Trennung von Kurs vs. Code: Didaktik im Kurs, ausführbare Beispiele in
examples/.
Anthropic und die Community stellt eine Sammlung von MCP-Servern bereit, die als Referenzimplementierungen dienen:
Repository: github.com/modelcontextprotocol/servers
Wichtige Server:
- filesystem: Sicherer Dateizugriff mit Whitelisting
- github: GitHub API Integration (Repos, Issues, PRs)
- gitlab: GitLab API Integration
- google-drive: Google Drive Zugriff
- slack: Slack-Integration (Nachrichten, Kanäle)
- postgres: PostgreSQL Datenbank-Queries
- sqlite: SQLite Datenbank-Zugriff
- brave-search: Web-Suche via Brave API
- fetch: HTTP-Requests (Web-Scraping, APIs)
- puppeteer: Browser-Automation
Achtung: Für produktive Systeme bestehen, wie in guten Softwareprojekten üblich, Anforderungen an Sicherheit, Skalierbarkeit und Wartbarkeit. Passen Sie Ihre Server Implementierung entsprechend an.
Minimalbeispiel: Ein MCP-Server mit einem einfachen Tool
add, das zwei Zahlen addiert. Ein Client ruft dieses Tool auf und zeigt das Ergebnis an.
Leider nur unter Windows und MacOS verfügbar.
Notenverwaltung: Ein MCP-Server mit Tools für Notenverwaltung (
list_courses,get_grades,get_student_transcript,calculate_gpa). Die Tools extrahieren Daten aus lokalen PDF-Dateien und bieten strukturierten Zugriff auf Notendaten.
