Guide · AWS Lambda

MCP Server Lambda Extensions — pre-load secrets, telemetry API, Parameters extension

Lambda Extensions let you inject processes into the Lambda execution environment alongside your function handler — before it starts, during each invocation, and during shutdown. The two most practical uses for MCP servers are: credential pre-loading via the AWS Parameters and Secrets Lambda Extension (retrieves SSM and Secrets Manager values at extension init, caches them in-process, serves them over a local HTTP endpoint — zero SDK calls in your handler at invoke time), and telemetry forwarding via the Telemetry API (receive function logs, platform events, and extension logs as a buffered stream, forward to any observability backend without changing handler code). Extensions also power partner integrations like the Datadog and Sentry Lambda agents.

TL;DR

Add the AWS Parameters and Secrets Lambda Extension as a layer (ARN varies by region). In your handler, call http://localhost:2773/secretsmanager/get?secretId=MySecret with the header X-Aws-Parameters-Secrets-Token: $AWS_SESSION_TOKEN. The extension caches the value and refreshes it every 300 seconds (configurable). Extension add ~150–300 ms to cold start. External extensions must register with the Extension API before the Init phase completes or Lambda kills them.

Internal vs external extensions

TypeHow it runsInit timingShutdown signal
InternalIn the same process as the function handler (e.g., a wrapper script or Node.js --require)Runs before the handler module is loaded if loaded via NODE_OPTIONS=--require /opt/extension-init.jsSame SIGTERM as the function process
ExternalA separate process started by Lambda before the function runtime; runs in parallel to the handlerMust register with the Extension API within the 10-second Init budget; otherwise Lambda kills itReceives SIGTERM + SHUTDOWN event from Extension API; has 2 seconds to clean up

The AWS Parameters and Secrets Lambda Extension is an external extension packaged as a Lambda layer. It starts a local HTTP server on port 2773. Your function handler reads secrets from this local server rather than calling the Secrets Manager or SSM APIs directly — no AWS SDK initialization, no IAM call, no network round-trip to the regional endpoint on every invocation.

AWS Parameters and Secrets Lambda Extension

Add the extension layer by ARN. The ARN is region-specific.

# Look up the latest layer ARN for your region
aws lambda get-layer-version-by-arn \
  --arn arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:12 \
  --query LayerVersionArn --output text

# Or use SSM to find current version
aws ssm get-parameter \
  --name /aws/service/lambda/layer/AWS-Parameters-and-Secrets-Lambda-Extension/us-east-1 \
  --query Parameter.Value --output text
// mcp-handler.ts — read secrets via the extension's local HTTP endpoint
const SECRET_ENDPOINT = "http://localhost:2773/secretsmanager/get";
const SSM_ENDPOINT    = "http://localhost:2773/systemsmanager/parameters/get";

// Cached at module load time — re-fetched by extension on TTL expiry
let dbCredentials: { host: string; password: string } | null = null;

const getDbCredentials = async () => {
  if (dbCredentials) return dbCredentials;

  // X-Aws-Parameters-Secrets-Token must equal the value of AWS_SESSION_TOKEN
  const res = await fetch(
    `${SECRET_ENDPOINT}?secretId=alivemcp/prod/db-credentials`,
    { headers: { "X-Aws-Parameters-Secrets-Token": process.env.AWS_SESSION_TOKEN! } }
  );
  if (!res.ok) throw new Error(`Extension error: ${res.status}`);
  const body = await res.json();
  dbCredentials = JSON.parse(body.SecretString);
  return dbCredentials!;
};

export const handler = async (event: unknown) => {
  const creds = await getDbCredentials();
  // ... use creds.host, creds.password
};

The extension uses the Lambda execution environment's IAM role credentials automatically — no additional configuration needed. Grant secretsmanager:GetSecretValue (and kms:Decrypt if the secret uses a CMK) to the Lambda execution role.

Environment variables for cache tuning

VariableDefaultDescription
PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLEDtrueDisable caching entirely (not recommended in production)
PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE1000Max number of parameter/secret values cached in memory
PARAMETERS_SECRETS_EXTENSION_MAX_CONNECTIONS3Max concurrent connections to AWS APIs during refresh
SECRETS_MANAGER_TTL300 (seconds)How long to cache a Secrets Manager value before re-fetching
SSM_PARAMETER_STORE_TTL300 (seconds)How long to cache an SSM Parameter Store value

For MCP server credentials that rotate (e.g., RDS IAM auth tokens which expire after 15 minutes), set SECRETS_MANAGER_TTL=600 to avoid caching past the token's natural expiry. AliveMCP's probe respects the extension endpoint — if the extension fails to start (missing layer, permission denied), AliveMCP will detect the resulting handler crash within 60 seconds.

Lambda Telemetry API

The Telemetry API lets an external extension subscribe to three event streams and receive them as HTTP POST batches on a local port. It replaces the need to patch the function handler to emit logs to a custom endpoint.

// telemetry-extension.js — Node.js external extension
// File placed at /opt/extensions/telemetry-extension (executable, no extension)
const http = require("http");

const TELEMETRY_PORT = 4243;
const RUNTIME_API = process.env.AWS_LAMBDA_RUNTIME_API;
const EXTENSION_NAME = "telemetry-extension";

// Step 1: Register with the Extension API
async function register() {
  const res = await fetch(`http://${RUNTIME_API}/2020-01-01/extension/register`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Lambda-Extension-Name": EXTENSION_NAME,
    },
    body: JSON.stringify({ events: ["INVOKE", "SHUTDOWN"] }),
  });
  return res.headers.get("lambda-extension-identifier");
}

// Step 2: Subscribe to Telemetry API
async function subscribeToTelemetry(extensionId) {
  await fetch(`http://${RUNTIME_API}/2022-07-01/telemetry`, {
    method: "PUT",
    headers: {
      "Content-Type": "application/json",
      "Lambda-Extension-Identifier": extensionId,
    },
    body: JSON.stringify({
      schemaVersion: "2022-12-13",
      types: ["function", "platform"],  // omit "extension" to reduce noise
      buffering: { maxBytes: 262144, maxItems: 1000, timeoutMs: 25 },
      destination: {
        protocol: "HTTP",
        URI: `http://sandbox.localdomain:${TELEMETRY_PORT}`,
      },
    }),
  });
}

// Local server receives batches of telemetry events
const server = http.createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => { body += chunk; });
  req.on("end", () => {
    const events = JSON.parse(body);
    // Forward to external observability backend (e.g., AliveMCP probe log endpoint)
    forwardToObservability(events);
    res.writeHead(200);
    res.end();
  });
});

server.listen(TELEMETRY_PORT, async () => {
  const extensionId = await register();
  await subscribeToTelemetry(extensionId);
  // Poll Extension API for INVOKE/SHUTDOWN events
  await pollExtensionEvents(extensionId);
});

The Telemetry API buffering parameters (maxBytes, maxItems, timeoutMs) control how often Lambda flushes batches to the extension's HTTP server. For low-latency alerting, set timeoutMs: 25; for throughput efficiency, increase to timeoutMs: 1000. Lambda may lose buffered telemetry events on abrupt function timeouts — the SHUTDOWN event gives the extension 2 seconds to drain any remaining buffer before the environment is frozen.

Extension cold start overhead

Each external extension adds to cold start duration because Lambda must wait for all registered extensions to complete their Init phase before the function handler can run. Practical overhead for the AWS Parameters and Secrets Extension is 150–300 ms per cold start. Multiple extensions add linearly.

ExtensionTypical cold start additionNotes
AWS Parameters and Secrets Lambda Extension150–300 msInitializes local HTTP server + pre-warms cache
Datadog Lambda Extension200–400 msRegisters traces API, pre-connects to Datadog intake
Custom external extension (Node.js)100–250 ms depending on startup workMinimize startup work; avoid network calls during Init

If cold start latency is critical, consider using the Provisioned Concurrency to pre-initialize all extensions. With provisioned concurrency, extension Init cost is paid once at provisioning time, not per cold start.

Common failure modes

SymptomCauseFix
connect ECONNREFUSED 127.0.0.1:2773 in handlerParameters and Secrets Extension layer not attached, or extension failed to startVerify layer ARN is correct for the function's region + architecture (arm64 vs x86_64 have different layer ARNs)
Extension killed during Init with ExtensionError: Init timed outExtension did not complete registration within the 10-second Init budgetRegister with the Extension API as the very first operation before any other initialization work
Stale secret value served after rotationCache TTL has not expired; extension serves cached valueLower SECRETS_MANAGER_TTL or call /secretsmanager/get?secretId=X&versionStage=AWSPENDING during rotation Lambda to bust cache
Telemetry events dropped on timeoutFunction timeout reached while telemetry buffer not fully flushedExtension has 2 seconds after SHUTDOWN event; design for this — do not rely on receiving every log line from a timed-out invocation
Extra 300 ms on every cold start unexplainedAn extension layer attached that is not neededAudit aws lambda list-layer-versions and remove unused extension layers