Guide · AWS S3 · Disaster Recovery

S3 Replication for MCP Servers

MCP servers accumulate valuable data: tool execution logs, user-uploaded files, model outputs, configuration artifacts. Losing this data to an S3 region failure (extremely rare) or accidental deletion (less rare) is a real operational risk. S3 replication — both cross-region (CRR) and same-region (SRR) — creates durable copies automatically, without application-level coordination. This guide covers when to use CRR vs SRR, replication rule configuration for MCP data patterns, Replication Time Control (RTC) for SLA-bounded replication, failover patterns that complement AWS Global Accelerator multi-region deployments, and the replication cost model. For S3 basics see MCP Tools for AWS S3; for multi-region MCP deployment see Multi-Region MCP with Global Accelerator.

TL;DR

S3 Cross-Region Replication (CRR) copies objects to a bucket in a different AWS region — use it for DR, compliance with data-at-rest residency rules, and reducing read latency for globally distributed MCP servers. Same-Region Replication (SRR) copies within the same region — use it for log aggregation, cross-account copies, and compliance that requires two independent copies in the same geography. Replication is asynchronous: p99.9 replication in under 15 minutes without RTC; under 15 seconds with Replication Time Control (adds ~15% cost overhead). Existing objects are NOT replicated — only new puts after replication is enabled. For existing objects, use S3 Batch Replication.

CRR vs SRR: choosing for your MCP architecture

CriterionCross-Region Replication (CRR)Same-Region Replication (SRR)
Primary use case DR, global read latency reduction, data sovereignty Log aggregation, cross-account copies, compliance within one geography
Replication latency ~2–15 min (p99.9 without RTC); <15s with RTC ~1–5 min; <15s with RTC
Data transfer cost S3 outbound data transfer to destination region (~$0.02/GB) No inter-region transfer cost (same region)
Bidirectional replication Two separate rules required (A→B and B→A) Two separate rules required
Encryption support SSE-S3, SSE-KMS (requires KMS key in destination region) Same; cross-account SRR requires destination account KMS key
Storage class options Can downgrade to S3-IA or Glacier in destination (cost savings) Can downgrade to cheaper storage class in replica

For most MCP servers, CRR to a DR region is the first replication investment. Pair it with an active-passive Global Accelerator deployment: your primary region (us-east-1) handles all traffic, and if it fails, you flip the GA traffic dial to the DR region — which already has a replica of all S3 data thanks to CRR. Without CRR, the DR region's S3 bucket is empty when failover happens, and your MCP tools fail immediately on any S3 read.

Configuring CRR with CDK

import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';

// Source bucket in us-east-1 — versioning required for replication
const sourceBucket = new s3.Bucket(this, 'McpSourceBucket', {
  bucketName: `mcp-data-primary-${this.account}`,
  versioned: true,  // mandatory for replication
  encryption: s3.BucketEncryption.KMS,
  encryptionKey: sourceKmsKey,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

// Destination bucket in eu-west-1 — in a cross-region stack
// (CDK: use a separate stack with env: { region: 'eu-west-1' })
const destinationBucket = new s3.Bucket(drStack, 'McpDestinationBucket', {
  bucketName: `mcp-data-dr-${this.account}`,
  versioned: true,
  encryption: s3.BucketEncryption.KMS,
  encryptionKey: drKmsKey,
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

// IAM role that S3 assumes to perform replication
const replicationRole = new iam.Role(this, 'ReplicationRole', {
  assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'),
  inlinePolicies: {
    ReplicationPolicy: new iam.PolicyDocument({
      statements: [
        new iam.PolicyStatement({
          actions: [
            's3:GetReplicationConfiguration',
            's3:ListBucket',
          ],
          resources: [sourceBucket.bucketArn],
        }),
        new iam.PolicyStatement({
          actions: [
            's3:GetObjectVersionForReplication',
            's3:GetObjectVersionAcl',
            's3:GetObjectVersionTagging',
          ],
          resources: [sourceBucket.arnForObjects('*')],
        }),
        new iam.PolicyStatement({
          actions: [
            's3:ReplicateObject',
            's3:ReplicateDelete',
            's3:ReplicateTags',
          ],
          resources: [destinationBucket.arnForObjects('*')],
        }),
        // KMS permissions for SSE-KMS encrypted objects
        new iam.PolicyStatement({
          actions: ['kms:Decrypt'],
          resources: [sourceKmsKey.keyArn],
        }),
        new iam.PolicyStatement({
          actions: ['kms:GenerateDataKey'],
          resources: [drKmsKey.keyArn],
        }),
      ],
    }),
  },
});

// Configure replication rule via CfnBucket (CDK L2 doesn't expose replication config)
const cfnSource = sourceBucket.node.defaultChild as s3.CfnBucket;
cfnSource.replicationConfiguration = {
  role: replicationRole.roleArn,
  rules: [
    {
      id: 'ReplicateAllToEuWest1',
      status: 'Enabled',
      filter: {
        // Replicate all objects (no prefix filter)
        // For selective replication, use: prefix: 'critical-data/'
      },
      destination: {
        bucket: destinationBucket.bucketArn,
        storageClass: 'STANDARD',
        encryptionConfiguration: {
          replicaKmsKeyId: drKmsKey.keyArn,
        },
      },
      deleteMarkerReplication: {
        // Replicate deletes too — keeps replica in sync with source
        // Set to 'Disabled' if replica should be append-only (deletion protection)
        status: 'Enabled',
      },
    },
  ],
};

Delete marker replication decision: setting deleteMarkerReplication.status to Enabled means that when an object is deleted in the source bucket, the delete marker is replicated and the object is effectively deleted in the destination too. This is correct for DR (replica tracks source exactly). For compliance use cases where the replica must be an immutable audit copy, set to Disabled — deletes in source don't propagate to destination.

Replication Time Control (RTC): bounded replication for SLAs

Standard replication has a p99.9 guarantee of 15 minutes but no SLA for individual objects. If your MCP server failover plan requires the DR region to be current within 5 minutes, you need Replication Time Control:

cfnSource.replicationConfiguration = {
  role: replicationRole.roleArn,
  rules: [{
    id: 'RtcReplication',
    status: 'Enabled',
    destination: {
      bucket: destinationBucket.bucketArn,
      replicationTime: {
        status: 'Enabled',
        time: { minutes: 15 },  // SLA: replicate within 15 minutes
      },
      metrics: {
        status: 'Enabled',
        eventThreshold: { minutes: 15 },
        // EventBridge fires when replication exceeds 15 minutes for any object
      },
    },
    deleteMarkerReplication: { status: 'Enabled' },
  }],
};

RTC adds monitoring infrastructure that tracks per-object replication time and emits CloudWatch metrics (ReplicationLatency, BytesPendingReplication, OperationsPendingReplication). These metrics are the leading indicators of replication health. AliveMCP monitors these metrics and fires an alert if replication latency exceeds 50% of your RTC threshold — a 15-minute RTC SLA triggers an alert at 7.5 minutes of observed replication latency, giving you time to investigate before the SLA is breached.

RTC cost: approximately 15% overhead on top of standard replication costs. For most MCP workloads (kilobytes to megabytes of tool data), this is negligible. For high-throughput MCP servers writing gigabytes per minute, model the cost before enabling RTC everywhere — use it selectively for critical prefixes only.

Selective replication: critical vs archival data

Not all S3 data in a MCP server needs cross-region replication. Replication cost = data transfer cost + storage in destination + replication API calls. For large, infrequently-accessed archives, CRR to another region may be unnecessary — a backup to Glacier within the same region is cheaper. Use prefix-based rules to replicate selectively:

cfnSource.replicationConfiguration = {
  role: replicationRole.roleArn,
  rules: [
    {
      id: 'ReplicateCriticalData',
      priority: 1,  // higher priority executes first
      status: 'Enabled',
      filter: {
        and: {
          prefix: 'critical/',
          tagFilters: [{ key: 'dr-required', value: 'true' }],
        },
      },
      destination: {
        bucket: destinationBucket.bucketArn,
        storageClass: 'STANDARD',
        replicationTime: { status: 'Enabled', time: { minutes: 15 } },
        metrics: { status: 'Enabled', eventThreshold: { minutes: 15 } },
      },
      deleteMarkerReplication: { status: 'Enabled' },
    },
    {
      id: 'ReplicateLogs',
      priority: 2,
      status: 'Enabled',
      filter: {
        prefix: 'logs/',
        // Don't replicate logs cross-region — replicate within same region only
        // (use a separate SRR rule for this)
      },
      destination: {
        // SRR destination would be in the same region — use a different bucket for SRR
        bucket: logArchiveBucket.bucketArn,
        storageClass: 'STANDARD_IA',  // cheaper storage for logs
      },
      deleteMarkerReplication: { status: 'Disabled' },  // logs are append-only
    },
  ],
};

Tag-based replication filtering is particularly useful for MCP servers with multiple tenants in the same bucket. Tag objects with the tenant's tier (tier: enterprise) at upload time and configure replication rules to only CRR enterprise-tier data. Author-tier data stays in the source region only — matching the pricing and SLA commitments of each tier.

Bidirectional replication for active-active S3

In active-active MCP deployments (both regions receiving write traffic), S3 data must be written in both regions and kept in sync. Bidirectional replication uses two separate replication rules — us-east-1 → eu-west-1 and eu-west-1 → us-east-1:

// Bidirectional replication creates a replication loop risk:
// - Object A written in us-east-1
// - CRR replicates A to eu-west-1
// - SRR/CRR from eu-west-1 tries to replicate A back to us-east-1 (loop!)
// S3 prevents this: replicated objects are NOT eligible for re-replication
// (S3 sets the x-amz-replication-status: REPLICA metadata on replicated objects)
// Objects with status REPLICA are excluded from replication rules automatically

// us-east-1 stack: replicate to eu-west-1
const cfnPrimaryBucket = primaryBucket.node.defaultChild as s3.CfnBucket;
cfnPrimaryBucket.replicationConfiguration = {
  role: primaryReplicationRole.roleArn,
  rules: [{
    id: 'PrimaryToDr',
    status: 'Enabled',
    destination: { bucket: drBucket.bucketArn },
    deleteMarkerReplication: { status: 'Enabled' },
  }],
};

// eu-west-1 stack (separate CDK stack): replicate back to us-east-1
const cfnDrBucket = drBucket.node.defaultChild as s3.CfnBucket;
cfnDrBucket.replicationConfiguration = {
  role: drReplicationRole.roleArn,
  rules: [{
    id: 'DrToPrimary',
    status: 'Enabled',
    destination: { bucket: primaryBucket.bucketArn },
    deleteMarkerReplication: { status: 'Enabled' },
  }],
};

Write conflicts in bidirectional replication: if the same object key is written in both regions within the same replication window (typically <1 minute), S3 uses last-write-wins based on the object's modification timestamp. For MCP servers with strong consistency requirements, avoid writing to the same key from multiple regions — use region-specific key prefixes (us-east-1/sessions/id vs eu-west-1/sessions/id) and merge at read time in the application layer.

Replication failure handling and S3 Batch Replication

Replication failures are surfaced via S3 event notifications: configure your source bucket to emit s3:Replication:OperationFailedReplication events to an SQS queue for monitoring. Common causes:

Failure causeDetectionRemediation
Destination bucket versioning disabled OperationFailedReplication event immediately Enable versioning on destination bucket
Replication role permissions revoked OperationFailedReplication events on all objects Restore role permissions; re-replicate failed objects via Batch Replication
KMS key in destination region deleted/disabled OperationFailedReplication on encrypted objects Restore KMS key; re-replicate via Batch Replication
Destination bucket not found (deleted) OperationFailedReplication events; BytesPendingReplication climbs Recreate destination bucket; re-replicate via Batch Replication

S3 Batch Replication re-processes existing objects that failed replication or were uploaded before replication was configured. It's a one-time Batch Operations job:

import { S3ControlClient, CreateJobCommand } from '@aws-sdk/client-s3-control';

const s3Control = new S3ControlClient({ region: 'us-east-1' });

// Create a manifest of objects to re-replicate (or use S3-generated manifest)
await s3Control.send(new CreateJobCommand({
  AccountId: process.env.ACCOUNT_ID,
  ConfirmationRequired: false,
  Operation: {
    S3ReplicateObject: {},  // S3 Batch Replication operation type
  },
  Report: {
    Bucket: reportBucketArn,
    Format: 'Report_CSV_20180820',
    Enabled: true,
    Prefix: 'replication-reports/',
    ReportScope: 'AllTasks',  // or FailedTasksOnly
  },
  Manifest: {
    Spec: {
      Format: 'S3BatchOperations_CSV_20180820',
      Fields: ['Bucket', 'Key'],
    },
    Location: {
      ObjectArn: manifestObjectArn,
      ETag: manifestETag,
    },
  },
  Priority: 10,
  RoleArn: batchOperationsRoleArn,
}));

Batch Replication is how you bootstrap replication on an existing bucket with thousands of objects. It's also the recovery path when replication fails for a subset of objects — identify the failures from the OperationFailedReplication events, generate a manifest of the affected keys, and submit a Batch Replication job to retry them.

How AliveMCP monitors S3 replication health

Replication failures are one of the most insidious failure modes in MCP DR architectures: the primary region appears healthy (all probes pass), but the DR region's S3 bucket is silently falling behind. You only discover the gap when you actually need to fail over. AliveMCP addresses this with proactive replication monitoring:

The combination of lag monitoring and failure event monitoring means AliveMCP catches both the "replication is slow" (lag probe) and "replication is broken" (failure event) failure modes before they affect your DR capability. This monitoring is available on the Author tier for a single replication pair; Team tier adds multi-region correlation and the canary replication timing test.