Anleitung benötigt: Idempotenz für MCP Server Trigger beim Aufrufen unsicherer HTTP-Tools im Queue-Modus

Umgebung

  • Selbst gehostetes n8n 2.38.7
  • Docker mit Redis-Warteschlangenmodus, ein Hauptprozess, ein Worker und externe Task-Runner
  • MCP Server Trigger mit einem HTTP Request Tool-Member
  • Nachgelagerte nicht-idempotente HTTP POST-Operationen

Kontext

Wir beobachteten zuvor doppelte nachgelagerte CRM-Schreibvorgänge von einer einzelnen logischen Formularübermittlung, daher fügten wir einen eng gefassten, vorübergehenden Idempotenzschutz hinzu. Dieser Schutz wurde nun gegen n8n 2.38.7 validiert. Wir wollen bewusst kein breites Response-Caching, da es einen legitimen zweiten Aufruf mit denselben Argumenten verbergen könnte.

Der zugehörige GitHub-Bericht wurde als Supportfrage geschlossen: Question: MCP queue-mode tool calls and duplicate unsafe HTTP writes · Issue #38545 · n8n-io/n8n · GitHub

Reproduktion / Verifizierung

Unser isolierter Test verwendet einen lokalen Counter-Endpoint anstelle eines CRM:

  1. Er stellt einen MCP Server Trigger mit einem HTTP Request Tool zur Verfügung, das einen lokalen POST für eine bereitgestellte synthetische Vorgangs-ID ausführt.
  2. Es führt vier Aufrufe durch: Streamable HTTP, SSE und einen gleichzeitigen Aufruf über jeden Transport.
  3. Jede Operation muss genau einen HTTP POST und eine erfolgreiche n8n-Ausführung zur Folge haben.
  4. Es wiederholt die gleiche Vorgangs-ID separat zweimal und erfordert zwei Schreibvorgänge, um zu beweisen, dass der Schutz legitime Anfragen nicht zwischenspeichert oder zusammenfasst.

Bei 2.38.7 erhalten wir vier erfolgreiche Ausführungen und genau einen Schreibvorgang für jeden der vier verschiedenen Aufrufe. Der absichtliche wiederholte Aufruf erzeugt zwei Schreibvorgänge.

Fragen

  1. Garantiert die 2.38.7 Worker-seitige MCP-Toolkit-Korrektur, dass ein einzelner MCP-Tool-Aufruf nur einmal über die Grenze zwischen Hauptprozess und Worker läuft, sich wieder verbindet und Wiederholungen durchführt? Oder behebt sie nur Fehler beim Tool-Routing/bei der Tool-Auswahl?
  2. Gibt es eine kanonische MCP-Request-ID oder n8n-Ausführungs-ID, die als Idempotenzschlüssel an ein HTTP-Tool weitergeleitet werden sollte?
  3. Gibt es einen empfohlenen Queue-Mode-Regressionstest für genau-einmalige Ausführung eines MCP-Tool-Aufrufs, der dennoch zwei absichtliche Aufrufe mit identischen Eingaben ermöglicht?

Es sind keine Kundendaten, Anmeldedaten, Domänen oder Workflow-Exporte enthalten.

Hey @luislm21, während du auf eine Antwort wartest, könnten dir folgende Ressourcen helfen:

Empfohlene Ressourcen

Automatisch zu deiner Frage zugeordnet.

Dokumentation:

Forum:

@Niffzy – du hast bei ähnlichen Problemen schon geholfen, kannst du da mal einen Blick drauf werfen?

Automatisch vorgeschlagen durch n8ns Community-Bot. Das ist ein Pilotprojekt – gib hier bitte Feedback.

hi @luislm21

Zu Q1/Q2: Ich nehme an, mindestens einmal ist die einzige Garantie über jede Haupt-/Worker±Wiederholungsgrenze hinweg, was für jedes Queue-System gilt. Ich würde den Fix in 2.38.7 als Routing-/Tool-Auswahlumfang behandeln, bis das Changelog explizit etwas anderes besagt.

Execution IDs sind über Wiederholungsschichten hinweg nicht stabil. Eine BullMQ-Wiederholung oder ein Transport-Reconnect kann eine neue Execution erzeugen. Verwende die früheste stabile Identifier-Quelle: supplied operation ID > n8n execution ID > transport request ID.

Bei Q3 ist es besser, einen Crash-Window-Test hinzuzufügen und einen Fehler nach dem Sink-Write erzwingen, bevor der Success-Ack kommt (Worker während des Aufrufs beenden) und dann erneut abspielen. Das ist das Zeitfenster, in dem Double-Writes tatsächlich stattfinden.

Nichts im Queue-Modus bietet At-Most-Once. Die Änderung 2.38.7 behebt die Tool-Auflösung auf Workern; ein Worker-Crash, Job-Neustart oder Client-Neuverbindung ergibt immer noch eine frische Ausführung mit einer neuen ID. Der Schlüssel muss also vom Aufrufer kommen. Die MCP JSON-RPC-Request-ID wird innerhalb des Workflows nicht offengelegt, und $execution.id ändert sich bei jedem Neustart, daher dedupliziert es nur innerhalb eines Durchlaufs.

Praktische Umsetzung: Im HTTP Request Tool einen Header Idempotency-Key mit dem Wert {{ $fromAI(‘call_id’, ‘unique id for this call’, ‘string’) || $execution.id }} hinzufügen. Deduplizierung auf der Empfängerseite anhand dieses Headers, niemals anhand von Argumenten, sodass zwei absichtliche Aufrufe mit identischen Eingaben immer noch zweimal geschrieben werden.

Regressions-Test: Die vier Aufrufe beibehalten, dann docker kill den Worker während eines Aufrufs durchführen und separat den SSE-Stream verwerfen und erneut senden. Eine Zeile pro Idempotency-Key und Zählung von Ausführungen nach Header, nicht nach Operation-ID bestätigen.

Hey @luislm21 Willkommen in der n8n-Community

Behalte die Kontrolle. Die gesamte 2.38.7-Änderung in job-processor.ts entpackt ein StructuredToolkit (das, was ein MCP Client Tool zurückgibt) zum benannten Member und schließt danach die MCP-Verbindung. Ein einzelnes HTTP Request Tool ist kein Toolkit, daher wird dieser Branch für deinen Workflow nie ausgeführt, und es gibt darin keine Deduplizierungslogik.

Die Queue wird deinen Aufruf auch nicht erneut ausführen. n8n erstellt die Bull-Queue mit maxStalledCount: 0, daher wird ein Job, dessen Worker abstürzt, als fehlgeschlagen markiert statt erneut versucht. Ein zweiter Schreibvorgang erfordert, dass der Aufruf zweimal ankommt, und n8n kann das verursachen: Im Queue-Modus stoppt main nach 120 Sekunden mit dem Warten und schlägt den Aufruf mit „Worker tool execution timeout

Hallo @luislm21,

Die Toolkit-Korrektur fügt Memberauswahl und Connection-Cleanup hinzu, ohne Deduplizierung hinzuzufügen. Eine Korrektur zum obigen Rat zur Operation-ID: Dein Intentional-Repeat-Test verwendet operation_id wieder, daher kann dieses Feld allein einen Wiederholungsversuch nicht von einer neuen Aktion unterscheiden.

Füge eine separate invocation_id hinzu, die vom Aufrufer vor dem ersten Versuch generiert und persistent gespeichert wird. Halte sie über Wiederholungen hinweg unverändert und verwende einen neuen Wert für jeden absichtlichen Aufruf, auch mit identischen geschäftlichen Argumenten. Übergebe sie als erforderliches Tool-Argument und leite sie an den Empfänger weiter. Lass das Modell sie nicht erfinden oder auf $execution.id zurückgreifen; MCP-Request-IDs sind auch keine dauerhaften Identifikatoren über Sessions hinweg.

Behalte deine vorhandenen Tests und füge hinzu:

  • Gleiche Argumente, unterschiedliche Invocation-IDs: zwei durchgeführte Schreibvorgänge.
  • Gleiche Invocation-ID gleichzeitig über SSE und Streamable HTTP: ein durchgeführter Schreibvorgang.
  • Empfänger committet, Antwort geht verloren, Aufrufer wiederholt explizit mit der gleichen Invocation-ID: immer noch ein durchgeführter Schreibvorgang.

Zähle durchgeführte Nebenwirkungen separat von HTTP-Versuchen und n8n-Ausführungen.

Der Empfänger muss den Schlüssel durchsetzen. Ein Header allein hilft nichts, und ein n8n-seitiger Marker kann einen separaten CRM-Schreibvorgang nicht atomar committen. Unterstützt dein CRM Idempotency-Keys, oder ist die Absicherung vollständig innerhalb von n8n?