# Wie MCP-Schema-Drift langlebige Agents bricht

Langlebige Agents machen eine falsche Annahme besonders teuer: dass die zu Beginn einer Sitzung geladene Toolbeschreibung für den gesamten Lauf gültig bleibt. Das tut sie oft nicht. Ein Server kann einen neuen Build ausrollen, eine kontospezifische Funktion aktivieren, eine angebundene API austauschen oder einen Ergebnisvertrag korrigieren, während ein Agent noch anhand der Toolstruktur von gestern plant.

Das ist MCP-Schema-Drift. Es handelt sich nicht um einen exotischen Sonderfall des Protokolls, sondern um eine Vertragsänderung zwischen einem Client, der bereits einen Plan gebildet hat, und einem Server, der inzwischen weitergezogen ist. Wenn du nach einem Deployment nur eine frische Verbindung testest, prüfst du den einfachen Fall und lässt den gefährlichen unberührt.

Das Model Context Protocol ermöglicht es Servern, Änderungen an der Tool-Liste anzukündigen. Es macht aus einem alten Client-Cache aber keinen neuen Vertrag, repariert keine bereits von einem Modell vorgeschlagenen Tool-Aufrufe und entscheidet nicht, ob eine alte Anfrage noch sicher ist. Das sind Engineering-Entscheidungen. Triff sie bewusst und teste sie bei laufender Sitzung.

## Eine Toolbeschreibung gehört zum Sitzungsstatus

Ein Tool-Schema ist ausführbarer Kontext. Ein Agent nutzt Namen, Beschreibung, Eingabeschema, Annotationen und manchmal das Ausgabeschema, um zu entscheiden, welche Aktion er anfordern soll. Viele Clients wandeln `tools/list` außerdem in lokale Strukturen um, kompilieren Validatoren oder legen eine verkürzte Toolbeschreibung in den Modellkontext. Keine dieser Kopien ändert sich allein dadurch, dass der Server eine andere Version ausrollt.

Das gilt auch dann, wenn das Netzwerkprotokoll exakt wie vorgesehen funktioniert. Angenommen, ein Client startet mit diesem Tool:

```json
{
  "name": "deploy_preview",
  "description": "Deploy the current branch to a preview environment.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "branch": { "type": "string" }
    },
    "required": ["branch"],
    "additionalProperties": false
  }
}
```

Eine Stunde später ändert der Server den Vorgang so, dass er ausdrücklich ein Feld `region` verlangt. Ein neu verbundener Client sieht das neue Schema und kann es liefern. Der ältere Client hält `{\"branch\":\"fix-login\"}` weiterhin für eine vollständige Anfrage.

Hier gibt es drei getrennte Zustände, die Teams regelmäßig vermischen:

1. Das **beworbene Schema** ist das, was der Server jetzt mit `tools/list` zurückgibt.
2. Der **Schema-Snapshot des Clients** ist das, was ein bestimmter Client beim letzten Abruf der Tools behalten hat.
3. Der **Ausführungsvertrag** ist das, was der Server akzeptiert und tut, wenn `tools/call` eintrifft.

Ein Server kann den ersten Zustand sofort aktualisieren. Er kann nicht voraussetzen, dass der zweite ebenfalls aktualisiert wurde. Für den dritten muss er eine Entscheidung treffen.

Das lediglich als Cache-Invalidierungsproblem zu bezeichnen, greift zu kurz. Cache-Invalidierung klingt nach veralteten Anzeigedaten. Ein Tool-Schema kann Zielkonten, Schreibbereiche, Bestätigungsfelder und die Bedeutung von Ergebnissen festlegen. Hält ein Agent die alte Version, kann die Abweichung zu fehlgeschlagener Arbeit, wiederholten Aufrufen oder einer Anfrage führen, die nun mehr bedeutet, als das Modell beabsichtigt hat.

Die sicherste Standardeinstellung ist einfach: Halte den akzeptierten Eingabevertrag eines ausgerollten Tools während eines Übergangsfensters rückwärtskompatibel, sofern die Annahme alter Eingaben nicht unsicher wäre. Wenn Sicherheit und Kompatibilität kollidieren, lehne die alte Struktur klar ab und verlange eine neue Entscheidung.

## `tools/list_changed` kündigt Änderungen an, synchronisiert sie aber nicht

Die MCP-Spezifikation definiert `notifications/tools/list_changed` für Server, deren Tool-Liste sich ändert. Der Server sendet eine Benachrichtigung, und ein Client kann seine Tools mit `tools/list` erneut abrufen. Die Spezifikation sieht außerdem vor, dass der Server seine Unterstützung für solche Benachrichtigungen während der Initialisierung in den Tool-Fähigkeiten bekannt gibt.

Das ist nützlich, aber das Wort «kann» trägt hier viel Verantwortung. Eine Benachrichtigung hat keine Antwort. Der Server kann allein daran nicht erkennen, ob der Client sie empfangen hat, ob er eine Aktualisierung durchgeführt hat, ob sein Tool-Cache aktualisiert wurde oder ob ein Modell bereits einen Aufruf anhand der alten Beschreibung vorbereitet hat.

Behandle die Benachrichtigung als Signal zur Ungültigkeit und nicht als Synchronisationsbarriere.

Ein Client, der Drift gut verarbeitet, sollte danach vier Dinge tun:

- `tools/list` erneut anfordern und die entsprechenden lokalen Definitionen atomar ersetzen.
- Den alten Snapshot lange genug behalten, um einen bereits geplanten Aufruf dem Schema zuzuordnen, aus dem er entstanden ist.
- Jeden wartenden Aufruf vor dem Senden erneut gegen das aktualisierte Schema prüfen.
- Dem Modell einen reparierbaren Fehler geben, wenn ein aus altem Kontext erstellter Aufruf nicht mehr ausgeführt werden kann.

Beim dritten Punkt sparen Clients oft am falschen Ende. Sie aktualisieren die sichtbare Tool-Liste, lassen aber einen bereits wartenden Aufruf mit dem alten Argumentobjekt weiterlaufen. Dadurch entsteht ein Wettlauf: Die Oberfläche sagt das eine, der Agent sendet etwas anderes, und der Server muss die Abweichung bereinigen.

Auch Server brauchen eine entsprechende Disziplin. Wenn sich ein Tool ändert, sende die Benachrichtigung erst, nachdem die neue Antwort von `tools/list` bereitsteht. Kündige keinen neuen Vertrag an, während noch ein alter Prozess für eine beliebige Zeitspanne Aufrufe bearbeitet. Wenn deine Deployment-Topologie das zulässt, nimm eine ausdrückliche Vertragsrevision in die Serverantwort auf und lehne Aufrufe ab, die auf einem Worker mit inkompatiblem Verhalten ankommen.

Die Benachrichtigung löst außerdem keine Probleme für Clients, die sie nicht unterstützen, während des Ereignisses die Verbindung verlieren oder über einen Vermittler mit eigenem Cache arbeiten. Kompatibilität bei `tools/call` bleibt nötig. Wenn der Server nur funktioniert, wenn jeder Client perfekt auf eine Benachrichtigung reagiert, funktioniert er in der Produktion nicht.

## Änderungen an Eingaben verursachen unterschiedliche Arten von Fehlern

Ein neues Feld ist nicht immer dieselbe Art von Änderung. Das Risiko hängt davon ab, ob das Feld Validierung, Bedeutung oder Berechtigungen verändert.

Eine optionale Anzeigeeinstellung ist meist sicher. Ein älterer Aufrufer lässt sie weg, und der Server wählt einen stabilen Standardwert. Auch ein optionaler Filter kann sicher sein, wenn das Weglassen dasselbe Ergebnis liefert wie zuvor.

Ein Pflichtfeld `region` für `deploy_preview` ist etwas anderes. Der alte Aufrufer erfüllt den Validator dann nicht mehr. Du kannst die Anfrage ablehnen oder einen Standardwert liefern. Die erste Variante unterbricht den Agent, stellt aber die Wahrheit klar. Die zweite ist nur dann akzeptabel, wenn dieser Standardwert für das Repository schon immer die beabsichtigte Region war und eine Bereitstellung nicht in eine sensiblere Umgebung umleiten kann.

Die Bedeutung eines Feldes zu ändern ist schlimmer, als ein Pflichtfeld hinzuzufügen. Stell dir ein Tool vor, das ursprünglich `project` als lesbaren Projektnamen akzeptiert. Der Server entscheidet später, dass `project` eine undurchsichtige Organisations-ID sein soll. Ein älterer Agent sendet vielleicht weiterhin `payments`, und der Server löst diese Zeichenfolge in einem unerwarteten Namensraum auf. Die Validierung funktioniert, die Anfrage ist erfolgreich, aber die Aktion ist falsch. Das ist ein semantischer Bruch und gefährlicher als ein sauberer Validierungsfehler.

Auch das Entfernen eines Eingabefelds erfordert Vorsicht. `additionalProperties: false` im JSON Schema macht den Bruch sichtbar. Ein alter Client sendet ein früher gültiges Argument und erhält einen Fehler. Ignoriert der Server das entfernte Feld stillschweigend, kann der Aufruf mit einer anderen Interpretation erfolgreich sein, als das Modell erwartet hat.

Der beliebte Rat «Sei großzügig bei dem, was du akzeptierst» ist für Aktions-Tools schlechter Rat. Er wurde populär, weil Integrationen dadurch auch bei schlampigen Änderungen weiterlaufen. Bei einer schreibgeschützten Formatierungseinstellung kann diese Toleranz harmlos sein. Bei einer HTTP-Anfrage mit Zugangsdaten, einem SSH-Befehl, einer Bereitstellung, Löschung oder Zahlung verwandelt großzügiges Parsen eine unklare Anfrage in serverseitiges Raten.

Verwende einen Kompatibilitätsadapter nur, wenn du sein Verhalten genau beschreiben kannst. Zum Beispiel:

```ts
function normalizeDeployArgs(raw: unknown) {
  if (!isPlainObject(raw)) {
    throw executionError("Expected an object for deploy_preview.");
  }

  if (typeof raw.branch !== "string" || raw.branch.length === 0) {
    throw executionError("The branch field must be a non-empty string.");
  }

  if (raw.region === undefined) {
    return { branch: raw.branch, region: "us-east-preview", schemaRevision: 1 };
  }

  if (raw.region !== "us-east-preview" && raw.region !== "eu-preview") {
    throw executionError("region must be us-east-preview or eu-preview.");
  }

  return { branch: raw.branch, region: raw.region, schemaRevision: 2 };
}
```

Dieser Adapter hat eine akzeptable Eigenschaft: Die alte Anfrage führt zum selben Vorschauziel wie zuvor. Er wäre nicht akzeptabel, wenn `us-east-preview` nach einer Änderung der Kontozuständigkeit nur eine bequeme Vermutung wäre.

Bei einer inkompatiblen Änderung solltest du mit einem Fehler antworten, den ein Agent verwenden kann. Nenne die vom Server erwartete Toolrevision sowie das fehlende oder veraltete Feld und fordere den Client auf, seine Tools zu aktualisieren. Gib keine vage Meldung wie «ungültige Eingabe» zurück. Modelle wiederholen vage Fehler oft mit kleinen Variationen. Klare Fehler lassen eine Reparatur zu.

## Änderungen an Ergebnisstrukturen können die nächste Entscheidung vergiften

Teams achten auf Eingabevalidierung, weil eine fehlerhafte Anfrage am Server stoppt. Änderungen an Ergebnissen prüfen sie weniger gründlich, weil die Aktion bereits abgeschlossen ist. Für Agents ist das die falsche Gewichtung. Das Ergebnis liefert oft die Grundlage für den nächsten Aufruf.

Angenommen, das ursprüngliche Ergebnis von `create_issue` sieht so aus:

```json
{
  "issue": {
    "id": "I-482",
    "url": "https://tracker.example/issues/I-482",
    "state": "open"
  }
}
```

Ein Agent kann `issue.id` auslesen, im Arbeitsgedächtnis speichern und später `add_comment` mit dieser Kennung aufrufen. Benennt ein überarbeiteter Server `id` in `issueId` um, legt das Ergebnis unter `data` ab oder ändert `state` von einer Zeichenfolge in ein Objekt, kann der nächste Aufruf des Agents weit entfernt vom ursprünglichen Aufruf scheitern. Noch schlimmer: Eine reine Textausgabe kann weiterhin einen plausiblen Satz enthalten, sodass das Modell eine Kennung aus dem Fließtext improvisiert.

MCP-Tool-Ergebnisse können Inhalt für das Modell und strukturierte Inhalte für die programmatische Verarbeitung enthalten. Wenn du ein Ausgabeschema veröffentlichst, muss die strukturierte Ausgabe der maßgebliche Vertrag für Maschinen sein. Halte den Text für Menschen, die ein Protokoll lesen, kurz und nützlich. Verlasse dich aber nicht darauf, dass Clients ihn zuverlässig auslesen.

Die Arbeit an MCP-Schemata rund um JSON Schema 2020-12 ist hier relevant. Die späteren Schema-Hinweise des Protokolls machen das Dialektmodell ausdrücklich, und Ausgabeschemata können mehr als eine objektförmige Teilmenge von JSON beschreiben. Das verbessert den Ausdruck, ist aber keine Erlaubnis, ein laufendes Ergebnis beiläufig umzuformen. Ein Client kann Ergebnisse mit einem bestimmten Draft, einem generierten Typ oder einem Decoder prüfen, der für deine neue Union- oder Array-Struktur keine Toleranz besitzt.

Für die Weiterentwicklung von Ergebnissen gelten diese Regeln:

1. Füge Felder hinzu, bevor du sie umbenennst oder entfernst.
2. Halte die Bedeutung von Feldern stabil, besonders bei Kennungen, Statuswerten und Zeitstempeln.
3. Nimm ein Feld wie `schema_revision` oder `result_version` in die strukturierte Ausgabe auf, wenn mehrere Interpretationen nebeneinander bestehen müssen.
4. Gib bei einem Ausführungsfehler ein vollständiges strukturiertes Fehlerobjekt zurück, statt ein erfolgreiches Ergebnis in eine unstrukturierte Entschuldigung zu verwandeln.
5. Entferne die alte Struktur erst, wenn alte Sitzungen beendet oder ein veröffentlichter Migrationszeitraum abgeschlossen ist.

Ein Feld für die Ergebnisrevision ist keine Dekoration. Es ermöglicht einem Client, zwischen «Der Server hat eine unvollständige alte Antwort geliefert» und «Der Server hat eine neue Antwort geliefert, in der ein optionales Feld fehlt» zu unterscheiden. Das ist wichtig, wenn ein Agent entscheidet, ob er wiederholt, den Nutzer fragt oder mit einer folgenreichen Aktion fortfährt.

Versioniere nicht jede Ausgabe nur deshalb, weil es möglich ist. Setze eine Revisionsmarkierung dort ein, wo mehrere unabhängig ausgerollte Verbraucher sie brauchen. Für einen kleinen privaten Server mit einem einzigen gebündelten Client reicht eine additive, stabile Struktur vielleicht aus. Bei einem Tool, das von mehreren Agent-Hosts, Workern und Plugins gemeinsam genutzt wird, spart eine ausdrückliche Revisionsinformation während eines Vorfalls viel Rätselarbeit.

## Der wichtige Test führt einen alten Plan gegen einen neuen Server aus

Ein frischer Client an einem neuen Server zeigt, dass das neue Schema gültig ist. Über Drift sagt das nichts aus. Der nötige Test lässt einen Client einen Snapshot erstellen, danach den Server ändern und anschließend Aufrufe ausführen, die aus diesem Snapshot stammen.

Baue den Test mit zwei Server-Fixtures. Fixture A bewirbt die alte Tooldefinition. Fixture B bewirbt die neue Definition und legt fest, wie alte Argumente behandelt werden. Der Client bleibt während des Wechsels verbunden. Wenn dein Server sein Verhalten nicht ohne Neustart ändern kann, setze einen deterministischen Testschalter hinter die Tool-Registry, statt das Timing über das Deploymentsystem nachbilden zu wollen.

Das ist das kleinste sinnvolle Protokoll:

```text
1. Client initializes and receives tools.listChanged capability.
2. Client calls tools/list and stores deploy_preview revision 1.
3. Client prepares arguments: {"branch":"fix-login"}.
4. Server switches to revision 2, where region is required for new clients.
5. Server emits notifications/tools/list_changed.
6. Client sends the already prepared revision 1 call.
7. Client refreshes tools/list.
8. Client retries only if its repair policy permits it.
9. Client calls revision 2 with {"branch":"fix-login","region":"us-east-preview"}.
```

Der Test sollte mehr als Erfolg oder Misserfolg prüfen. Erfasse die tatsächlichen JSON-RPC-Nachrichten, die vom Server normalisierten Argumente, Aufrufe an externe Effekt-Stubs und das Ereignisprotokoll des Clients. Ein Server, der einen sauberen Fehler zurückgibt, aber bereits eine externe Bereitstellung gestartet hat, hat den Test nicht bestanden.

Verwende einen simulierten nachgelagerten Dienst mit einem nur erweiterbaren Anfrageprotokoll. Er sollte Methode, Pfad, für die Autorisierung relevante Header, Request-Body und eine Test-Korrelations-ID aufzeichnen. Prüfe anschließend, dass der veraltete Aufruf keinen nachgelagerten Request erzeugt hat, wenn er sicher abgewiesen werden sollte.

Eine kompakte Tabelle macht erwartetes Verhalten überprüfbar:

| Drift-Ereignis | Verhalten des alten Clients | Serververhalten | Auswirkung nachgelagert |
| --- | --- | --- | --- |
| Optionales `label` hinzufügen | Aufruf ohne `label` | Verwendet den bisherigen Standard | Eine erwartete Anfrage |
| Pflichtfeld `region` mit sicherem historischem Standard hinzufügen | Aufruf ohne `region` | Normalisiert auf den stabilen Standard | Eine erwartete Anfrage |
| Erforderlichen Freigabebereich hinzufügen | Aufruf ohne Bereich | Gibt einen reparierbaren Ausführungsfehler zurück | Keine Anfrage |
| Bedeutung von `project` umbenennen | Aufruf mit altem `project` | Lehnt als inkompatibel ab | Keine Anfrage |
| Ausgabefeld hinzufügen | Verarbeitet bisherige Felder | Gibt alte und neue Felder zurück | Keine zusätzliche Aktion |
| Ausgabe-ID entfernen | Versucht den nächsten abhängigen Aufruf | Client stoppt und meldet einen Vertragsfehler | Keine abhängige Anfrage |

Teste nicht nur den erfolgreichen Wiederholungsversuch. Agents sind gut darin, Aufrufe zu wiederholen, und genau deshalb können sie eine schlechte Migration verstärken. Teste wiederholte alte Aufrufe, eine Benachrichtigung, die erst eintrifft, nachdem ein Aufruf in die Warteschlange gelangt ist, eine fehlgeschlagene Aktualisierung und einen Client, der sich mitten im Übergang neu verbindet.

Der schwierige Fall ist ein laufender Tool-Aufruf. Ein Server sollte jeden Aufruf anhand einer zusammenhängenden Vertragsrevision ausführen. Beginne die Validierung nicht unter Revision 1, lade dann die Konfiguration neu und erstelle die nachgelagerte Anfrage unter Revision 2. Sichere die Handler-Konfiguration bei der Annahme des Aufrufs. Wenn der Vorgang so lange dauern kann, dass sich auch der Zielvertrag ändert, stelle einen dauerhaften Job bereit oder lehne den Aufruf vor der nicht rückgängig zu machenden Phase ab. Vermische nicht zwei Revisionen in einer Aktion.

## Die Reparaturlogik des Clients braucht eine Grenze für Wiederholungen

Wenn ein Server eine veraltete Anfrage ablehnt, hat der Client mehrere Möglichkeiten: aktualisieren, das Modell die Argumente reparieren lassen, die Anfrage mit einer Zuordnung wiederholen oder auf eine Nutzereingabe warten. Die richtige Wahl hängt davon ab, ob sich nur die Syntax oder auch die Berechtigung der Aktion ändert.

Aktualisiere automatisch und wiederhole nur, wenn alle folgenden Bedingungen erfüllt sind:

- Der Server benennt ausdrücklich eine veraltete Schemarevision oder ein fehlendes Feld.
- Die aktualisierte Tooldefinition liefert einen eindeutigen, nicht sensiblen Standardwert oder eine deterministische Zuordnung.
- Die ursprüngliche Aktion bleibt innerhalb derselben Ziel- und Berechtigungsgrenze.
- Der erste Versuch hat keine externe Nebenwirkung ausgelöst.

Alles andere erfordert eine neue Entscheidung. Fügt eine Revision `environment`, `account_id`, `repository`, `host`, `user` oder einen Bestätigungstext hinzu, kann eine automatische Wiederholung die Aktion ausweiten oder umleiten. Selbst wenn das Modell eine wahrscheinliche Antwort ableiten kann, sollte es neuen Kontext oder eine menschliche Genehmigung einholen.

Trenne die Identität einer Anfrage von der Identität einer Wiederholung. Wenn ein Tool-Aufruf ein externes System erreichen kann, bevor der Client seine Antwort erhält, darf der Client ihn nach einer Schemaaktualisierung nicht blind erneut senden. Verwende ein Idempotenz-Token, sofern die nachgelagerte API das unterstützt. Bei SSH gibt es keinen allgemeinen Idempotenzmechanismus. Entwirf Befehle deshalb so, dass eine Wiederholung entweder sicher oder erkennbar ist. Eine Schema-Migration ist der falsche Zeitpunkt, um festzustellen, dass ein Timeout doppelte Arbeit erzeugt.

Ein guter Client-Fehler gibt dem Modell Informationen, ohne ihm eine falsche Anweisung zu übergeben. Zum Beispiel:

```json
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "deploy_preview rejected this request because its input contract changed. Refresh tools before retrying. The current schema requires branch and region. No deployment was started."
    }
  ],
  "structuredContent": {
    "error_code": "STALE_TOOL_SCHEMA",
    "tool": "deploy_preview",
    "required_action": "refresh_tools",
    "side_effect_started": false,
    "current_revision": 2
  }
}
```

Das genaue Fehlerformat ist deine Designentscheidung, die Fakten sind es nicht. Nenne, ob eine Auswirkung begonnen hat. Nenne, ob eine Aktualisierung helfen kann. Nenne die aktuelle Revision, wenn dein Server Revisionen bereitstellt. Ein Modell kann diese Fakten nutzen. Ein allgemeiner Transportfehler kann es nicht.

Bezeichne ein Schema-Validierungsproblem nicht als Transportfehler. Die MCP-Spezifikation unterscheidet Fehler auf Protokollebene von Fehlern bei der Toolausführung. Aktuelle Hinweise bevorzugen bei ungültiger Tool-Eingabe einen Tool-Ausführungsfehler, damit das Modell sich selbst korrigieren kann. Nutze diese Unterscheidung. Eine unbekannte JSON-RPC-Methode ist nicht dasselbe Ereignis wie ein bekanntes Tool, das alte Argumente ablehnt.

## Die Sicherheitsprüfung muss Bedeutung abdecken, nicht nur Geheimnisse

Schema-Drift wird zum Sicherheitsproblem, wenn eine alte Beschreibung durch ihre implizite Bedeutung eine neuere Aktion autorisiert. Das geschieht durch Standardwerte, umbenannte Felder, zusätzliche Bereiche und übermäßig hilfreiche Serveradapter.

Betrachte ein Tool namens `run_report`, das ursprünglich `{\"team\":\"sales\"}` akzeptiert. Der Server ändert es so, dass `target` ein Team, einen gespeicherten Bericht oder eine direkte Abfrage benennen kann. Ein alter Agent sendet weiterhin `team`. Wenn der Adapter daraus `target: \"sales\"` macht, was wurde autorisiert? Ein Team? Ein Bericht namens sales? Ein Abfragealias? Der Server hat an einer Aktionsgrenze Mehrdeutigkeit geschaffen. Lehne das ab und veröffentliche ein eigenes Tool oder einen ausdrücklichen Migrationsweg.

Zugangsdaten erhöhen den Einsatz. Ein Agent, der API-Schlüssel direkt hält, kann Schema-Reparatur und Geheimnisverarbeitung im eigenen Prozess und in den eigenen Logs vermischen. Dadurch kann ein veralteter Plan mehr Schaden anrichten. Sallyport bewahrt HTTP- und SSH-Zugangsdaten in seinem verschlüsselten Tresor auf und führt die Aktion aus, statt die Zugangsdaten an den Agent zu übergeben. Diese Trennung macht einen geänderten Toolvertrag nicht automatisch sicher, erleichtert aber die Prüfung der tatsächlichen Anfrage, des Autorisierungspunkts und des Audit-Eintrags.

Eine Genehmigung muss an der konkreten Aktion hängen, die jetzt ausgeführt wird, und nicht an einer gemerkten Toolbeschreibung. Wenn sich ein Tool von einem festen Host zu einer flexiblen Hostauswahl ändert, reicht eine sitzungsweite Genehmigung anhand einer früheren Codeidentität für das neue Ziel nicht aus. Fordere für die sensible Aktion eine frische Genehmigung pro Aufruf oder verwende einen neuen Toolnamen, der die größere Berechtigung sichtbar macht.

Auch hier zeigen Audit-Einträge ihren Wert. Protokolliere Toolnamen, verfügbare Schemarevision, empfangene Rohargumente, verwendete normalisierte Argumente, Server-Build oder Handlerrevision, Genehmigungsentscheidung und nachgelagertes Ziel. Überschreibe die Rohargumente nicht mit den normalisierten. Während eines Vorfalls musst du sehen können, ob der Client eine alte Struktur gesendet hat, ob der Adapter sie verändert hat und ob die externe Anfrage dem Versprechen des Adapters entsprach.

Sallyports Aktivitäts- und Sitzungsjournal sind gute Beispiele dafür, einen Agent-Lauf von einzelnen externen Aufrufen zu trennen. Für Drift-Tests brauchst du beide Ansichten: einen Eintrag, dass der Lauf autorisiert war, und einen weiteren für jeden HTTP- oder SSH-Aufruf, der den Rechner verlassen hat oder nicht.

## Verwende Kompatibilitätsfenster und entferne sie bewusst

Rückwärtskompatibilität sollte ein Enddatum haben, selbst wenn dieses an einen Release-Zyklus oder die Lebensdauer einer Sitzung statt an einen Kalendertag gebunden ist. Sonst bleiben alle veralteten Eingabezuordnungen für immer bestehen, und der Server wird zu einem Museum von Annahmen, die niemand sicher ändern kann.

Beginne damit, die Änderung zu klassifizieren.

Eine additive Änderung hält den alten Aufruf gültig und bewahrt seine Bedeutung. Behalte denselben Toolnamen, kündige die Änderung der Liste an und akzeptiere beide Strukturen, solange aktive Sitzungen auslaufen.

Eine begrenzte Migration ändert die Syntax, erlaubt aber eine sichere deterministische Zuordnung. Behalte den Toolnamen nur, wenn du den Mapper vollständig testen und seine Verwendung protokollieren kannst. Informiere neue Clients über das aktuelle Schema und akzeptiere alte Eingaben nur während des kurzen Übergangsfensters.

Eine semantische Änderung oder eine Änderung der Berechtigungen braucht einen neuen Toolnamen. `deploy_preview` und `deploy_environment` können dieselbe Implementierung nutzen, sollten aber keinen gemeinsamen Vertrag haben, wenn das eine bekannte Vorschauumgebung auswählt und das andere die Produktion auswählen kann. Das mag in einer Tool-Liste ausführlich wirken. Es kostet trotzdem weniger, als wenn ein Agent glaubt, die engere Aktion aufgerufen zu haben.

Eine Entfernung sollte klar scheitern. Gib einen Ausführungsfehler zurück, der das Ersatz-Tool nennt oder sagt, dass die Funktion nicht mehr vorhanden ist. Behalte keinen Toolnamen, der nichts tut. Stiller Erfolg ist für automatisierte Arbeit Gift, weil der Agent den Abschluss vermerkt, obwohl die beabsichtigte Wirkung nie eingetreten ist.

Verwende Telemetrie, um zu entscheiden, wann ein Adapter entfernt werden kann, aber sammle nicht nur aggregierte Erfolgszahlen. Zähle Aufrufe, die aus jeder alten Revision normalisiert wurden, abgelehnte alte Aufrufe, automatische Reparaturen des Clients und Aufrufe, die menschliches Eingreifen erforderten. Wenige alte Aufrufe können trotzdem wichtig sein, wenn sie von den langlebigsten oder privilegiertesten Agent-Jobs stammen.

Führe vor der Entfernung den Drift-Test umgekehrt aus: Starte einen Client mit der alten Version, aktualisiere den Server über das Kompatibilitätsfenster hinaus und bestätige, dass der Fehler klar, ohne Nebenwirkung und durch eine erneute Verbindung oder Toolaktualisierung behebar ist. Ein sauberer Bruch ist besser als eine stille Neuinterpretation.

## Ein Release-Gate, das Drift erkennt, bevor Nutzer ihn bemerken

Nimm Schema-Drift für jeden aktionsfähigen MCP-Server in das Release-Gate auf. Am ersten Tag brauchst du keine riesige Matrix. Du brauchst eine disziplinierte Fixture für jede Klasse von Vertragsänderung.

Beantworte für jedes geänderte Tool im Pull Request oder Release-Review diese Fragen:

1. Kann eine bestehende Sitzung weiterhin die zuvor gültigen Argumente senden?
2. Wenn ja, haben diese Argumente exakt dieselbe Aktionsbedeutung wie zuvor?
3. Wenn nein, erfolgt die Ablehnung, bevor eine externe Wirkung eintritt?
4. Sendet der Server `notifications/tools/list_changed` erst, nachdem die Ersatzliste verfügbar ist?
5. Kann der Client den Reparaturweg erklären, ohne fehlende Berechtigungen zu erfinden?

Mach die Antworten anschließend ausführbar. Speichere alte und neue `tools/list`-Fixtures neben dem Test. Führe die Sequenz mit einem alten Client-Snapshot aus. Prüfe nachgelagerte Anfragen und nicht nur MCP-Antworten. Behalte nach der Migration eine Regression-Fixture, weil der nächste Refactor einen Kompatibilitätszweig entfernen könnte, ohne dass sich jemand an seinen Grund erinnert.

Der erste Test, den du hinzufügen solltest, ist brutal klein: Liste ein Tool auf, ändere einen Pflichtparameter, führe den alten Aufruf aus und beweise, dass der Server entweder das alte sichere Verhalten beibehält oder gar nichts tut. Dieser Test erzwingt die Frage, der viele Deployments ausweichen: Was genau darf ein bereits laufender Agent bedeuten, nachdem du das Tool unter ihm verändert hast?
