Lässt es sich mit cremefarbenem Papier, einer Tönung, mehr Kontrast oder größerer Schrift besser lesen? Wählen Sie Ihre Farben und Ihre Schrift, einmalig für alle Seiten.

Kostenlose Lektion

MCP-Nachrichten lesen und schreiben

Nach dieser Lektion: Ich kann die JSON-Nachrichten lesen und schreiben, die ein Server und ein Client austauschen.

Lektion, 70 Min.6 BefehleLab für KI-Coding-Agenten: mit opencode Code erklären und reparieren lassen und die Arbeit prüfen

Wie ein MCP-Server Nachrichten liest

Ein MCP-Server ist ein Programm, das JSON-Nachrichten aus seiner Eingabe liest und JSON-Nachrichten in seine Ausgabe schreibt, eine Nachricht pro Zeile. Ein Server, der das schlecht macht, läuft allein und scheitert an jedem echten Client.

Modul 67, Lektion 5, zeigte die Seite des Clients und die Arten von Nachricht. Hier schreiben Sie die Leseschleife eines Servers.

Am Ende dieser Lektion

Notiz

Ich kann die JSON-Nachrichten lesen und schreiben, die ein Server und ein Client austauschen.

Schlüsselwörter

BegriffWas er bedeutetBeispiel
PufferText, der aufbewahrt wird, bis eine ganze Zeile da ist
Rahmeneine ganze Nachricht, am Zeilenumbruch aus dem Strom geschnitten
stdoutder Ausgabestrom: Hier gehen nur Nachrichten hin
stderrder Fehlerstrom: Hier gehen Protokolle hin
Parse errordie Zeile war gar kein JSON-32700
Invalid RequestJSON, aber keine Anfrage-32600

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Ein Strom wird in Zeilen geschnitten

Die Eingabe kommt nicht als ganze Nachrichten an. Sie kommt in Stücken. Ein Stück kann eine halbe Nachricht enthalten, eine ganze oder zweieinhalb.

Der Server führt einen Puffer. Er hängt jedes Stück an, schneidet jede vollständige Zeile ab und behält den Rest. Eine Nachricht enthält nie einen rohen Zeilenumbruch, also beendet ein Zeilenumbruch immer eine.

Beispiel: drei Stücke

Stück trifft einPuffer danachFertige Nachrichten
{"id":1} {"id{"id1 Nachricht: {"id":1}
":2} (leer)1 Nachricht: {"id":2}
{"i{"iNoch keine

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Auch eine schlechte Zeile bekommt eine Antwort

Eine Zeile, die kein JSON ist, bekommt einen Parse error. Der Server kann keine id lesen, die id ist also null.

Eine Zeile, die JSON, aber keine Anfrage ist, bekommt Invalid Request. Lässt sich eine id lesen, wird sie übernommen. Eine Anfrage ohne id ist eine Benachrichtigung, und selbst eine Benachrichtigung für eine unbekannte Methode bekommt nichts.

Was der Server zurückschickt

ZeileAntwort
kein jsonFehler -32700, id null
{"jsonrpc":"2.0","id":5,"method":5}Fehler -32600, id 5
[1,2]Fehler -32600, id null
{"jsonrpc":"2.0","method":"unsinn"}Nichts: eine Benachrichtigung

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Antworten können in beliebiger Reihenfolge kommen

Eine langsame Anfrage darf eine schnelle nicht aufhalten. Der Server beantwortet jede Anfrage, sobald ihre Antwort fertig ist.

Ein Client kann Antworten also nicht nach ihrer Stelle zuordnen. Er ordnet sie nach der id zu. Darum übernimmt der Server die id genau so, wie sie kam, als Zahl oder als Text.

Ein häufiger Fehler

Warnung

Viele suchen Fehler mit einer Druckzeile. Auf stdout ist diese Zeile keine Nachricht, und der Client scheitert an ihr. Nur Nachrichten gehen nach stdout. Schreiben Sie Protokolle nach stderr, das der Client zeigen oder ignorieren kann.

Modell-Helfer schreiben

Befehl
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const basis = process.env.AI_URL || \"http://ai:4000\";' 'async function fragen(nachrichten, probe, max) {' '  try {' '    const res = await fetch(basis + \"/v1/chat/completions\", { method: \"POST\", headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" }, body: JSON.stringify({ model: \"lab\", messages: nachrichten, max_tokens: max || 80 }), signal: AbortSignal.timeout(30000) });' '    if (res.ok === false) throw new Error(\"Status \" + res.status);' '    const daten = await res.json();' '    return { text: daten.choices[0].message.content, aufgezeichnet: false };' '  } catch (fehler) {' '    console.log(\"Aufgezeichnete Probe: Kein Modell hat geantwortet (\" + fehler.message + \").\");' '    return { text: probe, aufgezeichnet: true };' '  }' '}' 'module.exports = { fragen };' > model.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.

Server schreiben, Teil 1

Befehl
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let puffer = \"\";' 'function senden(m) { process.stdout.write(JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, m)) + \"\n\"); }' 'function fehler(id, code, text) { senden({ id: id, error: { code: code, message: text } }); }' 'function protokoll(text) { process.stderr.write(\"Protokoll: \" + text + \"\n\"); }' 'function verarbeiten(zeile) {' '  let nachricht;' '  try { nachricht = JSON.parse(zeile); } catch (problem) { return fehler(null, -32700, \"Parse error\"); }' '  const gesehen = nachricht && (typeof nachricht.id === \"number\" || typeof nachricht.id === \"string\") ? nachricht.id : null;' '  const gut = nachricht && typeof nachricht === \"object\" && nachricht.jsonrpc === \"2.0\" && typeof nachricht.method === \"string\";' '  if (Boolean(gut) === false) return fehler(gesehen, -32600, \"Invalid Request\");' '  protokoll(\"erhalten \" + nachricht.method);' > server1.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.

Server schreiben, Teil 2

Befehl
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  if (nachricht.id === undefined) return;' '  const ms = (nachricht.params || {}).ms || 0;' '  if (nachricht.method === \"ping\") return senden({ id: nachricht.id, result: {} });' '  if (nachricht.method === \"warten\") return setTimeout(function () { senden({ id: nachricht.id, result: { gewartet: ms } }); }, ms);' '  fehler(nachricht.id, -32601, \"Method not found: \" + nachricht.method);' '}' 'process.stdin.on(\"data\", function (stueck) {' '  puffer += stueck.toString();' '  let stelle = puffer.indexOf(\"\n\");' '  while (stelle >= 0) {' '    const zeile = puffer.slice(0, stelle);' '    puffer = puffer.slice(stelle + 1);' '    if (zeile.trim().length > 0) verarbeiten(zeile);' '    stelle = puffer.indexOf(\"\n\");' '  }' '});' 'protokoll(\"bereit\");' >> server1.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.

Skript-Client schreiben, Teil 1

Befehl
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { fragen } = require(\"./model\");' 'const server = require(\"child_process\").spawn(\"node\", [\"server1.js\"]);' 'const nachricht = function (obj) { return JSON.stringify(Object.assign({ jsonrpc: \"2.0\" }, obj)); };' 'const warten = function (id, ms) { return nachricht({ id: id, method: \"warten\", params: { ms: ms } }) + \"\n\"; };' 'const ping = nachricht({ id: 3, method: \"ping\" }) + \"\n\";' 'const schlecht = \"kein json\n\" + nachricht({ id: 5, method: 5 }) + \"\n\" + nachricht({ method: \"notifications/x\" }) + \"\n\";' 'const schritte = [[0, warten(1, 80)], [10, warten(2, 10)], [20, ping.slice(0, 12)], [40, ping.slice(12) + schlecht]];' 'function starten() {' '  schritte.forEach(function (s) {' '    setTimeout(function () { console.log(\"-> \" + s[1].trim().split(\"\n\").join(\" | \")); server.stdin.write(s[1]); }, s[0]);' '  });' > client1.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.

Skript-Client schreiben, Teil 2

Befehl
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  setTimeout(function () { server.stdin.end(); }, 200);' '}' 'require(\"readline\").createInterface({ input: server.stderr }).on(\"line\", function (zeile) {' '  console.log(\"stderr \" + zeile);' '  if (zeile === \"Protokoll: bereit\") starten();' '});' 'require(\"readline\").createInterface({ input: server.stdout }).on(\"line\", function (zeile) { console.log(\"<- \" + zeile); });' 'server.on(\"close\", async function () {' '  const frage = \"Warum muss eine Antwort die id ihrer Anfrage tragen? Antworten Sie in einem Satz.\";' '  const antwort = await fragen([{ role: \"user\", content: frage }], \"Damit der Client erkennt, zu welcher Anfrage jede Antwort gehört, denn Antworten können in beliebiger Reihenfolge eintreffen.\", 60);' '  console.log(\"Modell: \" + antwort.text);' '});' >> client1.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.

Client gegen den Server ausführen

Befehl
docker exec -w /home/node/project agent node client1.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.

Was der Client tut

Der Client startet den Server und wartet auf dessen Bereit-Zeile auf stderr. Er sendet zwei langsame Anfragen, ein halbes ping und dann den Rest des ping mit drei weiteren Zeilen.

Ein Aufruf am Ende bittet ein Modell um einen Satz. Ohne Modell druckt der Run dafür eine Aufgezeichnete Probe.

Die Antworten, in der Reihenfolge ihres Eintreffens

Mit <- gedruckte ZeileWarum sie dann kam
id 2, gewartet 10Das kurze Warten endete zuerst
id 3, ein leeres ErgebnisDas ping war ganz, als seine zweite Hälfte kam
id null, Fehler -32700Die Zeile "kein json"
id 5, Fehler -32600Eine Methode, die eine Zahl ist
id 1, gewartet 80Das lange Warten endete zuletzt

Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.

Über dieses Lab und seinen Quellcode: Lab für KI-Coding-Agenten: mit opencode Code erklären und reparieren lassen und die Arbeit prüfen

Sie unterrichten eine Gruppe? Weisen Sie dieses Modul Ihren Studierenden zu, mit Handbuch für Lehrende, automatisch geprüften Aufgaben und einem Lab für jede Person.