Docker con modo de cola Redis, un proceso principal, un worker y ejecutores de tareas externos
Disparador de servidor MCP con miembro de herramienta de solicitud HTTP
Operaciones HTTP POST no idempotentes posteriores
Contexto
Antes observamos escrituras duplicadas en CRM aguas abajo de un único envío de formulario lógico, por lo que agregamos un guard de idempotencia temporal y estrictamente delimitado. Ese guard ha sido validado contra n8n 2.38.7. Deliberadamente no queremos almacenamiento en caché de respuestas amplias porque podría ocultar una segunda llamada legítima con los mismos argumentos.
Nuestra prueba aislada utiliza un punto final de contador local en lugar de un CRM:
Expone un disparador de servidor MCP con una herramienta de solicitud HTTP que realiza un POST local para un ID de operación sintética suministrada.
Realiza cuatro llamadas: HTTP transmisible, SSE y una llamada concurrente a través de cada transporte.
Cada operación debe resultar en exactamente un POST HTTP y una ejecución n8n exitosa.
Por separado, repite el mismo ID de operación dos veces y requiere dos escrituras, demostrando que el guard no almacena en caché ni contrae solicitudes legítimas.
En 2.38.7 obtenemos cuatro ejecuciones exitosas y exactamente una escritura para cada una de las cuatro llamadas distintas. La llamada repetida intencional produce dos escrituras.
Preguntas
¿Garantiza la corrección del kit de herramientas MCP del lado del worker en 2.38.7 que una invocación única de herramienta MCP se ejecute solo una vez a través del límite principal/worker, se reconecte y reintente? ¿O solo resuelve fallas de enrutamiento/selección de herramientas?
¿Existe un ID de solicitud MCP canónico o un ID de ejecución n8n que deba reenviarse a una herramienta HTTP como clave de idempotencia?
¿Hay una prueba de regresión recomendada en modo de cola para ejecución exactamente una vez de una llamada de herramienta MCP que aún permita dos llamadas intencionales con entradas idénticas?
No se incluyen datos de clientes, credenciales, dominios o exportaciones de flujos de trabajo.
Sobre Q1/Q2 asumo que al menos una vez es la única garantía en cualquier límite de reintento principal/worker+, eso es cierto para cada sistema de cola y trataría el arreglo 2.38.7 como alcance de enrutamiento/selección de herramientas hasta que el registro de cambios diga lo contrario explícitamente
Los ID de ejecución no son estables entre capas de reintento; un reintento de BullMQ o una reconexión de transporte puede generar una nueva ejecución. Usa la fuente de identificador estable más temprana suministrada: ID de operación > ID de ejecución de n8n > ID de solicitud de transporte.
Q3 es mejor para agregar la prueba de ventana de bloqueo: fuerza una falla después de la escritura del sumidero antes del ack de éxito (mata el worker a mitad de la llamada) y luego reproduce. Esa es la ventana donde los escritos dobles realmente ocurren
Nada en modo de cola proporciona at-most-once. El cambio 2.38.7 corrige la resolución de herramientas en workers; un fallo de worker, reintento de trabajo o reconexión del cliente aún genera una ejecución nueva con un id nuevo. Por lo tanto, la clave debe provenir del llamador. El id de solicitud JSON-RPC de MCP no se expone dentro del flujo de trabajo, y $execution.id cambia en cada reintento, por lo que solo deduplica dentro de una ejecución.
Forma práctica: en la Herramienta de Solicitud HTTP agrega un encabezado Idempotency-Key con valor {{ $fromAI(‘call_id’, ‘unique id for this call’, ‘string’) || $execution.id }}. Deduplica en el lado receptor por ese encabezado, nunca por argumentos, así que dos llamadas deliberadas con entradas idénticas aún escriben dos veces.
Prueba de regresión: mantén las cuatro llamadas, luego usa docker kill en el worker a mitad de la llamada, y por separado suelta la transmisión SSE y reenvía. Afirma una fila por Idempotency-Key y cuenta ejecuciones por encabezado, no por id de operación.
Mantén la guardia. Todo el cambio 2.38.7 en job-processor.ts desenvuelve un StructuredToolkit (lo que devuelve una Herramienta de Cliente MCP) al miembro nombrado y cierra esa conexión MCP después. Una Herramienta de Solicitud HTTP única no es un kit de herramientas, por lo que esa rama nunca se ejecuta para tu flujo de trabajo, y no hay lógica de deduplicación en ella.
La cola tampoco volverá a ejecutar tu llamada. n8n construye la cola Bull con maxStalledCount: 0, por lo que un trabajo cuyo worker muere se marca como fallido en lugar de reintentar. Una segunda escritura necesita que la llamada llegue dos veces, y n8n puede causar eso: en modo de cola el main deja de esperar después de 120 segundos y falla la llamada con “Worker tool execution timeout” sin cancelar el trabajo, por lo que el POST sigue llegando, el resultado tardío se descarta, y un cliente que reintenta en errores la envía de nuevo.
Por eso tu operation id es la clave correcta, ya que una llamada reenviada obtiene una nueva ejecución y $execution.id no coincidirá. Para probarlo, haz que el receptor registre la escritura y mantenga su respuesta pasados los 120 segundos, reenvía el mismo operation id cuando el timeout regresa, y afirma una escritura.
La corrección del kit de herramientas añade selección de miembros y limpieza de conexiones, sin añadir deduplicación. Una corrección al consejo sobre operation-ID anterior: tu prueba de repetición intencional reutiliza operation_id, por lo que ese campo por sí solo no puede distinguir un reintento de una acción nueva.
Añade un invocation_id separado, generado y persistido por quien llama antes del primer intento. Mantenlo sin cambios en los reintentos y usa un valor nuevo para cada llamada intencional, incluso con argumentos de negocio idénticos. Pásalo como argumento obligatorio de la herramienta y reenvíalo al receptor. No dejes que el modelo lo invente ni recurras a $execution.id; los IDs de solicitud de MCP tampoco son identificadores duraderos entre sesiones.
Mantén tus pruebas existentes y añade:
Mismos argumentos, diferentes invocation IDs: dos escrituras confirmadas.
Mismo invocation ID concurrentemente sobre SSE y HTTP transmisible: una escritura confirmada.
El receptor confirma, la respuesta se pierde, quien llama reintenta explícitamente con el mismo invocation ID: aún una escritura confirmada.
Cuenta los efectos secundarios confirmados por separado de los intentos HTTP y las ejecuciones de n8n.
El receptor debe aplicar la clave. Una cabecera por sí sola no hace nada, y un marcador del lado de n8n no puede confirmar atómicamente una escritura CRM separada. ¿Tu CRM admite claves de idempotencia, o la protección está completamente dentro de n8n?