Guide · AWS Lambda

MCP Server Lambda@Edge — edge routing, auth, and geo-routing for MCP endpoints

Lambda@Edge runs your code at CloudFront edge locations — reducing latency for auth, routing, and request rewriting — but it cannot run full MCP server logic. Three constraints make Lambda@Edge unsuitable as the MCP execution layer: 128 MB memory limit (the MCP SDK, tool handlers, and their dependencies typically exceed 128 MB; Lambda@Edge viewer-facing functions are capped at 128 MB versus the 10 GB available to regional Lambda), no VPC access (Lambda@Edge cannot be placed in a VPC and cannot reach private databases, ElastiCache, or internal services — all the data sources your MCP tools query), and 5-second timeout for viewer-request and viewer-response triggers (tool calls routinely take 5–60 seconds; the edge function would time out before the tool completes). Use Lambda@Edge for the functions it excels at: token validation, geo-routing to the nearest regional origin, and request normalization.

TL;DR

Run the MCP server itself in regional Lambda (with Function URLs) or ECS. Put CloudFront in front for a custom domain. Use Lambda@Edge or CloudFront Functions at the edge for: JWT signature validation (reject bad tokens before they reach your origin), geo-routing (route /mcp requests to the nearest regional Lambda origin), and request rewriting (normalize paths, add headers). Viewer-request triggers have a 5-second timeout; origin-request triggers have 30 seconds and 128 MB — neither is sufficient for running tool calls.

Lambda@Edge vs CloudFront Functions vs regional Lambda

AWS offers three compute layers at CloudFront: CloudFront Functions (sub-millisecond, JavaScript, very limited), Lambda@Edge (milliseconds, Node.js or Python, moderate limits), and regional Lambda (seconds to minutes, all runtimes, full limits). Match the workload to the layer.

CapabilityCloudFront FunctionsLambda@Edge (viewer)Lambda@Edge (origin)Regional Lambda
Max timeout1ms5s30s900s
Max memory2 MB128 MB128 MB10,240 MB
VPC accessNoNoNoYes
Environment variablesNoNo (read from event)NoYes
Async/network callsNoYes (within timeout)Yes (within timeout)Yes
Good for MCP tool executionNoNoNoYes
Good for MCP auth/routingYes (simple checks)Yes (JWT validation)Yes (geo-routing)Not needed here

Pattern 1: Edge JWT validation with CloudFront Functions

CloudFront Functions run in sub-millisecond at every edge location and can validate JWT structure and signature for simple HMAC-signed tokens. This pattern rejects unauthorized requests before they reach your origin, reducing load and preventing unauthenticated sessions from consuming Lambda concurrency.

// cloudfront-mcp-auth.js — CloudFront Function (viewer-request trigger)
// Validates HMAC-SHA256 signed JWT before forwarding to Lambda origin
// Note: CloudFront Functions do not support crypto.subtle — use a pre-shared key pattern

function handler(event) {
    var request = event.request;
    var headers = request.headers;

    // Only protect MCP endpoint paths
    if (!request.uri.startsWith('/mcp')) {
        return request;  // pass through for other paths
    }

    var authHeader = headers['authorization'] ? headers['authorization'].value : '';
    if (!authHeader.startsWith('Bearer ')) {
        return {
            statusCode: 401,
            statusDescription: 'Unauthorized',
            headers: {
                'content-type': { value: 'application/json' },
                'cache-control': { value: 'no-store' },
            },
            body: JSON.stringify({ error: 'Missing or invalid Authorization header' }),
        };
    }

    // For full JWT signature validation, use Lambda@Edge (origin-request trigger)
    // CloudFront Functions cannot make network calls to fetch JWKS
    // This function only validates token format; signature check happens at origin
    var token = authHeader.slice(7);
    var parts = token.split('.');
    if (parts.length !== 3) {
        return {
            statusCode: 401,
            statusDescription: 'Unauthorized',
            headers: { 'content-type': { value: 'application/json' } },
            body: JSON.stringify({ error: 'Malformed token' }),
        };
    }

    return request;  // pass to origin for signature validation
}

For full JWT signature validation at the edge (including JWKS fetch from Cognito), use a Lambda@Edge origin-request trigger (30-second timeout, can make HTTPS calls to fetch the JWKS URI). Cache the JWKS response at the Lambda@Edge execution environment level to avoid fetching on every request.

Pattern 2: Geo-routing to nearest regional MCP origin

If you deploy MCP servers in multiple AWS regions (us-east-1, eu-west-1, ap-southeast-1), a Lambda@Edge origin-request trigger can inspect the client's country/region header and route to the closest regional origin, reducing round-trip latency for MCP tool calls.

// lambda-edge-geo-router.js — Lambda@Edge (origin-request trigger)
// Deployed in us-east-1 (Lambda@Edge functions must be in us-east-1)
// Runs at CloudFront edge location closest to the viewer

exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;

    // CloudFront adds viewer-country header (ISO 3166-1 alpha-2)
    const viewerCountry = headers['cloudfront-viewer-country']?.[0]?.value ?? 'US';

    // Map viewer country to nearest regional Lambda Function URL
    const regionMap = {
        // Europe
        'GB': 'eu-west-1', 'DE': 'eu-west-1', 'FR': 'eu-west-1',
        'NL': 'eu-west-1', 'SE': 'eu-north-1', 'FI': 'eu-north-1',
        // Asia Pacific
        'JP': 'ap-northeast-1', 'AU': 'ap-southeast-2',
        'SG': 'ap-southeast-1', 'IN': 'ap-south-1',
    };

    const region = regionMap[viewerCountry] ?? 'us-east-1';  // default to US

    // Regional Lambda Function URL origins (configured in CloudFront distribution)
    const originDomains = {
        'us-east-1': 'abc123.lambda-url.us-east-1.on.aws',
        'eu-west-1': 'def456.lambda-url.eu-west-1.on.aws',
        'ap-southeast-1': 'ghi789.lambda-url.ap-southeast-1.on.aws',
        'ap-northeast-1': 'jkl012.lambda-url.ap-northeast-1.on.aws',
    };

    const targetDomain = originDomains[region] ?? originDomains['us-east-1'];

    // Rewrite origin host
    request.origin = {
        custom: {
            domainName: targetDomain,
            port: 443,
            protocol: 'https',
            readTimeout: 60,
            keepaliveTimeout: 5,
            sslProtocols: ['TLSv1.2'],
            customHeaders: {},
        }
    };
    request.headers['host'] = [{ key: 'Host', value: targetDomain }];

    return request;
};

Deployment constraint: Lambda@Edge functions must be deployed in us-east-1 regardless of where your origin regions are — CloudFront replicates the function to all edge locations automatically. The CDK code goes in a stack that targets us-east-1.

CDK pattern for Lambda@Edge with CloudFront

import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { EdgeFunction } from "aws-cdk-lib/aws-cloudfront";

// Lambda@Edge must be deployed to us-east-1
// Use a cross-stack reference or EdgeFunction construct
const geoRouter = new cloudfront.experimental.EdgeFunction(this, "GeoRouter", {
  runtime: lambda.Runtime.NODEJS_22_X,
  handler: "lambda-edge-geo-router.handler",
  code: lambda.Code.fromAsset("src/edge"),
  // No VPC, no environment variables — Lambda@Edge constraints
});

const distribution = new cloudfront.Distribution(this, "McpDistribution", {
  defaultBehavior: {
    origin: new origins.HttpOrigin("abc123.lambda-url.us-east-1.on.aws", {
      protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
      readTimeout: Duration.seconds(60),
    }),
    cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
    compress: false,  // critical: never compress SSE streams
    edgeLambdas: [
      {
        functionVersion: geoRouter.currentVersion,
        eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST,
      },
    ],
  },
  domainNames: ["alivemcp.com"],
  certificate: acmCert,
});

Monitoring edge-distributed MCP with AliveMCP

When your MCP endpoint is geo-distributed across regions, standard monitoring from a single location only checks one origin. AliveMCP probes from multiple global locations — each probe independently checks your CloudFront endpoint, which routes to the nearest regional origin. This means a regional Lambda failure (e.g., us-east-1 ECS task crashing) is detected by US probes while EU probes confirm the eu-west-1 origin is healthy — giving you a precise failure scope rather than a binary up/down alert.

Common failure modes

SymptomCauseFix
Lambda@Edge function fails to deploy — InvalidLambdaFunctionAssociationLambda@Edge functions must be in us-east-1; deploying from another region failsUse cloudfront.experimental.EdgeFunction which always creates in us-east-1, or manually specify the region in the CDK stack
SSE stream terminates at 5 seconds from edgeViewer-request or viewer-response Lambda@Edge trigger has a 5-second timeout; streaming MCP sessions exceed thisUse origin-request trigger (30-second timeout) for any logic near the MCP connection; or move auth to CloudFront Functions (sub-1ms) so no timeout applies
Edge function cannot reach Cognito JWKS URILambda@Edge runs outside VPC and makes HTTPS calls; the call succeeds but latency is 50–200ms per cold JWKS fetchCache the JWKS JSON in the Lambda@Edge execution environment's module scope (survives warm invocations); refresh only when a kid is not found in the cache
Geo-routing sends EU users to US origin despite CloudFront Functioncloudfront-viewer-country header not enabled on the distributionEnable CloudFrontViewerCountry in the CloudFront origin request policy; the header is only populated when explicitly included in the request policy
Environment variables not available in Lambda@Edge functionLambda@Edge does not support environment variables — a hard platform constraintEmbed configuration as constants in the function code, or fetch from SSM/Secrets Manager at function initialization time (add latency to first invocation)