Guide · AWS Networking · Global Accelerator
AWS Global Accelerator Routing for MCP Server Endpoints
Global Accelerator's routing model — listeners, endpoint groups, traffic dials, endpoint weights, and client affinity — is the layer where you control which MCP server receives which client, across how many regions, and at what traffic fraction. Getting this configuration wrong costs you either availability (too aggressive a failover) or sticky-session correctness (wrong affinity setting). This guide covers the full routing object model, SOURCE_IP affinity for stateful SSE connections, traffic dial patterns for blue/green and canary deployments, and when to use a custom routing accelerator instead of a standard one. For GA overview and CDK setup see Global Accelerator for MCP Servers; for health checks and failover timing see Global Accelerator Health Checks; for multi-region active-active see Multi-Region MCP Servers with GA.
TL;DR
Set client affinity to SOURCE_IP on any listener that carries SSE sessions — without it, reconnecting clients may land on a different endpoint and lose session state. Use traffic dials at 0% for standby regions and ramp slowly during blue/green. Use endpoint weights within a group for canary (5% to new ECS task version). For large-scale MCP deployments where each client session must reach a specific process, evaluate custom routing accelerators — they provide deterministic IP:port → EC2 mapping that standard GA load balancing cannot guarantee.
Listener configuration for MCP SSE
A GA listener specifies which ports to accept and how to handle client affinity. For MCP over SSE, you typically create one listener per protocol port:
// listener for MCP SSE (HTTPS/443) with SOURCE_IP affinity
const mcpListener = accelerator.addListener('McpSseListener', {
portRanges: [{ fromPort: 443, toPort: 443 }],
protocol: ga.ConnectionProtocol.TCP,
clientAffinity: ga.ClientAffinity.SOURCE_IP,
});
// separate listener for MCP management API (8443) — no affinity needed
// because management calls are stateless
const mgmtListener = accelerator.addListener('McpMgmtListener', {
portRanges: [{ fromPort: 8443, toPort: 8443 }],
protocol: ga.ConnectionProtocol.TCP,
clientAffinity: ga.ClientAffinity.NONE,
});
SOURCE_IP affinity is a 5-tuple consistent hash on client IP (not the full 5-tuple) — GA uses the source IP address to deterministically route new connections from the same client to the same endpoint. The affinity persists for the duration of the TCP connection, which for SSE means the entire session. When the SSE connection drops and reconnects, the same client IP re-hashes to the same endpoint.
Caveat for clients behind corporate NAT: if multiple MCP clients share an outbound IP (common in enterprise environments), all of them hash to the same endpoint. If you have 500 corporate users behind one NAT IP, they all hit the same backend. Combine SOURCE_IP affinity with adequate capacity on each endpoint — don't rely on affinity as a load distribution mechanism.
Endpoint group and traffic dial patterns
Each listener can have one endpoint group per AWS region. The traffic dial (0–100%) controls what fraction of traffic the listener routes to each group. GA distributes traffic proportionally across groups by dial — a group at 50% with a group at 50% splits traffic evenly regardless of geographic proximity (proximity still affects POP routing, but dial controls the total fraction).
| Pattern | Primary group dial | Secondary group dial | Use case |
|---|---|---|---|
| Single region (normal) | 100% | — | Default for startups; simplest setup |
| Active-passive DR | 100% | 0% | DR region warm and healthy but receiving no traffic; failover by setting primary to 0% + secondary to 100% |
| Blue/green deployment | 100% (blue) | 0% → 100% (green) | New region with new version at 0%; smoke test; ramp green to 100%; set blue to 0%; delete blue |
| Canary (5%) | 95% | 5% | New region with new MCP version receives 5% of traffic for validation before full ramp |
| Active-active (geo split) | 50% (us-east-1) | 50% (eu-west-1) | Both regions fully active; GA routes each client to nearest POP then to nearest group; requires shared session state across regions |
Traffic dials are a blunt instrument — they divide the total traffic pool, not the geographic bucket. A 50/50 dial sends roughly half of all global clients to each region, regardless of whether those clients are geographically near one region. GA's POP routing still routes clients through the nearest edge, but the endpoint group selection respects the dial percentages. For true latency-based routing where EU clients always go to eu-west-1 and US clients always go to us-east-1, you need proximity routing — which GA provides automatically when both groups are at 100% and GA detects the closer group's endpoints as healthy.
Proximity-based routing: how GA picks the group
When multiple endpoint groups are present and all have non-zero traffic dials, GA routes each incoming connection to the geographically nearest healthy endpoint group. "Nearest" is measured by GA's internal latency model from the client's ingress POP, not by physical distance. This means:
- A client in London connecting to a GA with groups in us-east-1 (100%) and eu-west-1 (100%) will typically be routed to eu-west-1 because the London POP has lower backbone latency to eu-west-1.
- If eu-west-1's endpoints become unhealthy, GA automatically fails over to us-east-1 — the London client is re-routed to us-east-1 within 30 seconds of health failure detection.
- Traffic dials override proximity: if eu-west-1 is at 0%, London clients go to us-east-1 even though eu-west-1 is closer. Use this for planned maintenance windows — set the region being maintained to 0%, then back to 100% when done.
Endpoint weights within a group
Within a single endpoint group, you can assign weights (0–255) to individual endpoints (NLBs, ALBs, EC2 instances, EIPs). GA distributes traffic proportionally to weight values. This enables within-region canary deployments: add a new NLB (pointing to your new ECS task set) at weight 5, keep the existing NLB at weight 100, and gradually increase the new NLB's weight as you verify the new version.
listener.addEndpointGroup('UsEast1Group', {
region: 'us-east-1',
trafficDialPercentage: 100,
endpoints: [
// existing NLB — 95% of weight-proportional traffic
new gaEndpoints.NetworkLoadBalancerEndpoint(existingNlb, {
weight: 100,
preserveClientIp: true,
}),
// new NLB with updated MCP version — 5% of weight-proportional traffic
// increase this weight as validation passes; remove old NLB when done
new gaEndpoints.NetworkLoadBalancerEndpoint(newNlb, {
weight: 5,
preserveClientIp: true,
}),
],
});
Setting an endpoint's weight to 0 effectively removes it from the pool without deleting it — useful for graceful draining. Unlike endpoint group traffic dial (which affects the whole region), per-endpoint weight allows in-region A/B testing without DNS changes or ALB weighted target groups.
Standard vs custom routing accelerator
GA offers two accelerator types. Standard accelerators (what we've discussed so far) let GA decide which endpoint receives each connection — it picks from the endpoint group based on health and weight. The connection is routed to any healthy endpoint, with optional SOURCE_IP affinity for session stickiness.
Custom routing accelerators work differently: you define a port range on the listener (e.g., 10000–20000), and GA deterministically maps each (accelerator_ip, port) combination to exactly one (ec2_instance_ip, port). Every connection to 1.2.3.4:10042 always goes to 10.0.0.5:8042. No load balancing decision is made — the mapping is fixed until you update it via the API.
Custom routing is useful for MCP architectures where each client session must bind to a specific MCP server process — for example, an agent orchestration platform where each orchestrator process owns a set of tool-server connections and those connections cannot migrate to a different process without session teardown. Custom routing gives you deterministic addressing without relying on SOURCE_IP affinity (which can break if client IP changes).
| Property | Standard accelerator | Custom routing accelerator |
|---|---|---|
| Endpoint selection | GA selects based on health + weight | Deterministic: each IP:port maps to one EC2 instance:port |
| Load balancing | Yes — GA distributes across healthy endpoints | No — you manage port:instance assignment externally |
| Target type | NLB, ALB, EC2, EIP | EC2 instance only (one instance per port mapping) |
| Session affinity | SOURCE_IP hash (optional) | Guaranteed — same port always reaches same instance |
| MCP use case | Standard MCP SSE endpoints with NLB backend | Stateful MCP orchestrators where clients must reach specific process |
| Pricing | $0.025/hr + data premium | $0.025/hr + data premium (same) |
For most MCP server deployments — a single stateless tool server with SSE transport — standard accelerator + SOURCE_IP affinity is the right choice. Custom routing is for agent orchestration platforms that implement their own connection mapping layer.
Combining GA traffic dials with NLB health checks
GA and NLB are independent health check layers. GA health-checks the NLB endpoint (TCP port open, or HTTP 200 at the GA health check URL). NLB health-checks the ECS tasks behind it. A failure can occur at either layer:
- NLB healthy, MCP app unhealthy: If the NLB health check only checks TCP connectivity (port open) and the MCP process is returning 500s, GA sees a healthy NLB but clients get errors. Fix: use HTTP health checks at both NLB and GA that check
/healthendpoint. - NLB unhealthy (all tasks down): NLB has no healthy targets. GA health check to NLB fails (NLB returns TCP RST or times out). GA marks endpoint unhealthy and routes to next group within 30 seconds.
- GA health check timing lag: GA checks every 10s and requires 3 consecutive failures = 30-second detection. After detection, GA re-routes within seconds. Total failover window: ~35s from first task failure to GA re-routing.
AliveMCP's probe design accounts for this: when we monitor an MCP endpoint that's fronted by GA, we probe both the origin endpoint and the GA endpoint separately. GA masking a partial failure is a known blind spot — AliveMCP raises an alert on origin health degradation even if GA is still reporting the endpoint as healthy from the client's perspective.