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
| Type | How it runs | Init timing | Shutdown signal |
|---|---|---|---|
| Internal | In 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.js | Same SIGTERM as the function process |
| External | A separate process started by Lambda before the function runtime; runs in parallel to the handler | Must register with the Extension API within the 10-second Init budget; otherwise Lambda kills it | Receives 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
| Variable | Default | Description |
|---|---|---|
PARAMETERS_SECRETS_EXTENSION_CACHE_ENABLED | true | Disable caching entirely (not recommended in production) |
PARAMETERS_SECRETS_EXTENSION_CACHE_SIZE | 1000 | Max number of parameter/secret values cached in memory |
PARAMETERS_SECRETS_EXTENSION_MAX_CONNECTIONS | 3 | Max concurrent connections to AWS APIs during refresh |
SECRETS_MANAGER_TTL | 300 (seconds) | How long to cache a Secrets Manager value before re-fetching |
SSM_PARAMETER_STORE_TTL | 300 (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.
| Extension | Typical cold start addition | Notes |
|---|---|---|
| AWS Parameters and Secrets Lambda Extension | 150–300 ms | Initializes local HTTP server + pre-warms cache |
| Datadog Lambda Extension | 200–400 ms | Registers traces API, pre-connects to Datadog intake |
| Custom external extension (Node.js) | 100–250 ms depending on startup work | Minimize 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
| Symptom | Cause | Fix |
|---|---|---|
connect ECONNREFUSED 127.0.0.1:2773 in handler | Parameters and Secrets Extension layer not attached, or extension failed to start | Verify 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 out | Extension did not complete registration within the 10-second Init budget | Register with the Extension API as the very first operation before any other initialization work |
| Stale secret value served after rotation | Cache TTL has not expired; extension serves cached value | Lower SECRETS_MANAGER_TTL or call /secretsmanager/get?secretId=X&versionStage=AWSPENDING during rotation Lambda to bust cache |
| Telemetry events dropped on timeout | Function timeout reached while telemetry buffer not fully flushed | Extension 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 unexplained | An extension layer attached that is not needed | Audit aws lambda list-layer-versions and remove unused extension layers |