Was ein Werkzeugserver wirklich sendet
Das Model Context Protocol (MCP) ist ein Standardweg, auf dem ein Agent Werkzeuge eines anderen Programms findet und aufruft. Seine Nachrichten sind schlichtes JSON, eine pro Zeile, über Standardeingabe und -ausgabe. Wer die Nachrichten lesen kann, kann jeden Werkzeugserver untersuchen.
Läuft ein Werkzeugserver für sich allein, aber nicht mit Ihrem Agenten, sendet er meist eine Nachricht in falscher Form.
Am Ende dieser Lektion
Notiz
Ich kann die Nachrichten lesen und schreiben, die ein Werkzeugserver und sein Client austauschen.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| JSON-RPC | ein Nachrichtenformat zum Aufrufen von Methoden, in JSON | |
| Anfrage | eine Nachricht mit id, die eine Antwort erwartet | |
| Benachrichtigung | eine Nachricht ohne id und ohne Antwort | |
| stdio | Standardeingabe und -ausgabe eines Prozesses | |
| Fehlercode | eine Zahl, die die Art des Fehlers nennt | -32601 |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
JSON-RPC kennt drei Arten von Nachrichten
Eine Anfrage hat eine id, einen Methodennamen und optionale Parameter. Eine Antwort übernimmt diese id und enthält entweder ein Ergebnis oder einen Fehler. Eine Benachrichtigung hat keine id, also antwortet nichts darauf.
Über stdio ist jede Nachricht eine Zeile JSON. Der Server schreibt nur Nachrichten auf seine Ausgabe und schickt Logs auf seinen Fehlerkanal.
Ausgearbeitetes Beispiel: drei Nachrichten
| Art | Beispiel | Antwort |
|---|---|---|
| Anfrage | {"jsonrpc":"2.0","id":2,"method":"tools/list"} | Eine Antwort mit id 2 |
| Antwort | {"jsonrpc":"2.0","id":2,"result":{"tools":[]}} | Keine |
| Benachrichtigung | {"jsonrpc":"2.0","method":"notifications/initialized"} | Keine |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Das ist die Form, nicht das ganze Protokoll
Wichtig
Der Server unten hat die Form des echten Protokolls, nur kleiner. Ein echter Server nutzt das offizielle SDK, das auch Versionen, Abbruch, weitere Inhaltsarten und viele weitere Methoden behandelt. Hier wird nichts installiert.
Den Werkzeugserver schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const PREISE = { brezel: 90, semmel: 45 };' 'const WERKZEUGE = [{ name: \"preis_lesen\", description: \"Preis eines Produkts der Bäckerei in Cent\", inputSchema: { type: \"object\", properties: { produkt: { type: \"string\" } }, required: [\"produkt\"] } }];' 'function senden(nachricht) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, nachricht)) + \"\n\"); }' 'function antworten(id, result) { senden({ id: id, result: result }); }' 'function scheitern(id, code, text) { senden({ id: id, error: { code: code, message: text } }); }' 'function inhalt(woerter, isError) { return { content: [{ type: \"text\", text: woerter }], isError: isError }; }' > server.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Werkzeugserver schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function aufrufen(id, params) {' ' if (params.name === \"preis_lesen\") {' ' const preis = PREISE[(params.arguments || {}).produkt];' ' if (preis === undefined) return antworten(id, inhalt(\"Unbekanntes Produkt\", true));' ' return antworten(id, inhalt(preis + \" Cent\", false));' ' }' ' return scheitern(id, -32602, \"Unbekanntes Werkzeug: \" + params.name);' '}' 'function behandeln(msg) {' ' if (msg.id === undefined) return;' ' if (msg.method === \"initialize\") return antworten(msg.id, { protocolVersion: \"2024-11-05\", capabilities: { tools: {} }, serverInfo: { name: \"baeckerei\", version: \"0.1\" } });' ' if (msg.method === \"tools/list\") return antworten(msg.id, { tools: WERKZEUGE });' ' if (msg.method === \"tools/call\") return aufrufen(msg.id, msg.params || {});' ' return scheitern(msg.id, -32601, \"Methode nicht gefunden: \" + msg.method);' '}' >> server.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Werkzeugserver schreiben, Teil 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'require(\"readline\").createInterface({ input: process.stdin }).on(\"line\", function (zeile) {' ' try { behandeln(JSON.parse(zeile)); } catch (fehler) { scheitern(null, -32700, \"Analysefehler\"); }' '});' >> server.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Client mit Drehbuch schreiben, Teil 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const server = require(\"child_process\").spawn(\"node\", [\"server.js\"]);' 'const aufruf = function (n, name, args) { return { id: n, method: \"tools/call\", params: { name: name, arguments: args } }; };' 'const drehbuch = [' ' { id: 1, method: \"initialize\", params: { protocolVersion: \"2024-11-05\", capabilities: {}, clientInfo: { name: \"demo\", version: \"0.1\" } } },' ' { method: \"notifications/initialized\" },' ' { id: 2, method: \"tools/list\" },' ' aufruf(3, \"preis_lesen\", { produkt: \"semmel\" }),' ' aufruf(4, \"preis_lesen\", { produkt: \"kiwi\" }),' ' aufruf(5, \"alles_loeschen\", {}),' ' { id: 6, method: \"resources/list\" },' '];' > client.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Client mit Drehbuch schreiben, Teil 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let offen = 6;' 'require(\"readline\").createInterface({ input: server.stdout }).on(\"line\", function (zeile) {' ' console.log(\"<- \" + zeile);' ' offen -= 1;' ' if (offen === 0) server.stdin.end();' '});' 'drehbuch.forEach(function (m) {' ' const ganz = Object.assign({ jsonrpc: \"2.0\" }, m);' ' console.log(\"-> \" + JSON.stringify(ganz));' ' server.stdin.write(JSON.stringify(ganz) + \"\n\");' '});' >> client.js"Hier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Client gegen den Server starten
docker exec -w /home/node/project agent node client.jsHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Eine mögliche Ausgabe
| Anfrage | Antwort |
|---|---|
| initialize | Ein Ergebnis mit Protokollversion und Servernamen |
| tools/list | Ein Ergebnis, das preis_lesen auflistet |
| preis_lesen für eine Semmel | Ein Ergebnis mit dem Text 45 Cent |
| preis_lesen für eine Kiwi | Ein Ergebnis mit isError true und Unbekanntes Produkt |
| alles_loeschen | Ein Fehler mit Code -32602 |
| resources/list | Ein Fehler mit Code -32601 |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Eine Sitzung folgt einer Reihenfolge
Der Client sendet zuerst initialize, und der Server antwortet mit dem, was er kann. Dann sendet der Client die Benachrichtigung, dass er bereit ist. Erst danach fragt er tools/list ab und nutzt tools/call.
Der Agent zeigt dem Modell die aufgelisteten Werkzeuge. Verlangt das Modell eines, sendet der Client tools/call.
Zwei Arten von Fehlern
Ein Protokollfehler ist eine JSON-RPC-Fehlerantwort. Die Anfrage selbst war falsch: unbekannte Methode, unbekanntes Werkzeug oder unlesbares JSON. Jeder hat einen Code, etwa -32601 für eine fehlende Methode.
Ein Werkzeugfehler ist ein normales Ergebnis mit isError auf true. Der Aufruf war in Ordnung, aber das Werkzeug konnte es nicht tun. Das Modell liest den Text und kann etwas anderes versuchen.
Ein häufiger Fehler
Warnung
Viele glauben, jeder Fehler müsse ein JSON-RPC-Fehler sein. Eine Fehlerantwort sagt, dass die Anfrage falsch gebaut ist. Ein Werkzeug, das lief und das Produkt nicht fand, soll ein Ergebnis mit isError true liefern. So sieht das Modell den Grund und kann sich fangen.