# MCP-Server implementieren: Zustand, Updates & Abbruch

> Implementiere die Laufzeitsemantik eines MCP-Servers: zustandsisolierte Verbindungen, robuste Discovery, Resource-Updates, Client-vermittelte Sampling- und Elicitation-Flows, Tool-Ergebnisse und sicheren Abbruch.

Track: [MCP Engineering](https://physar.tech/learn/mcp-engineering)  
Kanonische Fassung: https://physar.tech/learn/mcp-engineering/server-implementation  
Stand: 2026-07-26  
Interaktiver Teil: 10 Checks (nur im Browser)

## MCP-Server implementieren: Semantik vor Handlern

### Ein Handler ist noch kein Serververtrag

Nach dem Entwurf der öffentlichen Verträge beginnt die schwierigere Arbeit: Ein MCP-Server verarbeitet nicht nur einzelne Aufrufe, sondern lebt in Verbindungen, veröffentlicht Listen, kann Updates signalisieren und darf den Host bei Modell- oder Nutzereingaben nur über vereinbarte Rückkanäle einbeziehen. Der Server bestimmt seine Fachlogik; der Host behält die Hoheit über Anwendung, Nutzerinteraktion und Modellzugriff.

Die Implementierung sollte deshalb nicht als Sammlung globaler Handler entstehen. Behandle jede Capability als einen Vertrag mit **Geltungsbereich**, **Lebenszyklus** und **Fehlerpfad**. Das verhindert die gefährlichsten Defekte: Daten einer anderen Verbindung, doppelte oder verlorene Listenobjekte, unkontrollierte Modellaufrufe und Abbrüche mit weiterlaufenden Seiteneffekten.

> **Leitfrage:** Bei jeder Serverfunktion: Wer besitzt den Zustand, wer darf sie anfordern, und was passiert bei Änderung oder Abbruch?

### Zustand gehört zur Verbindung und Identität

Ein Prozess kann viele MCP-Sitzungen bedienen. Informationen wie angemeldete Identität, Mandant, ausgehandelte Capabilities, Cursor-Snapshots und Subscriptions dürfen daher nicht versehentlich als ein globaler Serverzustand geführt werden. Sie gehören zur MCP-Sitzung und, wo Berechtigungen gelten, zur geprüften Identität. Laufende Requests und Event-Lieferung haben zusätzlich einen kurzlebigen Stream- oder Request-Kontext.

_[Abbildung: Transport-Stream, MCP-Sitzung und Identität haben verschiedene Lebensdauern und dürfen nicht zusammenfallen.]_

|  |  |
| --- | --- |
| Prozessweit | Statische Konfiguration oder bewusst geteilte, synchronisierte Infrastruktur |
| MCP-Sitzung | Negotiated Capabilities, Subscriptions und Cursor-Snapshots bei Streamable HTTP |
| Pro Stream oder Request | Event-Lieferung, laufende Arbeit und temporäre Transportressourcen |
| Pro Identität oder Mandant | Autorisierte Datenansicht und fachlicher Zugriffskontext |

Bei Streamable HTTP beendet das Schließen eines SSE- oder TCP-Streams nicht automatisch die MCP-Sitzung: Der Client kann denselben Session-Identifier bei späteren HTTP-Requests weiterführen. Der Server räumt deshalb nur Stream-Zustand beim Streamende. Sitzungszustand endet bei expliziter Session-Terminierung, serverseitigem Ablauf oder einer klaren Löschregel. Bei `stdio` endet der Lebenszyklus dagegen mit Prozess und Streams. Eine WebSocket-Implementierung wäre ein eigener Custom Transport und muss dieselben Lifecycle-Regeln dokumentieren.

> **Merksatz:** Teile Infrastruktur bewusst, aber nie zufällig die Sicht einer Sitzung oder Identität.

### Listen brauchen Ordnung und eine definierte Sicht

Prompts, Resources und Tools können über Listenoperationen entdeckt werden. Bei kleinen, statischen Mengen ist eine vollständige Antwort praktisch. Bei vielen oder dynamischen Einträgen braucht die Antwort Pagination. Entscheidend ist nicht die Seitengröße, sondern dass ein Client beim Weiterblättern keine zufälligen Duplikate oder Lücken erhält.

Datenansicht für Verbindung und Identität bestimmen → **Deterministisch nach stabiler Reihenfolge lesen** → Seite liefern → Undurchsichtigen `nextCursor` ausgeben → Folgeaufruf gegen dieselbe definierte Sicht prüfen

Wähle ausdrücklich eine Semantik: etwa eine Snapshot-Sicht für die Dauer einer Pagination oder eine stabile Sortierung mit einer nachvollziehbaren Fortsetzungsgrenze. Welche Wahl passt, hängt von Änderungsrate und Speicherkosten ab. Ohne diese Entscheidung verschieben Einfügungen und Löschungen die nächste Seite unbemerkt.

> **Trade-off:** Snapshots machen Listen konsistent, kosten aber Zustand oder Speicher. Eine fortlaufend aktuelle Sicht ist günstiger, muss aber ihre möglichen Veränderungen klar behandeln.

**Kurzcheck:** Während ein Host Resources durchblättert, werden Einträge archiviert und neue angelegt. Die UI darf im Durchlauf weder Duplikate noch Lücken sehen. Welche Zusage braucht der Server?

- [x] Eine identitätsgebundene, stabil geordnete Sicht und einen opaque Cursor, der nur gegen diese Sicht fortsetzt.
- [ ] Einen öffentlichen Offset gegen den jeweils aktuellen Datenbestand, damit der Client die Position versteht.
- [ ] Eine zufällige Reihenfolge pro Seite, damit neue Einträge gleich schnell sichtbar werden.

> Richtig. Die Zusage betrifft eine definierte Fortsetzung. Der Cursor bleibt serverkontrolliert; Änderungen am Live-Bestand verschieben nicht unbemerkt die bereits begonnene Liste.

### Ein Cursor ist ein Server-Token, kein Client-Format

Der Client gibt einen Cursor unverändert zurück; er soll ihn weder verstehen noch konstruieren müssen. Der Server behandelt ihn als opaque Token und bindet ihn an die Datenansicht, Identität und gegebenenfalls Ablaufzeit. Er darf einen Cursor intern signieren, verschlüsseln oder auf serverseitigen Zustand abbilden — die Repräsentation ist aber keine öffentliche API.

Ein abgelaufener, widerrufener oder zur falschen Identität gehörender Cursor ist ein erwartbarer Zustand. Antworte kontrolliert mit einem geeigneten Fehler oder einer Neustart-Anweisung, statt still mit einem anderen Offset fortzufahren. Das ist besonders wichtig, wenn sich Berechtigungen oder der zugrunde liegende Bestand zwischen zwei Seiten ändern.

> **Merksatz:** Opaque bedeutet: Der Server kann Fortsetzung und Gültigkeit ändern, ohne den Client zu brechen oder ihm fremde Sicht zu öffnen.

### Resource-Updates signalisieren Änderungen, sie liefern keinen Cache

Resources sind anwendungsgetriebener Kontext. Ein Client liest ihren Inhalt über die Resource-URI; ein Server kann Änderungen an einer einzelnen abonnierten Resource oder an der verfügbaren Liste signalisieren, wenn er die jeweilige Capability angekündigt hat. Eine Update-Notification transportiert dabei nicht automatisch den neuen Inhalt.

|  |  |
| --- | --- |
| `listChanged` | Die Menge verfügbarer Resources hat sich geändert; Clients können die Discovery-Liste neu laden. |
| `subscribe` | Ein Client kann Änderungen einer konkreten Resource abonnieren; der Server signalisiert danach deren Update. |
| Cache-Entscheidung | Der Client oder Host entscheidet, ob, wann und mit welcher Staleness er erneut liest und Kontext ersetzt. |

Der Server hält Subscriptions nur für unterstützte, erfolgreiche Anmeldungen und bindet sie an MCP-Sitzung, Resource-URI und verifizierte Identität. Er räumt sie bei Unsubscribe, expliziter Session-Terminierung oder Ablauf auf. Das Ende eines Streamable-HTTP-SSE-Streams löscht sie nicht vorschnell, weil die Sitzung weiterlaufen und ein Stream wiederverbunden werden kann. Er sendet keine globalen Updates an andere Sessions, nur weil ein Objekt irgendwo geändert wurde.

> **Merksatz:** Eine Notification sagt „prüfe erneut“ — nicht „übernimm diesen Inhalt blind“.“

### Sampling fordert beim Client Modellarbeit an

Muss eine Serverfunktion eine Modellantwort erzeugen, stellt der Server eine Sampling-Anfrage an den MCP-Client. Nur wenn der Client Sampling als Capability ausgehandelt hat, darf der Server sie nutzen. Der Client beziehungsweise Host wählt Modellzugang, Richtlinien, Kosten- und Sicherheitsgrenzen und kann die Anfrage für Nutzer sichtbar machen oder ablehnen.

Der Server hinterlegt dafür nicht heimlich einen Modellschlüssel und umgeht keine Host-Policy. Er kann seine fachliche Aufgabe koordinieren und eine begrenzte Sampling-Anfrage formulieren, aber der Rückkanal bleibt Teil des Client-Vertrags. Unterstützt der Client Tool-Use innerhalb des Sampling-Flows nicht, fordert der Server diese Variante ebenfalls nicht an.

> **Merksatz:** Sampling verlagert Modellzugriff zum Client. Eine Serverfunktion gewinnt dadurch keine eigene, unkontrollierte Agentenidentität.

### Elicitation holt Eingaben sichtbar über den Client ein

Benötigt ein Workflow eine Auswahl oder Fachangabe, fragt der Server den Nutzer mittels Elicitation über den Client. Auch diese Capability und ihr Modus müssen ausgehandelt sein. Form Mode sammelt flache, strukturierte und nicht sensible Daten im Client; die Anfrage erklärt Zweck und Felder so, dass Nutzer prüfen, ändern, ablehnen oder abbrechen können.

Passwörter, API-Schlüssel, Zugriffstokens und Zahlungsdaten gehören nicht in Form Mode. Für sensible Interaktionen verwendet der Server URL Mode: Der Client zeigt Ziel-Domain und Zweck, holt vor Navigation Consent ein, und die geheimen Daten passieren nicht den MCP-Client. Ein Server darf deshalb kein scheinbar harmloses Freitextfeld als Secret-Sammelstelle verwenden.

> **Merksatz:** Nutzereingabe ist ein expliziter, rückkanalfähiger Vertrag — keine versteckte Aufforderung in Tool-Text.

### Tool-Ergebnisse sind publizierte Output-Verträge

Ein Tool mit maschinenlesbarem Ergebnis veröffentlicht sein `outputSchema` bereits in der Tool-Definition. Liefert es später `structuredContent`, muss dieses serverproduzierte JSON dem veröffentlichten Schema entsprechen; Clients sollten die Übereinstimmung prüfen. Damit bleibt ein Ergebnis für Host-Logik, Tests und spätere Serverversionen berechenbar.

|  |  |
| --- | --- |
| `outputSchema` | Der beim Discovery veröffentlichte Vertrag für strukturierte Ergebnisse |
| `structuredContent` | Die konkrete serverproduzierte JSON-Antwort, die diesem Vertrag entsprechen muss |
| TextContent | Eine zusätzliche serialisierte Darstellung für Kompatibilität und Modellkontext |

Text ist kein Ersatz für ein versprochenes Schema: Ein Host müsste daraus Felder erraten, obwohl der Server einen maschinenlesbaren Vertrag anbieten kann. Umgekehrt ergänzt eine Textdarstellung `structuredContent`, weil ältere oder modellzentrierte Clients strukturiertes Ergebnis nicht vollständig auswerten. Die Felder, Typen und Semantik entwickelt der Server deshalb versioniert und bewusst.

> **Merksatz:** Veröffentlichtes Output Schema und ausgeliefertes `structuredContent` sind derselbe Vertrag zu zwei Zeitpunkten.

### Cancellation ist kooperativ und kein Rollback

Eine Cancellation-Notification kann einen noch laufenden Request markieren. Der Empfänger sollte Verarbeitung beenden, zugehörige Ressourcen freigeben und für den abgebrochenen Request keine Antwort mehr senden. Wegen Netzwerklatenz kann die Cancellation aber nach Abschluss eintreffen oder nicht umsetzbar sein; beide Seiten müssen diese Race Conditions vertragen.

Eine robuste Serverfunktion propagiert den Abbruch in Datenbankabfragen, Streams und Child-Jobs, prüft ihn vor irreversiblen Schritten und dokumentiert den Fachzustand. Ein Abbruch garantiert weder, dass ein externer API-Call nie ausgeführt wurde, noch dass bereits entstandene Seiteneffekte automatisch zurückgerollt werden. Dafür braucht die Fachlogik Idempotenz, Kompensation oder einen expliziten Status.

> **Merksatz:** Cancellation beendet Warten und Arbeit nach Möglichkeit; sie beweist nicht, dass die Außenwelt unverändert blieb.

### Eine Implementierung verbindet die Grenzen bewusst

Sitzung initialisieren und Capabilities binden → Identität und Sitzungskontext herstellen → Discovery mit stabiler Sicht und Cursorn anbieten → Updates nur für vereinbarte Subscriptions signalisieren → **Client-Rückkanäle für Sampling oder Elicitation prüfen** → Schema-konformes strukturiertes Tool-Ergebnis liefern → Bei Cancellation Arbeit und Ressourcen kooperativ beenden → Stream oder Sitzung nach jeweiliger Lifecycle-Regel räumen

Nicht jeder Server braucht jede optionale Capability. Weniger veröffentlichte Semantik bedeutet weniger Zustand und weniger Fehlerpfade. Veröffentliche eine Capability erst, wenn ihr Lifecycle, ihre Berechtigungsgrenze und ihr Testszenario klar sind; ein ausgelassenes Feature ist sicherer als eine halb implementierte Capability. Trenne dabei in Streamable HTTP immer kurzlebige Streams von langlebiger Sitzungssemantik.

> **Implementierungsregel:** Capabilities sind Zusagen. Implementiere und teste ihre vollständige Semantik, bevor du sie aushandelst.

### Die neun Laufzeitgrenzen im Check

- **Isolation**: Datenansicht und verbindungsbezogenen Zustand nicht vermischen.
- **Capability**: Nur verhandelte, vollständig betriebene Funktionen verwenden.
- **Rückkanal und Fehler**: Host- und Nutzersouveränität sowie sichere Fachsemantik erhalten.

> **Ausblick:** Im nächsten Modul geht es um Hosts und Clients. Hier baust du die Serverseite so, dass diese Integration zuverlässig überhaupt möglich ist.

## Quellen

- modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle — https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle
- modelcontextprotocol.io/specification/2025-11-25/basic/transports — https://modelcontextprotocol.io/specification/2025-11-25/basic/transports
- modelcontextprotocol.io/specification/2025-11-25/server/resources — https://modelcontextprotocol.io/specification/2025-11-25/server/resources
- modelcontextprotocol.io/specification/2025-11-25/server/tools — https://modelcontextprotocol.io/specification/2025-11-25/server/tools
- modelcontextprotocol.io/specification/2025-11-…utilities/pagination — https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination
- modelcontextprotocol.io/specification/2025-11-25/client/sampling — https://modelcontextprotocol.io/specification/2025-11-25/client/sampling
- modelcontextprotocol.io/specification/2025-11-25/client/elicitation — https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation
- modelcontextprotocol.io/specification/2025-11-…ilities/cancellation — https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
- modelcontextprotocol.io/specification/2025-11-25/architecture — https://modelcontextprotocol.io/specification/2025-11-25/architecture
