1. Warum brauchen wir MCP‑Ereignisse überhaupt
Bisher sah fast die gesamte Kommunikation zwischen ChatGPT und Ihrem Backend wie RPC aus: Das Modell rief ein Tool auf, dieses tat etwas, gab ein Ergebnis zurück – fertig. Das ist bequem, solange die Operationen kurz sind: 200–500 ms, maximal ein paar Sekunden.
Sobald jedoch etwas Langläuferiges auftaucht – die Analyse einer großen Datei mit Mitarbeiterpräferenzen für GiftGenius, die Aggregation von Empfehlungen aus vielen externen APIs, die Neuberechnung eines großen Feeds – wird es unangenehm. HTTP‑Timeouts, Neustarts von Funktionen, „endlose“ Spinner, und der Nutzer sitzt da und fragt sich: „Lebt es noch oder ist es schon tot?“
Genau hier beginnt das Ereignismodell. Anstatt einen langen Tool‑Aufruf offenzuhalten, starten Sie einen Job, erhalten eine jobId, und danach sendet der Server eigenständig Ereignisse: gestartet, Fortschritt, fertig, fehlgeschlagen. Diese Ereignisse sind in MCP als JSON‑RPC‑Notifications umgesetzt – unidirektionale Nachrichten ohne id, auf die keine Antwort erwartet wird.
Wichtig zu verstehen: Ein Ereignis ist kein „console.log auf der Leitung“. Es ist eine formale Protokollnachricht mit einer definierten Struktur, die Ihr UI (Widget) und/oder Agent genauso diszipliniert verarbeiten muss wie das Ergebnis eines Tool‑Aufrufs.
Zur Erinnerung: Nachrichtentypen in MCP
Bevor wir weitergehen, frischen wir kurz auf, welche Nachrichten es in MCP überhaupt gibt.
Wenn man alle Marketingschichten weglässt, basiert MCP auf JSON‑RPC 2.0. Dort gibt es drei grundlegende Nachrichtentypen: Requests, Responses und Notifications.
Statt sie als Liste auszuschreiben, schauen wir uns eine kleine Vergleichstabelle an:
| Typ | Feld id | Wer initiiert | Wird eine Antwort erwartet? | Beispiel in MCP |
|---|---|---|---|---|
| Request | vorhanden | In der Regel der Client (ChatGPT) | Ja | Aufruf des Tools tools/call |
| Response | vorhanden | MCP‑Server | Das ist die Antwort | Ergebnis von tools/call |
| Notification | nicht vorhanden | Client oder Server | Nein | notifications/progress, resources/updated, logging/message |
MCP‑Ereignisse fallen genau in die dritte Zeile: Das sind Notifications. Kennzeichen:
- kein id auf der obersten Ebene – es kommt keine result- oder error-Antwort zurück;
- der Initiator wartet nicht auf ein ACK – „Fire‑and‑forget“ auf Protokollebene;
- Zuverlässigkeit entsteht nicht durch Bestätigungen, sondern durch idempotente Handler und eine Wiederholungsstrategie.
Wichtige Einschränkung: MCP‑Ereignisse fliegen nicht „irgendwann irgendwo im All“. Sie leben innerhalb einer bestehenden MCP‑Verbindung über einen konkreten Transport. Meist ist das ein Stream wie SSE (Transportdetails und Varianten behandeln wir in einer eigenen Vorlesung).
2. Was ist ein „MCP‑Ereignis“ in der Praxis?
Formal ist ein MCP‑Ereignis eine JSON‑RPC‑Notification, also ein Objekt der Form:
{
"jsonrpc": "2.0",
"method": "notifications/job/progress",
"params": {
"jobId": "job_123",
"percentage": 30,
"stage": "Wir suchen Varianten im Katalog",
"eventId": "evt_abc123",
"timestamp": "2025-11-21T10:15:00Z"
}
}
Hier sind einige wichtige Punkte:
- Im Feld method kodieren wir den Ereignistyp und seinen „Namensraum“. MCP definiert bereits eine Reihe von Standardmethoden der Form notifications/... für Logs, Fortschritt und Ressourcenänderungen, aber Sie können und sollten Ihre geschäftsspezifischen Methoden ergänzen, wie notifications/job/progress oder notifications/job/completed.
- Alle Geschäftsdaten liegen in params. Dort halten wir auch Job‑IDs (jobId), eindeutige Ereignis‑IDs (eventId), Zeitstempel (timestamp), menschenlesbare Nachrichten usw.
- Auf der obersten Ebene fehlt das Feld id – daher ist es eine Notification. Eine Antwort ist im Protokoll nicht vorgesehen. Wenn der Server wissen will, „ob man ihn verstanden hat“, kann er ein weiteres Ereignis senden oder auf reaktive Handlungen des Clients warten (z. B. eine neue Anfrage). Ein ACK im Sinne von JSON‑RPC gibt es nicht.
Auf mentaler Ebene kann man so denken: Der Tool‑Aufruf tools/call ist „ein Brief, auf dessen Antwort Sie warten“, ein Ereignis ist „eine Benachrichtigung eines Slack‑Bots: „Hintergrundaufgabe #123 abgeschlossen““.
3. Taxonomie der Ereignisse: Welche Notifications gibt es?
Wenn man einfach „beliebige JSONs als Notifications“ zulässt, wird das System in zwei Wochen zur Müllhalde: Ereignisnamen unterscheiden sich, Felder variieren, das UI weiß nicht, was es damit tun soll. Deshalb ist es sinnvoll, sich auf eine kleine Taxonomie zu einigen.
Im Folgenden ein praktikabler Klassifizierungsansatz, der gut zur MCP‑Spezifikation und realen ChatGPT‑App‑Fällen passt.
Ereignisse des Aufgaben‑Lebenszyklus (Job Lifecycle)
Das sind Ereignisse, die Schlüsselübergänge des Aufgabenstatus widerspiegeln. Üblicherweise hat eine Aufgabe eine State Machine wie pending → running → (completed | failed | canceled).
Typische Ereignisse:
- job.created – Aufgabe registriert;
- job.started – Worker hat mit der Ausführung begonnen;
- job.completed – Aufgabe erfolgreich abgeschlossen;
- job.failed – Aufgabe mit Fehler fehlgeschlagen;
- job.canceled – Aufgabe wurde vom Nutzer abgebrochen.
Beispiel job.completed für GiftGenius:
{
"jsonrpc": "2.0",
"method": "notifications/job/completed",
"params": {
"eventId": "evt_gg_100",
"jobId": "giftjob_42",
"timestamp": "2025-11-21T10:20:00Z",
"summary": "Geschenkauswahl abgeschlossen",
"resultResourceId": "resource:gifts:giftjob_42"
}
}
Hier kann resultResourceId auf eine MCP‑Ressource verweisen, die anschließend vom Widget oder Agent gelesen wird.
Fortschrittsereignisse (Progress Updates)
Das sind „kleine Schritte“ innerhalb des Lebenszyklus: Sie ändern den finalen Status nicht, vermitteln dem Nutzer aber das Gefühl, dass etwas passiert.
Typisches Ereignis job.progress:
{
"jsonrpc": "2.0",
"method": "notifications/job/progress",
"params": {
"eventId": "evt_gg_101",
"jobId": "giftjob_42",
"timestamp": "2025-11-21T10:18:30Z",
"percentage": 40,
"stage": "Wir filtern die Geschenke nach Budget",
"etaSeconds": 25
}
}
Wichtig ist hier, dass sich percentage sinnvoll in Richtung 100 bewegt und nicht hin‑ und herspringt. Wählen Sie einen konsistenten Namen für das Fortschrittsfeld (z. B. percentage) und verwenden Sie ihn in allen Ereignissen. Auch das offizielle MCP‑Progress‑Utility hat die Regel: Der Fortschritt steigt nur.
Ressourcen-/Datenaktualisierungen (Resource/Data events)
Manchmal ist dem Nutzer die konkrete jobId gar nicht wichtig. Wichtiger ist, dass sich eine Entität geändert hat: Der Produktfeed wurde aktualisiert, ein neuer Report‑Snapshot erstellt, ein persönliches Profil neu generiert.
In MCP gibt es bereits Standard‑Notifications auf der Ebene resources/updated, resources/list_changed und ähnliche, die dem Client signalisieren: „Lies die Ressourcenliste neu ein, da hat sich etwas geändert.“
Für GiftGenius kann das so aussehen:
{
"jsonrpc": "2.0",
"method": "resources/updated",
"params": {
"eventId": "evt_feed_17",
"timestamp": "2025-11-21T09:00:00Z",
"resourceId": "resource:product-feed",
"changeType": "snapshot_ready"
}
}
Das Widget kann nach Erhalt eines solchen Ereignisses zum Beispiel die Schaltfläche „Geschenkliste aktualisieren“ hervorheben.
UX‑ und Systemereignisse
Es gibt noch Ereignisse, die nicht strikt geschäftlich sind, aber für UX oder Diagnose wichtig:
- Log‑Nachrichten logging/message – eine Standard‑MCP‑Notification für Logs;
- Heartbeat/Ping – periodische „Ich lebe“-Signale vom Server;
- Warnungen zur Degradation: zum Beispiel „Externe APIs sind gerade langsam, Ergebnisse können verzögert eintreffen“.
Solche Ereignisse sind für Monitoring und Debugging nützlich; manchmal kann man sie im UI „entdramatisieren“, um zu zeigen, dass das System nicht tot ist, sondern beschäftigt.
4. Struktur eines Ereignisses: Pflichtfelder und Payload
Ein Ereignis ist genauso ein API‑Objekt wie ein Tool‑Request. Es muss gestaltet werden. Eine gute Gewohnheit ist, sich auf einen Basissatz von Feldern zu einigen.
Konzeptionell ist es hilfreich, ein Ereignis in drei Teile zu trennen: Metadaten, Korrelation und Payload.
Beispiel einer allgemeinen Form:
{
"jsonrpc": "2.0",
"method": "notifications/job/progress",
"params": {
"eventId": "evt_gg_103",
"type": "job.progress",
"timestamp": "2025-11-21T10:19:00Z",
"jobId": "giftjob_42",
"payload": {
"percentage": 60,
"stage": "Wir vergleichen Bewertungen",
"etaSeconds": 15
}
}
}
In dieser Struktur lassen sich unterscheiden:
- eventId – eindeutige Kennung des Ereignisses. Dient der Deduplizierung auf dem Client;
- type – logischer Name des Ereignisses (kann method duplizieren/normalisieren);
- timestamp – wann das Ereignis vom Server generiert wurde;
- jobId oder eine andere Correlation‑ID – um zu verstehen, worauf sich das Ereignis bezieht;
- payload – die eigentlichen Daten. Für jeden Ereignistyp mit eigener Form.
In einem realen System werden Sie diese Strukturen fast sicher formal mit JSON Schema oder zumindest TypeScript‑Typen beschreiben wollen, damit sowohl Server als auch Client die Nachrichten validieren. Manche Teams nutzen dafür ein von CloudEvents inspiriertes Format: Dort gibt es ebenfalls Standardfelder wie id, source, type, time usw.
Die Kernaussage ist einfach: Ein Ereignis muss maschinenlesbar und konsistent sein – ohne Überraschungen wie „manchmal heißt das Feld jobId, manchmal job_id, manchmal fehlt es“.
In den folgenden Beispielen verwenden wir, um den Code nicht zu überladen, häufiger eine „flache“ Variante: Alle Ereignisdaten liegen direkt in params ohne verschachteltes payload, und das Feld type wird gelegentlich weggelassen, wenn seine Rolle ohnehin method übernimmt. Das Prinzip bleibt: Jedes Ereignis hat stabile Metadaten (eventId, jobId, timestamp) und eine vorhersagbare Payload.
5. Idempotenz von Ereignissen: Warum und wie
Jetzt zum wichtigsten Wort dieser Vorlesung – Idempotenz.
Idempotenz eines Ereignishandlers bedeutet, dass der Endzustand des Systems korrekt bleibt, egal ob dasselbe Ereignis einmal oder zehnmal verarbeitet wird. In verteilten Systemen mit Netzwerk und Retries ist das eine Frage von Leben und Tod.
Warum kann dasselbe Ereignis überhaupt mehrfach ankommen?
Es gibt viele Gründe: von Verbindungsabbrüchen und Wiederverbindungen bis hin zu Retries auf Serverseite, die eine Benachrichtigung „zur Sicherheit“ erneut senden. Bei Streaming‑Protokollen (z. B. wenn der Server Ereignisse in eine offene Verbindung wie SSE pusht – Details dazu in der separaten Vorlesung zum Transport) ist das klassisch: Der Client verbindet sich mit Last-Event-ID neu, der Server liefert verpasste Ereignisse nach, und einige davon sieht der Client ein zweites Mal.
Wenn Ihr Handler nicht idempotent ist, passieren Seltsamkeiten:
- das Ereignis job.completed führt zu doppelter Bonusgutschrift oder setzt den Bestellstatus zweimal um;
- das Ereignis resource.updated veranlasst das Widget, jedes Mal Karten „hinzuzufügen“ und sie im UI zu duplizieren;
- wiederholte job.progress erschrecken Nutzer, wenn die Fortschrittsanzeige hin‑ und herspringt.
Die richtige Strategie hat zwei Ebenen: Erzeugung von Ereignissen auf dem Server und deren Verarbeitung auf dem Client.
Serverseite: stabile IDs und State Machine
Der Server sollte:
- für jedes logische Ereignis eine eindeutige eventId generieren;
- garantieren, dass die Ereignisse einer jobId eine gültige Statussequenz bilden: Sie können kein job.failed nach job.completed senden oder zwei verschiedene job.completed mit unterschiedlichen Ergebnissen.
Das heißt, Sie haben de facto eine State Machine für die Aufgabe, und jedes Ereignis ist ein erlaubter Übergang.
Clientseite: Deduplizierung und „sanfte“ Updates
Der Client (Widget, Agent oder eine andere Komponente) sollte:
- die Menge der bereits verarbeiteten eventId zumindest für die Dauer der aktuellen Verbindung/Sitzung speichern;
- vor der Verarbeitung prüfen: Wenn die eventId bereits gesehen wurde, einfach ignorieren oder das UI ohne Nebeneffekte neu zeichnen;
- bei Ereignissen, die den Aufgabenstatus ändern (job.completed, job.failed), sicherstellen, dass der Übergang zulässig ist: Wenn die Aufgabe bereits als completed markiert ist, darf ein erneutes job.completed nichts ändern, und failed sollte man als fehlerhaft ignorieren.
Klassisches Beispiel aus dem Commerce‑Bereich: Verarbeitung eines Payment‑Webhook‑Bestätigung. Ein und dasselbe order.paid kann leicht zweimal ankommen; daher speichert das Backend eine paymentId und eine Flagge „bereits gutgeschrieben“. Selbst wenn der Webhook ein zweites Mal kommt, ändert sich der Bestellstatus nicht. MCP‑Ereignisse sollten mit demselben Mindset entworfen werden.
6. Beispiel: Ereignisse für GiftGenius entwerfen
Übertragen wir das auf unser Lernprojekt GiftGenius. Stellen wir uns ein langes Szenario vor: Der Nutzer lädt eine große CSV mit einer Liste von Mitarbeitenden und deren Interessen hoch und bittet darum, „Geschenkideen für alle auszuwählen“. Der Vorgang kann Dutzende Sekunden dauern.
Ein vernünftiges Ereignismodell lässt sich so beschreiben:
- Der Nutzer startet das Tool start_bulk_gift_analysis. Das Tool gibt eine jobId zurück: "bulk_2025_001".
- Der MCP‑Server erstellt die Aufgabe und sendet fast sofort job.started mit einer kurzen Beschreibung.
- Während der Ausführung sendet er mehrere job.progress mit den Etappen:
- 10 % – „CSV parsen und Format prüfen“;
- 40 % – „Interessen und Abteilungen extrahieren“;
- 70 % – „Geschenke nach Kategorien zuordnen“;
- 100 % – direkt vor dem Abschluss.
- Am Ende kommt job.completed mit einem Verweis auf die Ressource mit den finalen Empfehlungen.
- Wenn etwas schiefgeht – statt completed kommt job.failed mit Fehlercode und eventuell einem Hinweis zur Behebung.
Informell wird es so sein, aber fixieren wir das in Form von JSON‑Schemata für zwei Schlüsselevents job.progress und job.completed. Pseudo‑JSON‑Schema (vereinfacht):
{
"job.progress": {
"type": "object",
"properties": {
"eventId": { "type": "string" },
"jobId": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" },
"percentage": { "type": "number", "minimum": 0, "maximum": 100 },
"stage": { "type": "string" },
"etaSeconds": { "type": "number" }
},
"required": ["eventId", "jobId", "timestamp", "percentage", "stage"]
}
}
{
"job.completed": {
"type": "object",
"properties": {
"eventId": { "type": "string" },
"jobId": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" },
"summary": { "type": "string" },
"resultResourceId": { "type": "string" }
},
"required": ["eventId", "jobId", "timestamp", "resultResourceId"]
}
}
Sie müssen jetzt nicht zwingend eine vollständige Schema‑Validierung implementieren, aber diese Struktur gedanklich parat zu haben, ist hilfreich: Sie verhindert, dass Felder „verschmieren“, und sorgt dafür, dass wichtige Metadaten nicht fehlen.
7. Mini‑Praxis: Ein Server, der MCP‑Ereignisse sendet
Verbinden wir Theorie mit einem kleinen Stück TypeScript‑Pseudocode. Wir gehen nicht in reale MCP‑Bibliotheken (erstens entwickeln sie sich noch, zweitens liegt der Fokus hier auf dem Modell), sondern zeichnen ein Gerüst.
Nehmen wir an, unser MCP‑Server hat die Abstraktion sendNotification, die eine JSON‑RPC‑Notification zurück an ChatGPT senden kann. Pseudo‑Interface:
// Hilfsfunktion zum Senden einer MCP-Benachrichtigung
async function sendNotification(
method: string,
params: Record<string, unknown>
) {
// Hier würden Sie JSON serialisieren und über die aktive MCP-Verbindung senden
}
Implementieren wir nun den Handler des Tools start_bulk_gift_analysis. Er registriert die Aufgabe, gibt eine jobId zurück und „tickt“ dann irgendwo im Hintergrund und sendet Fortschritt. In der Praxis wären das ein Worker und eine Queue, wir begnügen uns vorerst mit einem Timer.
type Job = {
id: string;
status: "pending" | "running" | "completed" | "failed";
};
const jobs = new Map<string, Job>();
export async function startBulkGiftAnalysisTool() {
const jobId = `bulk_${Date.now()}`;
jobs.set(jobId, { id: jobId, status: "pending" });
// Sofort job.started senden
await sendNotification("notifications/job/started", {
eventId: `evt_${jobId}_started`,
jobId,
timestamp: new Date().toISOString(),
summary: "Analyse der großen Geschenkeliste gestartet"
});
simulateJob(jobId); // Aufgabe „starten“ im Hintergrund
return { jobId };
}
Die eigentliche Job‑Simulation:
async function simulateJob(jobId: string) {
jobs.set(jobId, { id: jobId, status: "running" });
const stages = [
{ percent: 10, stage: "CSV parsen" },
{ percent: 40, stage: "Interessen analysieren" },
{ percent: 70, stage: "Geschenke zuordnen" },
{ percent: 100, stage: "Ergebnis erstellen" }
];
for (const s of stages) {
await sendNotification("notifications/job/progress", {
eventId: `evt_${jobId}_${s.percent}`,
jobId,
timestamp: new Date().toISOString(),
percentage: s.percent,
stage: s.stage
});
await new Promise(r => setTimeout(r, 1000));
}
jobs.set(jobId, { id: jobId, status: "completed" });
await sendNotification("notifications/job/completed", {
eventId: `evt_${jobId}_done`,
jobId,
timestamp: new Date().toISOString(),
summary: "Geschenkanalyse abgeschlossen",
resultResourceId: `resource:gifts:${jobId}`
});
}
Der Code ist bewusst einfach, aber daran sieht man gut:
- wir verwenden die Sequenz started → progress* → completed;
- jedes Ereignis erhält eine eindeutige eventId;
- alle Ereignisse sind an dieselbe jobId gebunden.
Später, wenn Sie echte Queues und Worker hinzufügen, bleibt die Ereignisstruktur ungefähr gleich – es ändert sich nur, wo genau sendNotification aufgerufen wird.
8. Client: ein einfachster idempotenter Ereignishandler
Auf der Clientseite (z. B. in Ihrem Apps‑SDK‑Widget) müssen Sie lernen, solche Ereignisse zu empfangen, sie mit den laufenden Aufgaben zu verknüpfen und nicht wegen Duplikaten verrückt zu werden.
Ohne auf den Transport einzugehen (später), stellen wir uns eine Funktion onMcpNotification vor, die Ihre MCP‑Client‑Schicht bei jeder eingehenden Notification aufruft.
Fügen wir eine einfachste Deduplizierung hinzu:
const processedEvents = new Set<string>();
function handleNotification(method: string, params: any) {
const eventId = params.eventId as string | undefined;
if (!eventId) return; // sehr fragwürdig, aber für das Beispiel reicht es
if (processedEvents.has(eventId)) {
// Duplikat — ignorieren oder UI sanft aktualisieren
return;
}
processedEvents.add(eventId);
if (method === "notifications/job/progress") {
updateJobProgress(params.jobId, params.percentage, params.stage);
} else if (method === "notifications/job/completed") {
markJobCompleted(params.jobId, params.resultResourceId);
}
}
Die Implementierung von updateJobProgress und markJobCompleted ist dann reiner React/UI‑Code:
function updateJobProgress(jobId: string, percent: number, stage: string) {
// z. B. in Zustand/Redux/React State speichern
console.log(`Job ${jobId}: ${percent}% — ${stage}`);
}
function markJobCompleted(jobId: string, resourceId: string) {
console.log(`Job ${jobId} abgeschlossen, Ressource: ${resourceId}`);
}
Ein solcher Handler:
- bricht nicht, wenn ein Ereignis zweimal ankommt;
- verursacht keine Nebeneffekte (z. B. „Fertig!“-Modal zum zweiten Mal zeigen);
- ebnet den Weg für komplexere Logik, etwa die Validierung zulässiger Zustandsübergänge (kein failed über bereits completed legen).
Im produktiven Code werden Sie wahrscheinlich processedEvents beim Wiederverbinden mit dem MCP‑Server zurücksetzen und nicht nur die eventId, sondern auch den aktuellen Status jeder jobId speichern, um sich bei ungewöhnlichen Ereignisfolgen vernünftiger zu verhalten.
Als Nächstes ist wichtig zu verstehen, wie diese MCP‑Ereignisse durch Agent/Widget fließen und in eine konkrete Nutzererfahrung übersetzt werden: Fortschrittsbalken, Etappen, finale Ergebnisse. Gehen wir zur Verknüpfung von Ereignissen mit Run/Workflow und UX über.
9. Verknüpfung von Ereignissen, Run/Workflow und UX
Obwohl wir ein eigenes Modul zu Workflow und Agenten hatten, sehen Sie hier das Gesamtbild. Wir haben bereits Ereignisfamilien eingeführt (job.*, resource.*, Systemereignisse); schauen wir, wie sie Agent/Widget und ChatGPT durchlaufen und in konkrete Nutzererlebnisse münden.
Ein typisches Szenario mit einer langlaufenden Aufgabe sieht so aus: ChatGPT ruft ein MCP‑Tool auf und erhält eine jobId; anschließend sendet der Server Ereignisse zum Fortschritt, Abschluss oder Fehler zur entsprechenden jobId; Ihr Widget oder die Agentenlogik aktualisiert daraufhin das UI und trifft Entscheidungen.
In einem Sequenzdiagramm kann man das so darstellen:
sequenceDiagram
participant User as Benutzer
participant GPT as ChatGPT (Modell)
participant App as GiftGenius MCP-Server
participant Widget as GiftGenius-Widget
User->>GPT: "Finde Geschenke für 2000 Mitarbeitende"
GPT->>App: tools.call start_bulk_gift_analysis
App-->>GPT: response { jobId: "bulk_2025_001" }
GPT->>Widget: ToolOutput { jobId }
Widget->>Widget: Fortschrittsbalken anzeigen
App-->>GPT: notification job.started
App-->>GPT: notification job.progress (10%, 40%, 70%, 100%)
App-->>GPT: notification job.completed { resultResourceId }
GPT->>Widget: Leitet Ereignisse/Daten an das Widget weiter
Widget->>User: Aktualisiert den Fortschritt und zeigt das Ergebnis
In der Praxis wird das reale Diagramm etwas komplexer sein, aber die Kernidee ist einfach: MCP‑Ereignisse sind das „Nervensystem“ zwischen Ihren Hintergrundoperationen und der Nutzererfahrung.
10. Typische Fehler im Umgang mit MCP‑Ereignissen
Fehler Nr. 1: „Ereignis = Log im Produktionsformat“.
Mitunter beginnen Entwickler damit, dass sie in MCP einfach weiterleiten, was sie früher in console.log schrieben. Am Ende enthalten die Ereignisse weder eventId noch jobId noch einen sauberen timestamp, sondern nur halbdichterische Botschaften à la „Wir sind fast fertig“. Dieser Ansatz macht das System fragil: schwer zu parsen, nicht deduplizierbar, das UI weiß nicht, zu welcher Aufgabe die Nachricht gehört. Besser von Anfang an Ereignisse als formalen Vertrag entwerfen: klarer Methodenname, stabiler Feldsatz, logische Payload.
Fehler Nr. 2: Fehlende Idempotenz und eindeutige eventId.
Viele starten naiv: „Ereignisse kommen doch nur einmal.“ Nach einer Woche geht es los: Beim Wiederverbinden werden Benachrichtigungen dupliziert, der Nutzer bekommt dasselbe zweimal, das Commerce‑Backend schreibt Boni doppelt gut. Ohne eindeutige eventId und elementare Deduplizierung auf dem Client fangen Sie früher oder später einen ernsten Bug. In einem verteilten System ist vom Modell „at‑least‑once delivery“ auszugehen: Duplikate sind unvermeidlich.
Fehler Nr. 3: Vermischung von System‑ und Business‑Ereignissen in einem Brei.
Zum Beispiel landen im selben Stream logging/message, job.progress, job.completed, resources/updated – ohne klare type/method-Trennung. Das UI endet bei merkwürdigen if (message.includes("fertig")), um zu erkennen, dass die Aufgabe abgeschlossen ist. Besser strikt trennen: Es gibt System‑Notifications (Logs, Heartbeat) und Business‑Ereignisse (job.*, resource.*) mit sauber beschriebenen Schemata.
Fehler Nr. 4: Inkonsistente Zustandsübergänge einer Aufgabe.
Es passiert, dass der Server in einem Ereignisstrom zuerst job.completed sendet, dann plötzlich job.progress, danach job.failed. Das passiert, wenn keine explizite State Machine und keine Prüfungen bei der Emission vorhanden sind. Für Clients ist dann nicht mehr verständlich, was tatsächlich geschieht. Besser ist, einen endlichen Automaten zu beschreiben und Ereignisse, die ihn verletzen, nicht auszugeben: Nach completed kann man maximal ein zusätzliches Informationsevent schicken, aber die Aufgabe nicht zurück in running versetzen.
Fehler Nr. 5: Harte Bindung an konkrete MCP‑Methodennamen aus der aktuellen Spezifikation.
Die MCP‑Spezifikation entwickelt sich noch. Wenn Sie alles an aktuelle Systemmethoden binden, ohne eigene Namespaces vorzusehen, zwingt jede Protokolländerung zu großen Umbauten. Besser ist es, Ereignisse als eigene Mini‑Spezifikation oberhalb von MCP zu betrachten: Sie können sich auf bestehende Methoden stützen (notifications/progress, resources/updated), aber Business‑Ereignisse (notifications/job/*) in Ihrem eigenen Namespace entwerfen und relativ unabhängig halten.
Fehler Nr. 6: Keine Verbindung der Ereignisse mit dem UX.
Manchmal entwirft das Team ein schönes Ereignismodell im Backend, bringt es aber nicht ins Widget: job.progress existiert in den Logs, doch das UI zeigt 40 Sekunden lang nur einen einsamen Spinner. In so einem Szenario glauben Nutzer weder an MCP noch an KI. Beim Entwerfen von Ereignissen sollten Sie immer an den konkreten UI‑Effekt denken, den Sie erreichen wollen: Fortschrittsbalken, Etappen, Teilresultate. MCP‑Ereignisse sind nicht für das Protokoll da, sondern für ein verständliches App‑Verhalten.
GO TO FULL VERSION