Guide · AWS SQS FIFO

SQS FIFO Message Deduplication for MCP Servers — MessageDeduplicationId vs ContentBasedDeduplication

SQS FIFO queues deduplicate messages within a 5-minute window: if two messages with the same deduplication token arrive at the same queue within that window, the second is silently discarded. This is critical for MCP monitoring pipelines where probe workers may retry a failed send — without deduplication you could process the same probe result twice, double-count an alert, or flip a verdict back to a stale state. The two mechanisms are MessageDeduplicationId (you supply an explicit token per message) and ContentBasedDeduplication (SQS hashes the body). Choosing the wrong one for your payload shape is one of the most common sources of silent message loss in SQS FIFO queues: a payload that contains a timestamp or any non-deterministic field will never match on content hash, making ContentBasedDeduplication a no-op — but two healthy-probe payloads with the same body content will always match, dropping one silently.

TL;DR

Enable ContentBasedDeduplication on the queue only if your message bodies are fully deterministic (no timestamps, no UUIDs, no per-invocation trace IDs). For probe-result payloads — which always include a probeRunId or timestamp — use explicit MessageDeduplicationId keyed on {endpointId}-{probeRunId}. The deduplication window is exactly 5 minutes and cannot be changed. Messages with the same ID sent after the window are treated as new. If you omit both mechanisms on a FIFO queue, SQS returns a InvalidParameterValue error.

How the deduplication window works

When a message arrives at a FIFO queue, SQS records the deduplication token and the arrival time. Any subsequent message with the same token received within 5 minutes of the first is silently accepted (SQS returns success) but not enqueued — the consumer never sees it. After 5 minutes the token expires, and a message with the same token is treated as a new, distinct message.

Two important consequences for MCP probe pipelines:

ContentBasedDeduplication: when it works and when it silently breaks

When ContentBasedDeduplication is enabled on a queue, SQS computes a SHA-256 hash of the raw message body and uses that as the deduplication token. You do not supply a MessageDeduplicationId — SQS derives it automatically.

This is safe only when your message bodies are fully deterministic for logically identical events:

Payload patternContentBasedDeduplication safe?Why
Pure command (no timestamps, no UUIDs): {"action":"ping","endpoint":"..."}YesSame logical command always produces the same hash
Probe result with timestamp fieldNoEach send has a unique timestamp — hash never matches, deduplication never fires
Probe result with probeRunId (UUID)NoEach probe run has a unique ID — same broken behaviour as timestamp
Aggregated hourly health summary (deterministic for given hour + endpoint)Yes, with careSame hour + endpoint always produces the same body, but a body with even one float field may differ across architectures
JSON with key ordering not guaranteed (e.g., JSON.stringify on an object whose properties were inserted in different orders)NoSame logical payload serialises to different byte sequences → different hashes

The failure mode is silent: SQS accepts the message, returns HTTP 200, but never enqueues a second copy when you expect it to. Your consumer processes only the first copy of a legitimately distinct probe result, and you may never notice — the DLQ stays empty, no errors are thrown, messages simply vanish.

MessageDeduplicationId: format best practices

An explicit MessageDeduplicationId is a string up to 128 characters, alphanumeric and !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~. It must be unique per distinct logical event within the 5-minute window.

Recommended formats for MCP monitoring pipelines:

// Probe result: one result per endpoint per run — {endpointId}-{probeRunId}
const deduplicationId = `${endpointId}-${probeRunId}`;
// Example: "server-abc123-run-9f4e2b"

// Alert dispatch: one alert per endpoint per incident — {endpointId}-{incidentId}
const deduplicationId = `alert-${endpointId}-${incidentId}`;
// Example: "alert-server-abc123-inc-20261029T120000Z"

// Verdict-minute aggregation: one aggregated verdict per endpoint per minute
const minute = new Date().toISOString().slice(0, 16).replace(':', '');  // "2026-10-29T1200"
const deduplicationId = `verdict-${endpointId}-${minute}`;
// Example: "verdict-server-abc123-2026-10-29T1200"

Key rules:

Enabling deduplication in CDK

import { Queue, QueueEncryption, DeduplicationScope, FifoThroughputLimit } from 'aws-cdk-lib/aws-sqs';

// Option A — content-based (only for deterministic payloads)
const probeCommandQueue = new Queue(this, 'ProbeCommandQueue', {
  queueName: 'mcp-probe-commands.fifo',
  fifo: true,
  contentBasedDeduplication: true,         // SQS derives token from SHA-256(body)
  visibilityTimeout: Duration.seconds(30),
  encryption: QueueEncryption.KMS_MANAGED,
});

// Option B — explicit MessageDeduplicationId (recommended for probe results)
const probeResultQueue = new Queue(this, 'ProbeResultQueue', {
  queueName: 'mcp-probe-results.fifo',
  fifo: true,
  contentBasedDeduplication: false,        // caller must supply MessageDeduplicationId
  visibilityTimeout: Duration.seconds(30),
  encryption: QueueEncryption.KMS_MANAGED,
});

// Sending with explicit ID (AWS SDK v3)
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
const sqs = new SQSClient({});

await sqs.send(new SendMessageCommand({
  QueueUrl: probeResultQueueUrl,
  MessageGroupId: endpointId,              // per-endpoint ordering
  MessageDeduplicationId: `${endpointId}-${probeRunId}`,
  MessageBody: JSON.stringify(probeResult),
}));

Deduplication scope in high-throughput FIFO mode

Standard FIFO queues deduplicate across the entire queue — the token is global. High-throughput FIFO queues (enabled by setting DeduplicationScope=messageGroup and FifoThroughputLimit=perMessageGroupId) scope deduplication to the message group: the same MessageDeduplicationId can be safely reused across different MessageGroupId values.

const highThroughputQueue = new Queue(this, 'HighThroughputProbeQueue', {
  queueName: 'mcp-probe-ht.fifo',
  fifo: true,
  contentBasedDeduplication: false,
});

// Enable high-throughput FIFO via escape hatch (CDK L1)
const cfnQueue = highThroughputQueue.node.defaultChild as CfnQueue;
cfnQueue.deduplicationScope = 'messageGroup';
cfnQueue.fifoThroughputLimit = 'perMessageGroupId';

For AliveMCP's per-endpoint probe pipeline, high-throughput FIFO makes sense only if you have many endpoints each generating probe results at high frequency. With message-group scoped deduplication, a MessageDeduplicationId of run-{probeRunId} (short form, without endpoint prefix) is sufficient — two different endpoints can use the same short form without collision, because their MessageGroupId values differ.

Common failure modes

SymptomCauseFix
Messages silently lost after producer retryRetry occurs within 5-minute window with the same deduplication ID — second message deduplicatedExpected and correct. Ensure retry logic computes the same ID as the original send; if it generates a new UUID, second copy will enqueue
Same probe result processed twice despite FIFO queueTwo sends of the same event use different MessageDeduplicationId values (e.g., new UUID generated per retry)Pre-compute deduplication ID before first send, store it, reuse on retry
InvalidParameterValue on sendQueue has contentBasedDeduplication: false and no MessageDeduplicationId suppliedAlways supply MessageDeduplicationId when content-based deduplication is off
DLQ replay re-deduplicates and message never reaches consumerReplaying DLQ message within 5 minutes of original sendWait 5 minutes before replaying; or use a different deduplication ID on replay if the original event semantics have changed
ContentBasedDeduplication drops distinct probe resultsTwo probes for the same endpoint produce identical JSON body (e.g., both returned healthy with same latency and the body has no per-run fields)Add a probeRunId field to the body, or switch to explicit MessageDeduplicationId