Doppelte MCP-Tool-Aufrufe brauchen eine Ausführungsidentität
Doppelte MCP-Tool-Aufrufe brauchen mehr als Wiederholungen. Verwende Anfrage-Fingerprints und Aktivitätsdatensätze, um Neuverbindungen von echten zweiten Aktionen zu unterscheiden.

Eine Neuverbindung ist ein Transportproblem. Eine zweite Aktion ist ein Ausführungsproblem. Teams geraten in Schwierigkeiten, wenn sie beides gleich behandeln und ein Client jede Anfrage wiederholt, für die keine sichtbare Antwort vorliegt.
Bei MCP wird das leicht übersehen, weil ein Tool-Aufruf mehrere Grenzen überschreiten kann: einen Agent-Prozess, einen MCP-Transport, ein Aktions-Gateway und eine HTTP-API oder ein SSH-Ziel. Eine Verbindung kann abbrechen, nachdem das Ziel die Arbeit angenommen hat, aber bevor der Agent das Ergebnis erhält. Sendet dein System den Aufruf danach erneut, sieht das Ziel zwei gültige Anfragen. Es hat keinen Grund anzunehmen, dass die zweite ein Versehen war.
Die Lösung ist kein größeres Wiederholungslimit. Gib jeder angeforderten Aktion eine Ausführungsidentität, zeichne ihren Lebenszyklus auf und triff Entscheidungen über Wiederholungen anhand dieses Datensatzes. Ein Anfrage-Fingerprint zeigt, was der Aufrufer tun wollte. Ein Aktivitätsdatensatz zeigt, ob das System bereits damit begonnen oder die Aktion abgeschlossen hat. Du brauchst beides.
Eine Neuverbindung autorisiert keine weitere Ausführung
Ein Client, der sich nach einem unterbrochenen Stream neu verbindet, hat Hinweise darauf, dass die Kommunikation fehlgeschlagen ist. Er weiß dadurch nicht, dass der ursprüngliche Tool-Aufruf fehlgeschlagen ist.
Das klingt offensichtlich, bis jemand generische Wiederholungs-Middleware unterhalb eines MCP-Clients einfügt. Die Middleware sieht einen Timeout, einen Verbindungsabbruch oder eine fehlende Antwort. Sie weiß nicht, ob der POST eine Lese- oder Schreiboperation, einen Remote-Befehl oder eine irreversible Aktion enthielt. Sie sendet die Bytes erneut, weil HTTP-Wiederholungscode das oft so vorsieht.
Bei einem Lesezugriff wie GET /repos/acme/api/branches kann das vertretbar sein. Bei POST /payments, DELETE /projects/atlas oder einem SSH-Befehl, der einen Produktionshost verändert, kann dadurch ein zweiter Seiteneffekt entstehen. Eine Tool-Schicht kann das nachträglich nicht reparieren, indem sie dem Modell nur ein einziges Ergebnis zurückgibt.
Die Spezifikation für MCP Streamable HTTP erlaubt Clients ausdrücklich, die Zustellung von Server-zu-Client-Ereignissen mit Last-Event-ID fortzusetzen, wenn ein Stream abbricht. Das ist ein Wiederherstellungsmechanismus für Nachrichten in einem Stream. Er macht aus einer zweiten JSON-RPC-Anfrage tools/call nicht dieselbe Ausführung. Auch die Dokumentation des TypeScript SDK trennt Fortsetzungstoken vom Anfragepfad und erlaubt Client-Middleware um fetch. Genau deshalb sollten Teams ihre Wiederholungsregel im Code festlegen, statt anzunehmen, dass der Transport sie schützt.
Verwende diese Regel:
Setze einen Antwort-Stream fort, wenn das Protokoll dies unterstützt. Sende eine Aktion mit Seiteneffekt nur dann erneut, wenn die Aktionsschicht sie als dieselbe Ausführung identifizieren kann.
Die schwierigen Fälle sind keine eindeutigen Fehler. Schwierig wird es, wenn der Server die Arbeit startet, die Antwort verschwindet und der Client nicht weiß, ob er warten, fortsetzen, den Status abfragen oder wiederholen soll. Dein Design muss diese Unsicherheit sichtbar machen.
JSON-RPC-IDs identifizieren Nachrichten, keine dauerhaften Aktionen
Eine JSON-RPC-Anfrage-ID hilft dabei, eine Anfrage ihrer Antwort zuzuordnen. Sie reicht aber nicht aus, um eine Aktion über Neuverbindungen, Prozessneustarts oder getrennte Agent-Läufe hinweg zu deduplizieren.
Betrachte dieses Paar von Aufrufen:
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
Das kann dieselbe Anfrage sein, die nach einer unterbrochenen Verbindung zweimal gesendet wurde. Es kann sich aber auch um zwei getrennte Client-Prozesse handeln, die beide bei 1 oder 41 mit der Nummerierung beginnen. Selbst innerhalb eines einzelnen Prozesses kann ein Implementierungsfehler IDs wiederverwenden. Der Wert sagt fast nichts aus, sofern du ihn nicht an einen authentifizierten Aufrufer und eine bestimmte Protokollsitzung bindest.
Betrachte nun zwei Aufrufe mit unterschiedlichen IDs:
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
Das könnte eine Transportwiederholung sein, bei der der Client eine neue ID vergeben hat. Der Agent könnte aber auch bewusst nach einem unsicheren Ergebnis eine zweite Bereitstellung angefordert haben. Nachrichten-IDs sind Hinweise, nicht die Entscheidungsgrundlage.
Mach nicht den entgegengesetzten Fehler und dedupliziere jeden übereinstimmenden Tool-Aufruf für immer. Dieselbe Version zweimal bereitzustellen kann harmlos oder sogar beabsichtigt sein. Dasselbe externe Ticket zweimal anzulegen, kann falsch sein. Ein Zugangsdaten-Set zweimal zu rotieren, kann einen Dienst aussperren. Die Aktionsklasse bestimmt, wie lange eine Ausführungsidentität relevant bleibt.
Ein praktisches Modell hält drei Kennungen getrennt:
- Protokoll-Korrelations-ID: die JSON-RPC-ID und, falls zutreffend, der MCP-Sitzungs- oder Stream-Kontext.
- Ausführungs-ID: eine vom Server erzeugte Kennung für einen akzeptierten Versuch, eine Tool-Aktion auszuführen.
- Intent-Fingerprint: ein stabiler Digest des gewünschten Effekts, mit dem du eine frühere Ausführung findest, wenn sich die Protokollkorrelation geändert hat.
Sobald du diese Kennungen trennst, tun die Logs nicht länger so, als würden sie eine Frage beantworten, die sie gar nicht beantworten können.
Ein nützlicher Fingerprint beschreibt den Effekt
Ein Anfrage-Fingerprint sollte gleich bleiben, wenn sich die Zustellung ändert, und sich ändern, wenn sich der gewünschte Effekt ändert. Hash nicht einfach die rohen JSON-Bytes und nenne das Ergebnis einen Fingerprint. Rohes JSON variiert durch die Reihenfolge von Eigenschaften, Leerzeichen, optionale Standardwerte, Anfrage-IDs und harmlose Formatierungsänderungen.
Erstelle zuerst einen kanonischen Aktionsdatensatz. Für eine HTTP-Aktion könnte er so aussehen:
{
"actor": "signed-process:com.example.agent",
"tool": "deploy_release",
"channel": "http",
"target": "deploy-api.internal.example/releases",
"credential_ref": "deploy-service",
"method": "POST",
"arguments": {
"service": "catalog",
"version": "2026.07.22",
"region": "us-east-1"
},
"intent_scope": "run:5f8097"
}
Kanonisiere die Feldreihenfolge, lasse Felder ohne semantische Bedeutung weg und normalisiere bekannte Entsprechungen, bevor du einen Digest berechnest. Wenn region standardmäßig us-east-1 ist, füge das Feld entweder immer ein oder lasse es immer weg, wenn das Ziel diesen Standardwert selbst setzt. Eine Mischung erzeugt falsche Nichtübereinstimmungen.
Das Feld actor ist wichtig. Zwei verschiedene autorisierte Agent-Prozesse, die identische Argumente senden, können zwei getrennte beabsichtigte Aktionen darstellen. Auch credential_ref ist wichtig. Eine Anfrage über eine Dienstidentität ist nicht zwangsläufig gleichbedeutend mit derselben Route und demselben Body über eine andere Identität. Bei SSH gehören die Hostidentität, das Konto, der Befehl, das Arbeitsverzeichnis, sofern es das Verhalten beeinflusst, und, soweit sicher möglich, eine normalisierte Darstellung des Befehls hinein.
Halte Geheimnisse aus dem kanonischen Datensatz heraus. Speichere niemals Bearer-Tokens, private Schlüssel oder rohe Autorisierungsheader in den Eingabedaten eines Fingerprints. Wenn ein Argument selbst ein Geheimnis enthält, ersetze es durch eine geschützte interne Referenz oder berechne den Fingerprint mit einer schlüsselbasierten Konstruktion wie HMAC. Ein einfacher Hash ohne Salt eines Geheimnisses mit geringer Entropie macht deinen Audit-Speicher zu einer Orakelquelle für Angriffsversuche.
Die bekannte Empfehlung, man solle «einfach die Anfrage hashen», ist beliebt, weil sie kurz ist. Für die Aktionskontrolle ist sie falsch. Ein Hash beweist nur, dass bestimmte Bytes einer Funktion übergeben wurden. Er sagt nicht, ob diese Bytes denselben Akteur, denselben Zieleffekt oder dasselbe Wiederholungsfenster darstellen.
Der Aktivitätsdatensatz braucht Zustände, keine einzelne Logzeile
Ein nützlicher Aktivitätsdatensatz zeigt, an welcher Stelle die Aktion angehalten hat. Zeichnet er nur Erfolg und Misserfolg auf, lässt dich eine Neuverbindung genau dann raten, wenn du eine klare Antwort brauchst.
Zeichne für jede Ausführungs-ID mindestens diese Übergänge auf:
- Akzeptiert: Das Gateway hat die Anfrage validiert und eine Ausführungs-ID vergeben.
- Autorisiert: Die erforderliche Freigabe oder Sitzungsautorisierung hat die Aktion erlaubt.
- Versendet: Das Gateway hat die Aktion an den HTTP-Client oder SSH-Helfer übergeben.
- Ergebnis beobachtet: Die Antwort des Ziels, der Exit-Status oder ein expliziter Zustellungsfehler ist eingegangen.
- Ergebnis zugestellt: Der Agent hat das Tool-Ergebnis erhalten, sofern der Transport dies sicher feststellen kann.
Der vierte und fünfte Zustand müssen getrennt bleiben. Ein Ziel kann HTTP 201 zurückgeben, während die Verbindung zum MCP-Client abbricht, bevor dieser die Antwort sieht. Diese Ausführung als fehlgeschlagen zu markieren, weil die Zustellung des Ergebnisses fehlgeschlagen ist, wäre eine Falschaussage. Sie als abgeschlossen zu markieren, gibt dem Wiederherstellungscode etwas Brauchbares: Er kann das bekannte Ergebnis zurückgeben oder rekonstruieren, ohne eine weitere Anfrage zu senden.
Das ist die Form des Datensatzes, die ich während eines Vorfalls sehen möchte:
{
"execution_id": "act_01J4K8J7DX7V",
"fingerprint": "hmac-sha256:4a1e...d90c",
"tool": "deploy_release",
"actor": "signed-process:com.example.agent",
"target": "deploy-api.internal.example/releases",
"state": "completed_result_not_delivered",
"accepted_at": "2026-07-22T14:03:18Z",
"dispatched_at": "2026-07-22T14:03:19Z",
"completed_at": "2026-07-22T14:03:25Z",
"target_status": 201,
"result_reference": "result_01J4K8JFM2"
}
Der Datensatz muss nicht jedem Operator die vollständige Antwort des Ziels offenlegen. Er braucht genügend geschützte Details, damit das Gateway eine Wiederherstellungsentscheidung treffen kann, und genügend verständliche Informationen, damit ein Mensch nachvollziehen kann, was passiert ist.
Sallyports Activity-Journal zeichnet einzelne Aufrufe auf, während das Sessions-Journal Agent-Läufe erfasst. Diese Trennung ist für die Untersuchung hilfreich: Der Lauf zeigt, welcher Agent-Prozess existierte, und der Aufrufdatensatz zeigt, ob eine bestimmte Aktion in der Außenwelt die Versandgrenze überschritten hat. Die Audit-Kette kann außerdem offline mit sp audit verify überprüft werden. Das hilft, wenn du nach einem Vorfall nachweisen musst, dass der Datensatz nicht nachträglich unbemerkt verändert wurde.
Behandle unbekannte Ergebnisse als eigenen Zustand
Die meisten doppelten Aktionen beginnen mit einem System, das nur zwei Ergebnisse kennt: Erfolg und Misserfolg. Vernetzte Aktionen brauchen ein drittes: unbekannt.
Unbekannt bedeutet nicht, dass das System nichts getan hat. Es bedeutet, dass das System nicht beweisen kann, ob das Ziel die Aktion angenommen hat. Ein Timeout, bevor irgendwelche Bytes deinen Prozess verlassen, kann oft sicher wiederholt werden. Ein Timeout, nachdem der HTTP-Anfrage-Body an das Betriebssystem übergeben wurde, ist nicht dasselbe Ereignis. Eine unterbrochene SSH-Verbindung, nachdem die entfernte Shell einen Befehl gestartet hat, ist noch problematischer, weil der Remote-Befehl nach dem Ende deines lokalen Prozesses weiterlaufen kann.
Ordne jede Tool-Aktion ein, bevor du die Wiederherstellung festlegst:
| Aktionstyp | Beispiel | Standard nach unbekanntem Ergebnis |
|---|---|---|
| Nur lesen | Build-Status abrufen | Mit normalen Limits wiederholen |
| Idempotentes Schreiben | Eine benannte Ressource in einen festgelegten Zustand versetzen | Mit derselben Idempotenzidentität wiederholen |
| Bedingtes Schreiben | Nur aktualisieren, wenn die Version übereinstimmt | Status abfragen und nur wiederholen, wenn die Bedingung weiterhin gilt |
| Irreversible Aktion | Zahlung senden, Zugriff entziehen, Zugangsdaten rotieren | Stoppen und eine ausdrückliche Prüfung anfordern |
| Remote-Befehl | Eine Migration über SSH ausführen | Dauerhafte Markierung abfragen oder Prüfung anfordern |
Das HTTP-Verb einer API entscheidet diese Tabelle nicht allein. PUT gilt oft als idempotent, aber ein schlecht entworfenes Endpunkt kann bei jeder Anfrage eine Benachrichtigung senden, einen Build auslösen oder einen Audit-Eintrag anhängen. POST kann sicher wiederholbar sein, wenn die API einen Idempotenzschlüssel unterstützt. Prüfe den tatsächlichen Vertrag des Ziels.
Bei lang laufenden Remote-Befehlen solltest du vor dem Start der Arbeit eine dauerhafte Markierung anlegen. Ein Migrationsbefehl kann einen Datensatz mit einer Ausführungs-ID erstellen, ihn beim Arbeitsbeginn aktualisieren und erst nach der Validierung als abgeschlossen markieren. Frage diese Markierung nach einer Neuverbindung ab, bevor du den Befehl erneut sendest. Ohne eine solche Markierung ist «wahrscheinlich nicht ausgeführt» keine Wiederherstellungsstrategie.
Ordne Wiederholungen einem begrenzten Intent-Bereich zu
Ein Fingerprint allein würde legitime Arbeit zu häufig zusammenfassen. Begrenze ihn auf den Zeitraum und Kontext, in dem eine Wiederholung plausibel ist.
Der einfachste Bereich ist ein einzelner Agent-Lauf. Sendet derselbe signierte Prozess dieselbe Aktion, während das Ergebnis des ersten Versuchs noch offen ist, behandle die zweite Anfrage als mögliche Wiederholung. Sendet ein anderer Prozess sie Stunden später, behandle sie als neuen Intent, sofern die Aktion selbst keinen dauerhaften Idempotenzschlüssel bereitstellt.
Eine gute Zuordnungsregel sieht so aus:
if prior.fingerprint == incoming.fingerprint
and prior.actor == incoming.actor
and prior.intent_scope == incoming.intent_scope
and prior.state in {accepted, authorized, dispatched, completed_result_not_delivered}:
recover_or_attach_to(prior.execution_id)
else:
create_new_execution()
recover_or_attach_to darf nicht blind Erfolg zurückgeben. Das Verhalten hängt vom vorherigen Zustand ab.
Ist die vorherige Ausführung akzeptiert, aber noch nicht versendet, kann das Gateway diese Ausführung fortsetzen. Wurde sie versendet und ist das Ergebnis unbekannt, sollte das Gateway den Status-Endpunkt, die Idempotenzfunktion des Ziels oder eine dauerhafte Markierung prüfen. Ist sie abgeschlossen, aber das Ergebnis nicht zugestellt worden, sollte es die gespeicherte Ergebnisreferenz zurückgeben. Wurde sie wegen fehlender Autorisierung abgelehnt, sollte es diese Ablehnung zurückgeben, statt aus derselben mehrdeutigen Wiederholung einen neuen Freigabeprozess zu erzeugen.
Der Bereich muss zur Aktion passen. Ein fünfminütiges Fenster kann für eine API-Anfrage mit Timeout angemessen sein. Für eine Softwarebereitstellung, die eine Stunde dauert, reicht es nicht. Bei einer Rotation von Zugangsdaten kann ein dauerhafter Fingerprint erforderlich sein, bis du prüfen kannst, welche Zugangsdaten aktiv sind. Verwende keine globale TTL, nur weil sie einfach zu konfigurieren ist. Lege auf die Aktion zugeschnittene Aufbewahrungs- und Wiederherstellungsregeln fest.
Eine Freigabe ist ein Hinweis, kein Idempotenzmechanismus
Eine menschliche Freigabe kann belegen, dass ein Prozess eine Aktion ausführen durfte. Sie kann nicht beweisen, ob ein früherer Versuch bereits stattgefunden hat.
Das ist für Systeme wichtig, die bei jedem sensiblen Aufruf nachfragen. Angenommen, ein Agent möchte Produktionszugangsdaten rotieren. Eine Person stimmt zu. Das Gateway versendet die Anfrage, danach trennt sich der Client. Der Agent verbindet sich neu und erzeugt denselben Tool-Aufruf. Die Person erneut zu fragen, erzeugt eine irreführende Entscheidung. Der Operator sieht eine vertraute Anfrage und kann sie freigeben. Die eigentlich nötige Antwort ist aber, ob die erste Rotation abgeschlossen wurde.
Eine Freigabe pro Aufruf hat weiterhin ihren Platz. Sie steuert die Autorisierung zum Zeitpunkt der Nutzung. Halte sie von der Duplikatbehandlung getrennt:
- Die Autorisierung entscheidet, ob der aktuelle Aufrufer eine Ausführung starten darf.
- Der Fingerprint entscheidet, ob eine eingehende Anfrage einer bestehenden Ausführung zugeordnet wird.
- Aktivitätsdatensätze entscheiden, ob diese bestehende Ausführung fortgesetzt oder wiederhergestellt werden kann oder geprüft werden muss.
Wenn eine Wiederholung einer ausstehenden Ausführung zugeordnet wird, zeige den ursprünglichen Aktivitätsdatensatz, statt eine neue Freigabe anzuzeigen, als wäre nichts geschehen. Ein Prüfer sollte das Ziel, den Zeitpunkt des ersten Versands, das bekannte Ergebnis und den Grund sehen, warum das Gateway die Aktion nicht erneut gesendet hat.
Sallyport verwendet eine feste Entscheidungskette: Ein gesperrter Tresor verweigert Aktionen, ein neuer Agent-Prozess erhält standardmäßig eine sitzungsbezogene Autorisierung und ausgewählte Zugangsdaten können bei jeder Verwendung eine Freigabe erfordern. Diese Kontrollen beantworten die Frage, wer handeln darf. Der Ausführungsdatensatz muss zusätzlich beantworten, ob die Aktion die Grenze bereits überschritten hat.
HTTP-Idempotenzschlüssel lösen nur einen Teil des Problems
Wenn eine übergeordnete API Idempotenzschlüssel akzeptiert, verwende sie. Sende für die Lebensdauer einer Ausführung einen stabilen Wert, speichere die Antwort des Ziels und verwende diesen Wert nur bei der Wiederherstellung derselben Ausführung erneut.
Beispielsweise kann das Gateway vor dem Versand eine Ausführungs-ID erzeugen und sie dem von der API erwarteten Header zuordnen:
POST /v1/releases HTTP/1.1
Host: deploy-api.internal.example
Idempotency-Key: act_01J4K8J7DX7V
Content-Type: application/json
{"service":"catalog","version":"2026.07.22","region":"us-east-1"}
Die API muss festlegen, was passiert, wenn dieser Header erneut vorkommt. Optimalerweise gibt sie für dieselbe semantische Anfrage das ursprüngliche Ergebnis zurück und lehnt eine andere Anfrage ab, die denselben Wert wiederverwenden will. Akzeptiert sie unter demselben Schlüssel stillschweigend einen geänderten Body, kann dein Gateway aus einer Wiederholung keine sichere Schlussfolgerung ziehen.
Verwende den Fingerprint selbst nicht als externen Idempotenzschlüssel, wenn er über absichtliche Aktionen hinweg bestehen bleiben kann. Eine Ausführungs-ID ist für einen akzeptierten Versuch eindeutig. Der Fingerprint findet einen möglicherweise zugehörigen Versuch. Beide haben unterschiedliche Aufgaben.
HTTP-Idempotenz hilft bei SSH allein nicht. Dafür brauchst du ein Protokoll auf der entfernten Seite. Ein sicherer Ansatz besteht darin, eine erzeugte Ausführungs-ID an ein Skript zu übergeben, das einen dauerhaften Statusdatensatz auf dem Host oder in einem gemeinsamen Speicher anlegt und sich weigert, dieselbe Operation zweimal zu starten. Wenn du den Befehl nicht ändern und keine externe Markierung prüfen kannst, ordne ihn der irreversiblen Kategorie zu und verlange nach einer unsicheren Trennung eine Prüfung.
Untersuche die Abfolge, nicht die Endanzahl
Zwei Aktivitätszeilen mit übereinstimmenden Argumenten beweisen noch kein Duplikat. Beginne mit der Abfolge der Ereignisse und verfolge den ersten Aufruf bis zu seiner Versandgrenze.
Eine echte Untersuchung sollte diese Fragen in der folgenden Reihenfolge beantworten:
- Hat ein Agent-Prozess oder haben zwei verschiedene Prozesse die Aufrufe gesendet?
- Hat der erste Aufruf eine Autorisierung erhalten und die Versandphase erreicht?
- Hat das Gateway eine Antwort oder einen Exit-Status vom Ziel erhalten?
- Ist die Zustellung des Ergebnisses nach dem Abschluss durch das Ziel fehlgeschlagen?
- Hat der zweite Aufruf die ursprüngliche Ausführungs-ID oder einen Idempotenzschlüssel wiederverwendet oder einen neuen Versuch erzeugt?
Diese Reihenfolge verhindert eine häufige falsche Schlussfolgerung: «Die Logs zeigen zwei Aufrufe, also hat der Agent zweimal gehandelt.» Vielleicht stellst du fest, dass das Gateway eine abgeschlossene Ausführung und einen Client-Wiederholungsversuch aufgezeichnet hat, der sich dieser Ausführung angeschlossen hat. Oder du findest zwei getrennte autorisierte Prozesse, die jeweils aus einem anderen Planungskontext heraus dieselbe Aktion ausgeführt haben. Dafür sind unterschiedliche Korrekturen nötig.
Halte den Aktivitätsspeicher nur anhängend oder anderweitig manipulationssicher. Untersuchungen zu doppelten Aktionen finden oft nach einem kostspieligen Ereignis statt, wenn jemand eine sauberere Geschichte erzählen möchte, als das System tatsächlich belegen kann. Eine hashverkettete Aufzeichnung macht die ursprüngliche Entscheidung nicht richtig, erschwert aber die nachträgliche Manipulation der Rekonstruktion.
Verbirg die Mehrdeutigkeit auch nicht vor dem Agenten. Gib ein Ergebnis zurück, das besagt, dass die vorherige Ausführung noch überprüft werden muss oder abgeschlossen ist, die Zustellung des Ergebnisses aber unterbrochen wurde. Ein Modell, das einen künstlich erzeugten Fehler sieht, wird es erneut versuchen. Ein Modell, das einen klaren unsicheren Zustand sieht, kann den Status abfragen, eine Prüfung anfordern oder einen sichereren Weg wählen.
Mache das Wiederholungsverhalten zu einem Teil jedes Tool-Vertrags
Jedes Tool mit Seiteneffekt braucht eine ausdrückliche Antwort auf eine Frage: Was passiert, wenn der Aufrufer die Antwort nach dem Versand verliert?
Schreibe die Antwort neben die Tool-Definition. Gib an, ob die Aktion schreibgeschützt, mit einer Idempotenzidentität wiederholbar, über eine Statusabfrage wiederherstellbar oder nach einem unbekannten Ergebnis blockiert ist. Lege fest, was in den Fingerprint gehört und wie lange eine unvollständige Ausführung zugeordnet werden kann. Wenn sich das niemand aufschreiben lässt, ist das Tool noch nicht für autonome Nutzung bereit.
Der Entwicklungsaufwand ist normalerweise gering verglichen mit den Aufräumarbeiten nach einer doppelten Bereitstellung, einem doppelten Konto, einer doppelten Zahlung oder einer zweiten Rotation von Zugangsdaten. Füge die Ausführungs-ID vor dem Aufruf des Ziels hinzu. Speichere Zustandsübergänge vor und nach dem Versand. Bewahre eine Ergebnisreferenz auf. Dann sorge dafür, dass der Code für Neuverbindungen diesen Datensatz prüft, bevor er die Außenwelt erneut berührt.
Das ist der Maßstab: Ein unterbrochener Transport darf eine Unterhaltung unterbrechen, aber er darf Unsicherheit nicht unbemerkt in eine zweite Aktion verwandeln.
FAQ
Was gilt als doppelter MCP-Tool-Aufruf?
Ein doppelter MCP-Tool-Aufruf ist ein wiederholter Seiteneffekt, der entsteht, wenn zwei Ausführungen das Ziel erreichen, nicht bloß zwei Nachrichten in einem Log. Eine wiederholte HTTP-Anfrage, die nur Daten liest, kann ärgerlich sein. Eine wiederholte Anfrage, die Geld sendet, einen Branch löscht, Zugangsdaten ändert oder ein Konto anlegt, erfordert eine andere Reaktion. Bestimme zuerst den Effekt auf das nachgelagerte System, bevor du darüber entscheidest, ob der Client den Aufruf «wirklich» wiederholen wollte.
Kann eine MCP-Neuverbindung dazu führen, dass derselbe Tool-Aufruf zweimal ausgeführt wird?
Manchmal. Ein Client kann die Antwort verlieren, nachdem der Server den Aufruf bereits abgeschlossen hat, und sich anschließend neu verbinden und dieselbe Anfrage erneut senden. Gleich aussehende Aufrufe können aber auch von einem Agenten stammen, der seinen Plan neu bewertet hat, von einem Supervisor, der einen Worker neu gestartet hat, oder von einem Menschen, der die Anweisung zweimal gegeben hat.
Reicht die JSON-RPC-Anfrage-ID zur Erkennung von Duplikaten aus?
Nein. Eine JSON-RPC-Anfrage-ID identifiziert eine Nachricht innerhalb einer Protokollunterhaltung. Sie ist aber keine dauerhafte Ausführungsidentität über einen Client-Neustart oder eine neue Verbindung hinweg. Betrachte sie als nützlichen Hinweis und kombiniere sie mit einem Anfrage-Fingerprint und einem Aktionsdatensatz.
Was sollte ein MCP-Anfrage-Fingerprint enthalten?
Der Fingerprint sollte den autorisierten Prozess oder Principal, den Tool-Namen, normalisierte Argumente, die Identität des Ziels, die Identität der Zugangsdaten sowie einen begrenzten Zeit- oder Intent-Bereich enthalten. Transportabhängige Daten wie eine Socket-Nummer, eine vorübergehende SSE-Ereignis-ID oder eine zufällig erzeugte Anfrage-ID gehören nicht hinein. Der Fingerprint sollte den gewünschten Effekt beschreiben, nicht den Übertragungsweg.
Sollte ich einen fehlgeschlagenen Tool-Aufruf automatisch wiederholen?
Vermeide die automatische erneute Ausführung jeder Aktion mit externem Seiteneffekt, außer das Ziel unterstützt einen Idempotenzschlüssel oder du kannst beweisen, dass der erste Versuch nicht begonnen hat. Das Wiederverbinden eines Antwort-Streams ist etwas anderes als das erneute Senden eines Tool-Aufrufs. Der sichere Wiederherstellungsweg besteht meist darin, zuerst das Ergebnis wiederherzustellen oder den Aktivitätsdatensatz zu prüfen.
Was sollte ein Aktivitätsdatensatz bei der Untersuchung einer Wiederholung zeigen?
Ein Aktivitätsdatensatz sollte den Anfrage-Fingerprint, den Ausführungsversuch, den Autorisierungskontext, Zeitstempel, das Ziel und einen Ergebnisstatus enthalten. Er muss zwischen «empfangen», «gestartet», «abgeschlossen» und «Zustellung des Ergebnisses fehlgeschlagen» unterscheiden. Wenn diese Zustände in einer einzigen Logzeile zusammenfallen, kann ein Operator nicht erkennen, ob eine zweite Aktion stattgefunden hat.
Wann sollte ein Agent einen Idempotenzschlüssel senden?
Verwende einen Idempotenzschlüssel, wenn die empfangende API einen solchen unterstützt, und behalte ihn bei einer Transportwiederholung derselben beabsichtigten Aktion bei. Verwende ihn nicht für eine spätere, absichtliche Aktion erneut, selbst wenn die Argumente identisch sind. Wenn die API keine Idempotenz unterstützt, braucht dein Gateway eigene Datensätze für ausstehende und abgeschlossene Ausführungen.
Wie unterscheide ich eine Wiederholung von einer absichtlichen zweiten Aktion?
Eine Neuverbindung verändert meist Verbindungsmerkmale wie Prozessstartzeit, Transportsitzung, Anfrage-ID oder Stream-Zustand. Eine absichtliche Wiederholung hat häufig einen neuen Planungskontext, eine neue Autorisierungsentscheidung, veränderte Argumente oder einen bedeutenden zeitlichen Abstand zum vorherigen Ergebnis. Keines dieser Signale entscheidet den Fall allein. Deshalb braucht das System eine aufgezeichnete Entscheidungsregel.
Kann ich nur einen Hash jeder Tool-Anfrage speichern?
Nein. Ein Digest ist nur ein Index. Bewahre eine geschützte kanonische Darstellung oder genügend strukturierte Felder auf, um zu erklären, warum zwei Aufrufe übereinstimmen, und beschränke gleichzeitig sensible Werte in den Ansichten für Operatoren. Wenn Fingerprints Geheimnisse abdecken, leite sie mit einem geheimen Schlüssel ab, damit ein Leser des Logs keine Vermutungen gegen den Digest testen kann.
Was soll passieren, wenn das Ergebnis des ersten Aufrufs unbekannt ist?
Behandle sie als mehrdeutige Ausführung und stoppe die automatische Wiederholung für diese Aktionsklasse. Zeige der Person, die die Aktion freigeben soll, den bisherigen Aktivitätsdatensatz, das bekannte Ergebnis und den genauen Effekt auf das Ziel. Eine schnelle doppelte Aktion ist zwar günstiger als eine irreversible doppelte Aktion, bleibt aber ein Designfehler, den du beheben solltest.