1. MCP und JSON‑RPC: der „langweilige“ Unterbau, den man einmal verstehen muss
In der letzten Vorlesung haben wir besprochen, wozu MCP überhaupt dient und wie es in den Stack des Apps SDK passt. In dieser Vorlesung verengen wir den Fokus auf die „langweiligste“ Schicht – das Format der MCP‑Nachrichten –, damit Sie rohe JSON‑Logs sicher lesen und verstehen können, was genau ChatGPT an Ihren Server schickt und was dieser antwortet.
MCP nutzt JSON‑RPC 2.0 als Datentransport: alle Anfragen, Antworten und Benachrichtigungen sind gewöhnliche JSON‑Objekte mit einer vorhersehbaren Struktur.
Statt „jeder Dienst erfindet sein eigenes Format“ gibt es also einen Basisvertrag:
- Eine Anfrage hat das Pflichtfeld jsonrpc (meist "2.0"), eine eindeutige id, einen Methodennamen als Zeichenkette method und ein Objekt params mit Parametern.
- Die Antwort wird über die id der Anfrage zugeordnet und enthält entweder result oder error.
- Benachrichtigungen (notifications) ähneln Anfragen, haben aber keine id; es gibt keine Antwort darauf.
So sieht das ungefähr aus:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/list",
"params": {
"cursor": null
}
}
Das ist ein Request. Und die Antwort im Erfolgsfall:
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"tools": [],
"nextCursor": null
}
}
Wenn Ihnen der Gedanke „das ist doch ganz normales RPC“ in den Sinn kam – genau so ist es. MCP legt lediglich fest, welche Methoden es gibt (tools/list, tools/call, resources/list, prompts/list, …) und in welchem Format sie Parameter erwarten und Daten zurückgeben.
Wichtig ist das Gefühl: JSON‑RPC ist das Gerüst „Anfrage–Antwort–Benachrichtigung“. MCP ist „welche konkreten Anfragen es gibt und was in ihnen drinsteckt“.
2. Request: wie MCP um eine Aktion bittet
Beginnen wir mit Anfragen. Sie laufen immer in die Richtung „jemand möchte etwas tun“. Üblicherweise ist das Client → Server (ChatGPT → Ihr MCP‑Server), aber MCP erlaubt auch umgekehrte Anfragen, wenn der Server den Client um Sampling oder Elicitation bittet. In dieser Vorlesung interessiert uns vor allem die klassische Variante: der Client bittet den Server.
Jeder MCP‑Request hat drei Schlüsselfelder:
- jsonrpc – die JSON‑RPC‑Protokollversion, meist "2.0".
- id – die Kennung der Anfrage; beliebiger JSON‑Typ, in der Praxis meist Zahl oder Zeichenkette. Wichtig ist, dass die id für aktive Anfragen eindeutig ist.
- method – eine Zeichenkette wie "tools/list" oder "tools/call". MCP spezifiziert den Satz zulässiger Methoden.
Und es gibt das Objekt params, in dem die Parameter der jeweiligen Methode leben.
Beispiel: Abruf der Werkzeugliste
Stellen wir uns vor, ChatGPT hat sich gerade mit Ihrem MCP‑Server verbunden und möchte wissen, welche Tools es aufrufen kann. Es sendet eine Anfrage in etwa dieser Form:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"cursor": null
}
}
Das Feld cursor wird für die Paginierung benötigt – wenn es viele Tools gibt, kann der Server sie in Portionen liefern.
Für unsere Übungsanwendung (Geschenke finden) ist es zunächst langweilig: ein bis zwei Tools, aber das Protokoll bleibt dasselbe. Betrachten Sie das zunächst als intuitives Beispiel; die formalen Strukturen besprechen wir später im Abschnitt zu Tools.
Beispiel: Tool‑Aufruf (tools/call)
Jetzt wird es etwas interessanter. Nehmen wir an, wir haben bereits ein MCP‑Tool suggest_gifts, das Sie in der Vorlesung zum MCP‑Server implementieren werden. Es erwartet folgende Parameter:
- occasion – Anlass (Birthday, Wedding, …),
- budget – Zahl in US‑Dollar,
- recipient – Zeichenkette mit der Beschreibung, für wen das Geschenk ist.
Wenn ChatGPT dieses Tool verwenden möchte, formt es einen MCP‑Request:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "suggest_gifts",
"arguments": {
"occasion": "birthday",
"budget": 100,
"recipient": "friend who loves board games"
}
}
}
Beachten Sie einige Details.
Erstens wird der Tool‑Name aus dem entnommen, was Sie auf Serverseite registriert haben (server.registerTool("suggest_gifts", …)). Zweitens muss das Objekt arguments der JSON Schema entsprechen, die Sie in der Tool‑Beschreibung angeben.
Wenn GPT versucht, Argumente nicht schemakonform zu senden (z. B. budget: "hundert Dollar"), darf der Server je nach Implementierung einen Fehler auf Protokoll‑ oder Business‑Logik‑Ebene zurückgeben. Wichtig ist zunächst, die allgemeine Form einer solchen Anfrage zu erfassen; im Abschnitt zu Tools unten betrachten wir dieselben Nachrichten dann systematischer.
Requests für Ressourcen und Prompts
Ähnlich sehen Anfragen an Ressourcen und Prompts aus. Die MCP‑Spezifikation definiert Methoden:
- resources/list – verfügbare Ressourcen aufzählen;
- resources/read (oder resources/get) – eine konkrete Ressource per URI lesen;
- prompts/list – die Liste verfügbarer Prompts holen;
- prompts/get – den Text eines konkreten Prompts holen.
Beispiel für eine Leseanfrage eines Ressourceneintrags mit einem Geschenk‑Katalog:
{
"jsonrpc": "2.0",
"id": 15,
"method": "resources/read",
"params": {
"uri": "mcp://gift-server/resources/gift_catalog"
}
}
Merken Sie sich vorerst zwei Dinge. Erstens gibt es für jedes Primitive die Methoden */list und */get/*/read. Zweitens steht der Methodenname immer im Zeichenkettenfeld method, und der gesamte Inhalt lebt im Objekt params.
3. Reply: wie MCP antwortet – result und error
Die Antwort (reply) ist immer über das Feld id einer Anfrage zugeordnet. Das ist wie correlationId in vielen verteilten Systemen: Man schaut in die Logs und sieht, dass die Anfrage mit id=7 eine Antwort mit id=7 erhalten hat – also gehört das zusammen.
JSON‑RPC stellt eine einfache Regel auf: In der Antwort steht entweder result, oder error, aber nicht beides gleichzeitig. MCP präzisiert darauf aufbauend die Struktur von result für verschiedene Methoden (tools/list, tools/call usw.) und empfiehlt Fehlercodes.
Erfolgreiche Antwort (result)
Schauen wir uns ein Beispiel für eine erfolgreiche Antwort auf tools/call unseres suggest_gifts an. Der Server hat alles abgearbeitet, passende Geschenke gefunden und gibt die Liste im Feld result zurück:
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "Here are some gift ideas for your friend..."
}
],
"structuredContent": {
"gifts": [
{ "name": "Board game: Catan", "price": 45 },
{ "name": "Dice set", "price": 20 }
]
},
"isError": false
}
}
Hier sind mehrere Punkte wichtig.
- Erstens sind content und structuredContent genau die Teile der MCP‑Tool‑Antwort, die Sie bereits im Apps SDK gesehen haben. Das Modell nutzt den Text aus content, und Ihr Widget rendert die Daten aus structuredContent sauber.
- Zweitens bezieht sich das Flag isError auf das Business‑Ergebnis. Aus Sicht des Protokolls lief alles erfolgreich: JSON ist valide, die Methode existiert, Argumente wurden geparst. Aber die Business‑Logik kann sagen: „Ich habe keine einzige Geschenkidee gefunden – aus UX‑Sicht werten wir das als Fehler.“ Dann setzen Sie isError: true und beschreiben das Problem im content.
- Drittens beschreibt die MCP‑Spezifikation für verschiedene Methoden (tools/list, tools/call, */list, */get) detailliert, welche Felder im result vorhanden sein müssen. Für tools/list gibt der Server beispielsweise ein Array von Tool‑Beschreibungen mit Namen, Titeln, Beschreibungen und der JSON Schema der Eingabeargumente zurück.
Fehlerhafte Antwort (error)
Wenn auf Protokoll‑ oder Serverebene etwas schief läuft, wird statt result ein error‑Objekt zurückgegeben. Es enthält üblicherweise:
- code – numerischen Fehlercode;
- message – menschenlesbare Beschreibung;
- data – optionale Zusatzdaten (Stack Trace, Details, …).
Beispiel: Das Modell hat eine nicht existierende Methode aufgerufen:
{
"jsonrpc": "2.0",
"id": 99,
"error": {
"code": -32601,
"message": "Method not found: tools/col"
}
}
Der Code -32601 ist der klassische JSON‑RPC‑Code für „method not found“.
Es gibt einen feinen, aber wichtigen Unterschied zwischen zwei Fehlertypen.
Protokollfehler – wenn Regeln von MCP/JSON‑RPC verletzt sind: unbekannte Methode, falscher Typ im Feld params, ungültiges JSON. Dann ist es angemessen, ein error auf oberster Ebene zurückzugeben.
Business‑Fehler – wenn das Protokoll eingehalten ist, die Operation aber aus Domänengründen fehlschlägt: leerer Katalog, keine Berechtigung für eine Ressource, falscher Geschäfts‑Identifikator. Dann empfiehlt MCP in der Regel, ein valides result zurückzugeben, es jedoch mit isError: true zu markieren und das Problem im Inhalt zu beschreiben.
Diese Trennung hilft ChatGPT und den Debug‑Tools sehr: Schon beim Blick in die Logs erkennt man, ob es sich um einen technischen Defekt oder eine bewusste Ablehnung der Business‑Logik handelte.
4. Notifications: unidirektionale Nachrichten
Eine Benachrichtigung (notification) ist ein „Brief ohne Erwartung einer Antwort“. In JSON‑RPC sehen Benachrichtigungen wie gewöhnliche Anfragen ohne Feld id aus. Der Client darf darauf keine Reply senden.
In MCP werden Benachrichtigungen für Ereignisse verwendet: Änderungen an den Listen von tools/resources/prompts, Fortschritt von Langläufern, Log‑Nachrichten usw.
Das einfachste Beispiel, dem Sie sicher begegnen – die Benachrichtigung, dass sich die Liste der Tools geändert hat. Die MCP‑Spezifikation beschreibt für Tools die Fähigkeit listChanged und die Benachrichtigung tools/list_changed, die der Server sendet, wenn sich der Satz verfügbarer Tools geändert hat.
Eine Benachrichtigung kann so aussehen:
{
"jsonrpc": "2.0",
"method": "tools/list_changed",
"params": {
"reason": "New tool 'suggest_gift_cards' was added"
}
}
Eine Antwort darauf ist nicht nötig. Der Client kann, nachdem er so eine Benachrichtigung erhalten hat, entscheiden: „Aha, ich sollte tools/list noch einmal aufrufen und den Tool‑Cache aktualisieren.“
Weitere typische MCP‑Benachrichtigungen (wir sprechen darüber im Modul zu Streams und Events ausführlicher):
- Fortschrittsereignisse (notifications/progress) für lange Operationen;
- Server‑Logs (notifications/logging/message);
- Ressourcenänderungen (resources/list_changed) und Prompt‑Änderungen (prompts/list_changed).
Vorläufig ist eines wichtig: Benachrichtigung = Anfrage ohne id und ohne erwartete Antwort. Wenn Sie in den Logs ein JSON ohne id sehen, ist das höchstwahrscheinlich eine Notification.
Insight
Experimentell wurde festgestellt, dass die ChatGPT‑App ihr gesendete Nachrichten (MCP‑Notification) ignoriert. Da sich ChatGPT‑Apps jedoch erst am Anfang ihrer Entwicklung befinden, ist die Wahrscheinlichkeit sehr hoch, dass in naher Zukunft die volle Unterstützung aller Seiten des MCP‑Protokolls kommt. Daher empfehle ich trotzdem, diesen Aspekt des MCP‑Protokolls zu studieren.
5. Wie tools/resources/prompts in Nachrichten aussehen
Jetzt das Spannendste: wie genau innerhalb von MCP‑Nachrichten die besagten tools, resources und prompts beschrieben sind, über die wir die ganze Zeit sprechen.
Tools: Beschreibung und Aufruf
Auf Protokollebene haben Tools zwei Hauptprozesse:
- discovery – der Client erfährt, welche Tools es gibt;
- invocation – der Client ruft ein konkretes Tool auf.
Wir haben tools/list und tools/call bereits kurz gesehen. Nun schauen wir systematischer darauf: welche Prozesse sie abdecken und was genau im result zurückkommt.
5.1.1. Werkzeugliste – tools/list
Wir haben den Request für tools/list gesehen. Sehen wir uns die Struktur der Antwort an. Die MCP‑Spezifikation sagt: In result.tools soll ein Array von Objekten zurückgegeben werden, von denen jedes ein Tool beschreibt. Ein Tool hat zwingend:
- name – eindeutiger Name, über den später tools/call aufgerufen wird;
- title – kurzer Titel (den sowohl Mensch als auch Modell sehen);
- description – ausführlichere Beschreibung dessen, was das Tool tut, so als erklärten Sie es einer Kollegin/einem Kollegen;
- inputSchema – die JSON Schema der Tool‑Argumente.
Für unser suggest_gifts kann die Antwort auf tools/list so aussehen (stark vereinfacht):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "suggest_gifts",
"title": "Gift ideas generator",
"description": "Suggests gift ideas for a given occasion and budget.",
"inputSchema": {
"type": "object",
"properties": {
"occasion": { "type": "string" },
"budget": { "type": "number" },
"recipient": { "type": "string" }
},
"required": ["occasion", "budget"]
}
}
],
"nextCursor": null
}
}
Wenn Sie im Apps SDK bereits inputSchema bei der Tool‑Registrierung geschrieben haben, haben Sie dieses Objekt praktisch schon gesehen – nur „von oben“ als TypeScript‑Objekt. MCP überträgt es einfach über das Protokoll an den Client.
5.1.2. Tool‑Aufruf – tools/call
Das Aufrufformat haben wir angerissen. Die MCP‑Spezifikation beschreibt, dass params enthalten muss:
- name – den Tool‑Namen;
- arguments – ein Objekt, das der inputSchema entspricht.
Zum Beispiel:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "suggest_gifts",
"arguments": {
"occasion": "wedding",
"budget": 150,
"recipient": "coworker from marketing"
}
}
}
Als Antwort gibt der Server ein result mit content, structuredContent und optional _meta zurück (z. B. mit openai/outputTemplate, wenn Sie dieses Tool mit einem konkreten Widget verknüpfen wollen).
Diese Kette tools/list → tools/call ist der grundlegende Zyklus der MCP‑Tools: zuerst Discovery, dann Nutzung.
Resources: adressierbare Daten
Resources in MCP sind beliebige Datenstücke, auf die der Client per URI zugreifen kann: Dateien, DB‑Einträge, Konfigurationen, Kataloge usw.
Sie haben einen standardisierten Operationssatz:
- resources/list – um zu erfahren, welche Ressourcen es gibt;
- resources/read – um eine konkrete Ressource (oder einen Teil) zu lesen.
Stellen wir uns die Ressource gift_catalog vor, die einen Basiskatalog für Geschenke beschreibt: Kategorien, Marken, minimale und maximale Preise. Der Server kann sie unter der URI "mcp://gift-server/resources/gift_catalog" veröffentlichen.
Die Antwort auf resources/list könnte so aussehen (vereinfacht):
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resources": [
{
"uri": "mcp://gift-server/resources/gift_catalog", // nur eine eindeutige Zeichenfolge. MCP ist kein Protokoll.
"name": "gift_catalog",
"description": "Base catalog of gifts with categories and prices",
"mimeType": "application/json"
}
],
"nextCursor": null
}
}
Und das Lesen der Ressource – resources/read:
{
"jsonrpc": "2.0",
"id": 4,
"method": "resources/read",
"params": {
"uri": "mcp://gift-server/resources/gift_catalog"
}
}
Die Antwort kann den eigentlichen Inhalt und Metadaten enthalten:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"contents": [
{
"uri": "mcp://gift-server/resources/gift_catalog",
"mimeType": "application/json",
"text": "{\"categories\":[\"boardgames\",\"books\"]}"
}
]
}
}
Die Hauptidee: Eine Ressource sind adressierbare Daten, Tools sind Operationen. Beides macht MCP im Protokoll explizit.
Prompts: wiederverwendbare Vorlagen
Prompts sind „vorgefertigte Hinweise“ oder Vorlagen, die der Server dem Client bereitstellen kann. MCP behandelt sie als Primitive mit:
- einem Namen;
- einem menschenlesbaren Titel/Beschreibung;
- Inhalt (oft eine System‑Prompt‑Vorlage oder ein Satz von Few‑Shot‑Beispielen).
Und, erwartbar, es gibt zwei Methoden:
- prompts/list – um zu erfahren, welche Prompts es gibt;
- prompts/get – um den Inhalt eines Prompts zu holen.
Zum Beispiel möchten Sie einen besonderen Stil für Glückwunschtexte zusammen mit dem Geschenk vorgeben. Dann kann man im MCP‑Server den Prompt gift_congrats_style deklarieren.
Die Antwort auf prompts/list könnte so aussehen:
{
"jsonrpc": "2.0",
"id": 10,
"result": {
"prompts": [
{
"name": "gift_congrats_style",
"description": "Style guide for birthday congratulations in a friendly tone"
}
]
}
}
Und prompts/get liefert dann den eigentlichen Text (oder strukturierte Inhalte), den der Client anschließend als Teil des System‑Prompts an die LLM übergeben kann. Beispiel für Anfrage und Antwort:
{
"jsonrpc": "2.0",
"id": 11,
"method": "prompts/get",
"params": {
"name": "gift_congrats_style"
}
}
{
"jsonrpc": "2.0",
"id": 11,
"result": {
"prompt": {
"name": "gift_congrats_style",
"messages": [
{
"role": "system",
"content": [
{
"type": "text",
"text": "You are a friendly assistant that writes short, warm birthday congratulations..."
}
]
}
]
}
}
}
6. Wie das mit dem Apps SDK und unserem Widget zusammenhängt
Jetzt wirkt MCP‑JSON möglicherweise immer noch etwas sperrig. Verknüpfen wir das mit dem, was Sie bereits über das Apps SDK gemacht haben.
Zur Erinnerung: Im Frontend des Widgets könnte Ihr Code so aussehen:
// innerhalb einer React-Komponente in der ChatGPT-Sandbox
async function fetchGifts() {
const result = await window.openai.callTool("suggest_gifts", {
occasion: "birthday",
budget: 50,
recipient: "friend who loves sci-fi"
});
console.log(result);
}
Auf Ebene des Apps SDK ist das eine bequeme Funktion, die:
- die URL des MCP‑Servers kennt (aus der App‑Konfiguration);
- den Tool‑Eintrag über den Namen suggest_gifts findet;
- Ihren Aufruf als MCP‑Request tools/call verpackt;
- ihn über das gewählte Transportprotokoll sendet (HTTP/SSE);
- auf die MCP‑Reply wartet, das result entpackt und es Ihnen in JavaScript als result zurückgibt.
Als Schema gezeichnet sieht das ungefähr so aus:
sequenceDiagram
participant Widget
participant AppsSDK as Apps SDK
participant MCP as MCP-Server
Widget->>AppsSDK: window.openai.callTool("suggest_gifts", {...})
AppsSDK->>MCP: JSON { id:7, method:"tools/call", params:{...} }
MCP-->>AppsSDK: JSON { id:7, result:{ content, structuredContent } }
AppsSDK-->>Widget: result (ToolOutput)
Widget->>Widget: setState(toolOutput)
Das Verständnis des MCP‑Formats schenkt Ihnen zwei großartige Fähigkeiten.
Erstens können Sie rohe MCP‑Logs (z. B. im MCP Inspector, dazu gibt es eine eigene Vorlesung) sinnvoll betrachten und sehen: welcher tools/call genau rausging, welche Argumente darin waren, was im result oder error zurückkam.
Zweitens können Sie bei der Gestaltung von Tools und Ressourcen nicht nur in TypeScript‑Typen denken, sondern auch in MCP‑Schemata: wie das im JSON aussehen wird und wie bequem das für andere Clients ist (z. B. Agenten, die sich ebenfalls mit Ihrem MCP‑Server verbinden können).
7. Mini‑Praxis: MCP‑JSON lesen und „reparieren“
Damit das MCP‑Format „ins Blut übergeht“, ist es am besten, zwei Nachrichten einmal händisch in ihre Teile zu zerlegen. Nehmen wir einen Beispiel‑Dialog tools/list → tools/call → Ergebnis.
Der Client möchte die Werkzeugliste
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Was wir sehen:
- Das ist ein Request (es gibt eine id);
- die Methode tools/list, es geht also um Discovery von Tools;
- Parameter leer, ohne Paginierung.
Der Server antwortet:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "suggest_gifts",
"title": "Gift ideas generator",
"description": "Suggests gift ideas",
"inputSchema": { "type": "object", "properties": { "occasion": { "type": "string" } } }
}
]
}
}
Sofort ist zu erkennen, dass dies die Antwort auf genau diese Anfrage ist (gleiche id: 1), das Protokoll erfolgreich war (result vorhanden, kein error), und der Client nun weiß, dass es das Tool suggest_gifts gibt.
Der Client ruft das Tool auf
Als Nächstes macht der Client tools/call:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "suggest_gifts",
"arguments": {
"occasion": "anniversary"
}
}
}
Wenn der Server zusätzlich budget erwartet, das Modell es aber nicht angegeben hat, kann der Server:
- entweder einen Protokollfehler zurückgeben (z. B. error mit dem Code „invalid params“);
- oder eine Standardentscheidung treffen (z. B. ein mittleres Budget verwenden) und ein normales result zurückgeben.
In den von uns eingeführten Begriffen ist die erste Variante ein Protokollfehler (error auf oberster Ebene), die zweite bereits Business‑Logik: Sie geben trotzdem ein valides result zurück und entscheiden, ob Sie eine solche Situation als Business‑Fehler betrachten (isError: true) oder als normales Verhalten.
Die Antwort bei fehlerhaften Argumenten könnte so aussehen:
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32602,
"message": "Missing required property 'budget' in arguments"
}
}
Auch hier unterscheiden wir das vom Business‑Fehler: Das Protokoll ist verletzt (Argumente entsprechen nicht dem Schema), daher ist hier ein error angemessen.
Kaputtes Beispiel: Bug suchen
Hier ein JSON, das Anfänger gelegentlich produzieren:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"tool": "suggest_gifts",
"args": {
"occasion": "birthday",
"budget": 100
}
}
}
Auf den ersten Blick plausibel, doch wenn Sie es mit der MCP‑Spezifikation abgleichen, sehen Sie, dass die Felder tool und args nicht den erwarteten name und arguments entsprechen.
Ein MCP‑SDK‑Client/‑Server wird solches JSON vermutlich nie generieren, aber wenn Sie ohne Spezifikationswissen „per Hand“ integrieren, ist so ein Bug durchaus realistisch. Genau deshalb behandeln wir im Kurs das Protokoll „im Rohzustand“ – und nicht nur die SDK‑Wrapper.
8. Typische Fehler bei der Arbeit mit MCP‑Nachrichten
Fehler Nr. 1: Protokoll‑ und Business‑Fehler vermischen.
Entwickler packen aus Gewohnheit oft alles, was „schiefging“, in einen error auf oberster Ebene – sowohl fehlende Ressourcen als auch falsche Argumente oder DB‑Abstürze. Im Kontext von MCP ist es hilfreich zu trennen: Wenn JSON‑Struktur und Aufrufschema verletzt sind (falsche Methode, falsche Felder, falsche Typen), dann ist error angebracht. Wenn das Tool jedoch die Domänen‑Operation nicht ausführen konnte (keine Geschenke für dieses Budget, Benutzer nicht gefunden), geben Sie besser ein valides result mit isError: true und einer verständlichen Nachricht im content zurück. So können sowohl das ChatGPT‑Modell als auch Debugger „Verbindung kaputt“ von „Server hat bewusst abgelehnt“ korrekt unterscheiden.
Fehler Nr. 2: das Feld id und die Korrelation von Anfragen ignorieren.
In MCP‑Server‑Logs sieht man manchmal manuelle Ausgaben ohne id oder mit wiederholten id‑Werten für unterschiedliche aktive Anfragen. Im einsträngigen Hello‑World mag das noch gutgehen, aber sobald parallele Aufrufe oder Retries ins Spiel kommen, wird es schwer, Antworten den jeweiligen Anfragen zuzuordnen. JSON‑RPC verlangt ausdrücklich eine eindeutige id für die Lebensdauer der Anfrage, und MCP baut auf dieser Regel auf. Wenn Sie offizielle SDKs nutzen, müssen Sie über id kaum nachdenken; sobald Sie Transport oder Logging selbst schreiben, vergessen Sie nicht, die id zu speichern und auszugeben – das ist das erste, worüber Sie seltsame Bugs debuggen werden.
Fehler Nr. 3: instabile result‑Strukturen für dieselbe Methode.
Es ist verlockend, das Antwortformat „ein wenig“ je nach Situation zu ändern: mal ein Array von Geschenken, mal ein Objekt mit nur einer Zeichenkette, mal nur text ohne structuredContent. Das Modell verkraftet solche Kunststücke vielleicht, Ihre Widgets und andere MCP‑Clients eher nicht. Die MCP‑Spezifikation beschreibt für jede Methode eine vorhersehbare result‑Struktur; halten Sie sich möglichst daran. Wenn Sie ein anderes Format brauchen, deklarieren Sie besser ein separates Tool oder eine Version, statt das Schema „on the fly“ zu ändern.
Fehler Nr. 4: überflüssige oder fehlende Felder in params.
Ein typisches Problem von Custom‑Implementierungen ist, in params etwas zu ergänzen, was MCP nicht erwartet, oder ein Pflichtfeld zu vergessen. Etwa toolName statt name in tools/call zu senden oder resourceId statt uri in resources/read. MCP‑SDKs validieren solche Dinge in der Regel und werfen eine verständliche Ausnahme; wenn Sie näher am Protokoll arbeiten, können Sie lange rätseln, warum „der Server mich nicht versteht“. Ein guter Kniff: Halten Sie neben dem Handler ein Beispiel eines korrekten JSON‑Requests aus der Spezifikation oder den Logs eines funktionierenden Clients bereit und vergleichen Sie es mit dem, was Sie senden.
Fehler Nr. 5: Notifications als „zweiten Antwortkanal“ verwenden wollen.
Manchmal beginnen Entwickler, nachdem sie Notifications gesehen haben, Ergebnisse von Operationen über Benachrichtigungen statt über gewöhnliche Replies zu schicken: „Wir sind doch schon in MCP und haben SSE, also pushen wir alles per Notifications.“ Das Problem ist, dass JSON‑RPC‑Benachrichtigungen per Definition nicht an eine konkrete id gebunden sind und vom Client nicht als Antwort auf eine Anfrage wahrgenommen werden. Ergebnis: schwierigeres Debugging und unmöglich, eine Nachricht einem Tool‑Aufruf zuzuordnen. Notifications sind hervorragend für Ereignisse geeignet (Tool/Resource/Prompt‑Liste hat sich geändert, neuer Fortschritt, Log eingetroffen), aber nicht für gewöhnliche Antworten auf tools/call und ähnliche.
Fehler Nr. 6: MCP‑Logs und Inspektoren nicht ansehen.
Der menschlichste Fehler – versuchen, die Integration nur über das ChatGPT‑UI zu debuggen: „Button geklickt, irgendwas kam nicht, kümmere mich später.“ Solange Sie die rohen MCP‑Nachrichten (Requests, Replies, Notifications) nicht sehen, ist es schwer zu verstehen, auf welcher Ebene das Problem liegt: Hat das Modell das Tool nicht aufgerufen, hat das Apps SDK den MCP‑Server nicht erreicht, hat der Server das falsche JSON zurückgegeben oder ist alles erst beim Rendern des Widgets kaputt gegangen? MCP Inspector / Jam und strukturiertes Logging der MCP‑Nachrichten sind Ihre besten Freunde. Sobald Sie einmal einen echten tools/call und tools/list in den Logs gesehen haben, hört das MCP‑Nachrichtenformat endgültig auf, „Magie“ zu sein, und wird zur normalen Ingenieursroutine.
GO TO FULL VERSION