Guide · AWS Lambda · Lambda Layers
Sharing Lambda Layers Across AWS Accounts for MCP Platforms
Lambda Layer permissions are controlled by resource-based policies — to let another AWS account (or all accounts in your organization) use a layer version, you call add-layer-version-permission to grant lambda:GetLayerVersion to the target principal, and from that moment on, any Lambda function in the permitted account can reference the layer ARN directly without copying the ZIP. This is the standard pattern for MCP platform teams: publish shared infrastructure layers (AWS SDK, authentication middleware, Zod schemas) once in a "platform" account, and let every product account's MCP tools consume them without re-publishing, version-drifting, or bloating cross-account S3 transfers. For building the layer itself see the shared dependencies guide; for versioning and rollback see the versioning guide.
TL;DR
Run aws lambda add-layer-version-permission from the platform account to grant lambda:GetLayerVersion to a specific account ID or to * scoped to your AWS Organization via --principal-org-id. Consumer accounts then reference the layer ARN in their function configuration as-is — no copying, no re-publishing. Layer permissions are per-region; replicate both the publish-layer-version and add-layer-version-permission calls to every region your MCP platform operates in.
Cross-account layer permissions model
Lambda Layers use resource-based policies, similar to S3 bucket policies or Lambda function policies. The policy lives on the specific layer version — when you publish a new version, the new version starts with an empty policy and you must re-grant permissions explicitly. There is no automatic inheritance from previous versions.
Two principal types are supported in layer version policies:
- Single account ID —
Principal: "123456789012"grants access to exactly one AWS account. Use this for individual product teams or external partners. - Organization-scoped wildcard —
Principal: "*"combined withPrincipalOrgID: "o-xxxx"grants access to every account currently in the specified AWS Organization. The org membership check happens at attach time (when a function is updated to reference the layer), not at grant time.
lambda:GetLayerVersion is the only action needed — it grants both the ability to read the layer metadata and to attach the layer to a function. You do not need separate permissions for attaching.
Using Principal: "*" without a PrincipalOrgID makes the layer version publicly accessible to anyone with an AWS account. This is appropriate for open-source shared layers (community runtimes, public toolkits) but must never be used for proprietary or security-sensitive layers such as authentication middleware, internal SDKs, or layers containing secrets or license-gated binaries.
# Grant access to a specific account
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-account-987654321098 \
--action lambda:GetLayerVersion \
--principal 987654321098 \
--region us-east-1
# Grant access to your entire AWS Organization
# Any account in the org can attach the layer — org membership checked at attach time
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-entire-org \
--action lambda:GetLayerVersion \
--principal "*" \
--principal-org-id o-xxxxxxxxxxxx \
--region us-east-1
Both commands return a RevisionId and the resulting policy JSON. The --statement-id is an arbitrary identifier for the permission statement — it must be unique within the layer version's policy and is used to target the statement for removal later via remove-layer-version-permission.
Granting to a specific account
The minimal command to grant a product account access to a layer version in the platform account:
# Run this in the PLATFORM account that owns the layer
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-account-987654321098 \
--action lambda:GetLayerVersion \
--principal 987654321098 \
--region us-east-1
# Output:
# {
# "RevisionId": "e1b2c3d4-...",
# "Statement": "{\"Sid\":\"allow-account-987654321098\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::987654321098:root\"},\"Action\":\"lambda:GetLayerVersion\",\"Resource\":\"arn:aws:lambda:us-east-1:111122223333:layer:mcp-shared-deps:5\"}"
# }
Verify the policy on the layer version after granting:
aws lambda get-layer-version-policy \
--layer-name mcp-shared-deps \
--version-number 5 \
--region us-east-1
# Output:
# {
# "RevisionId": "e1b2c3d4-...",
# "Policy": "{
# \"Version\":\"2012-10-17\",
# \"Statement\":[
# {
# \"Sid\":\"allow-account-987654321098\",
# \"Effect\":\"Allow\",
# \"Principal\":{\"AWS\":\"arn:aws:iam::987654321098:root\"},
# \"Action\":\"lambda:GetLayerVersion\",
# \"Resource\":\"arn:aws:lambda:us-east-1:111122223333:layer:mcp-shared-deps:5\"
# }
# ]
# }"
# }
To grant access to multiple product accounts, add additional statements to the same layer version. Each statement needs a unique --statement-id:
# Grant to a second product account
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-account-444455556666 \
--action lambda:GetLayerVersion \
--principal 444455556666 \
--region us-east-1
# Grant to a third product account
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-account-777788889999 \
--action lambda:GetLayerVersion \
--principal 777788889999 \
--region us-east-1
# List all statements in the policy to confirm
aws lambda get-layer-version-policy \
--layer-name mcp-shared-deps \
--version-number 5 \
--query 'Policy' \
--output text | python3 -m json.tool
There is no hard limit on the number of permission statements per layer version, but the total policy document size cannot exceed 20 KB. For large MCP platforms with dozens of product accounts, the Organization-level grant (covered in the next section) is more practical than individual account grants.
Granting to your entire AWS Organization
For MCP platform teams managing many product accounts, granting at the Organization level is the right default. Any account that is a member of the organization at the time a Lambda function is updated to reference the layer will be permitted — no per-account grant management required.
# Look up your Organization ID from the management account (or any member account)
aws organizations describe-organization \
--query 'Organization.Id' \
--output text
# Output: o-xxxxxxxxxxxx
# Grant the entire organization access to the layer version
# Run this in the PLATFORM account that owns the layer
aws lambda add-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-entire-org \
--action lambda:GetLayerVersion \
--principal "*" \
--principal-org-id o-xxxxxxxxxxxx \
--region us-east-1
The resulting policy statement includes both the wildcard principal and the org condition:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "allow-entire-org",
"Effect": "Allow",
"Principal": "*",
"Action": "lambda:GetLayerVersion",
"Resource": "arn:aws:lambda:us-east-1:111122223333:layer:mcp-shared-deps:5",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxxxx"
}
}
}
]
}
The aws:PrincipalOrgID condition key is evaluated against the organization membership of the IAM principal making the UpdateFunctionConfiguration call in the consumer account — not the account that owns the function or the layer. New accounts added to the organization after the permission was granted can immediately attach the layer; there is no need to re-run the permission command when your org grows.
Using a cross-account layer in a function
From the consumer account, attaching a cross-account layer uses the same update-function-configuration command as attaching a layer from the same account — just use the full layer ARN including the platform account ID:
# Run this in the CONSUMER account that owns the function
# The function itself does not need to be in the same account as the layer
aws lambda update-function-configuration \
--function-name mcp-tool-s3 \
--layers arn:aws:lambda:us-east-1:111122223333:layer:mcp-shared-deps:5 \
--region us-east-1
# To attach multiple layers (up to 5 total), list all ARNs
aws lambda update-function-configuration \
--function-name mcp-tool-s3 \
--layers \
arn:aws:lambda:us-east-1:111122223333:layer:mcp-shared-deps:5 \
arn:aws:lambda:us-east-1:111122223333:layer:mcp-auth-middleware:3 \
--region us-east-1
The consumer account's IAM principal needs lambda:UpdateFunctionConfiguration on its own function and nothing else — the permission check for cross-account layer access is evaluated against the layer's resource-based policy in the platform account, not against the consumer account's IAM policies. The consumer account cannot grant or deny cross-account layer access; that control lives entirely in the platform account.
In CDK deployed in the consumer account, reference the cross-account layer by ARN using LayerVersion.fromLayerVersionArn:
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Duration } from "aws-cdk-lib";
// In the consumer account's CDK stack
// The layer ARN comes from the platform team's release notes or SSM Parameter Store
const PLATFORM_ACCOUNT_ID = "111122223333";
const SHARED_DEPS_LAYER_ARN =
`arn:aws:lambda:us-east-1:${PLATFORM_ACCOUNT_ID}:layer:mcp-shared-deps:5`;
// Import the cross-account layer — no permission check on the consumer side
const sharedDepsLayer = lambda.LayerVersion.fromLayerVersionArn(
this,
"SharedDepsLayer",
SHARED_DEPS_LAYER_ARN
);
// Attach the cross-account layer to an MCP tool function
const mcpToolFn = new lambda.Function(this, "McpToolS3", {
runtime: lambda.Runtime.NODEJS_22_X,
handler: "handler.main",
code: lambda.Code.fromAsset("dist/mcp-tool-s3"),
timeout: Duration.seconds(30),
memorySize: 512,
layers: [sharedDepsLayer],
});
// If the layer ARN is managed via SSM in the platform account,
// you can read it with SSM Parameter Store cross-account access:
// const layerArn = ssm.StringParameter.valueForStringParameter(
// this, "/platform/layers/mcp-shared-deps/latest-arn"
// );
If CDK performs a diff and the layer ARN has changed (new version published by the platform team), CDK will update the function configuration on the next cdk deploy. The layer ZIP is never transferred to the consumer account — Lambda fetches it from the platform account's storage at cold start.
Organization-level rollout pattern
For large MCP platform teams, a structured rollout process prevents version drift and ensures product teams adopt updates on a predictable cadence:
- Platform account publishes the new layer version via
publish-layer-versionand notes the new version number. - Platform account grants organization-wide permission on the new version via
add-layer-version-permission --principal-org-id. - Platform team sends an internal announcement (Slack #platform-releases, email) with the new layer ARN, a changelog summary, and a deprecation date for the old version.
- Each product team updates their CDK stack to reference the new ARN and deploys on their own schedule within the deprecation window.
- After the deprecation date, platform team revokes permission on the old version. Functions still referencing the old version will fail at next cold start.
| Responsibility | Platform account | Consumer account |
|---|---|---|
| Build and package the layer ZIP | Yes — CI/CD pipeline builds, tests, and publishes | No — never touch the layer contents |
| Publish layer version | Yes — publish-layer-version with compatible runtimes set | No |
| Grant cross-account permissions | Yes — add-layer-version-permission for each new version | No |
| Announce new version ARN | Yes — send ARN + changelog to all product teams | No |
| Update function configuration | No | Yes — update CDK stack to reference new layer ARN and deploy |
| Test compatibility | Optional integration test in a staging account | Yes — run function-level tests before promoting to production |
| Revoke old version permissions | Yes — after deprecation window expires | No — but must migrate before revocation date |
IAM permissions required in each account
The platform account's CI/CD role needs permissions to publish layers and manage their policies. The consumer account's CI/CD role only needs permissions on its own functions:
# Platform account CI/CD role — minimal IAM policy
cat > /tmp/platform-layer-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublishLayers",
"Effect": "Allow",
"Action": [
"lambda:PublishLayerVersion",
"lambda:GetLayerVersion",
"lambda:ListLayerVersions"
],
"Resource": "arn:aws:lambda:*:111122223333:layer:mcp-*"
},
{
"Sid": "ManageLayerPermissions",
"Effect": "Allow",
"Action": [
"lambda:AddLayerVersionPermission",
"lambda:RemoveLayerVersionPermission",
"lambda:GetLayerVersionPolicy"
],
"Resource": "arn:aws:lambda:*:111122223333:layer:mcp-*:*"
}
]
}
EOF
aws iam put-role-policy \
--role-name PlatformCiCdRole \
--policy-name LayerPublishAndPermissions \
--policy-document file:///tmp/platform-layer-policy.json
# Consumer account CI/CD role — minimal IAM policy for attaching cross-account layers
cat > /tmp/consumer-function-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "UpdateFunctionLayers",
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionConfiguration",
"lambda:GetFunctionConfiguration"
],
"Resource": "arn:aws:lambda:*:987654321098:function:mcp-*"
},
{
"Sid": "DeployFunctionCode",
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:PublishVersion",
"lambda:CreateAlias",
"lambda:UpdateAlias"
],
"Resource": "arn:aws:lambda:*:987654321098:function:mcp-*"
}
]
}
EOF
# Note: the consumer account does NOT need any permission on the platform account's layer ARN.
# The access check is performed by AWS against the layer's resource-based policy in the
# platform account — the consumer's IAM policy only controls what the CI/CD role can do
# within the consumer account itself.
aws iam put-role-policy \
--role-name ConsumerCiCdRole \
--policy-name FunctionDeployAndLayerAttach \
--policy-document file:///tmp/consumer-function-policy.json
A common mistake is adding lambda:GetLayerVersion to the consumer account's IAM policy for the platform account's layer ARN. This is not required and will not help if the layer's resource-based policy does not include the consumer account — the resource-based policy is the gate, not the identity policy in the consumer account.
Cross-region considerations
Lambda Layer permissions are per-region. A layer version in us-east-1 cannot be directly referenced by a Lambda function in eu-west-1 — the layer ARN includes the region, and Lambda requires the function and its layers to be in the same region.
For MCP platforms operating across multiple regions, replicate both the layer publish and the permission grant to each region. A shell loop handles this cleanly in CI/CD pipelines:
#!/bin/bash
# Build the layer ZIP once; publish to each region
LAYER_NAME="mcp-shared-deps"
ZIP_FILE="layer.zip"
ORG_ID="o-xxxxxxxxxxxx"
for REGION in us-east-1 eu-west-1 ap-southeast-1; do
echo "Publishing layer to $REGION..."
# Publish the layer version in this region
VERSION=$(aws lambda publish-layer-version \
--layer-name "$LAYER_NAME" \
--description "MCP shared dependencies — $(date -u +%Y-%m-%d)" \
--zip-file "fileb://${ZIP_FILE}" \
--compatible-runtimes nodejs22.x nodejs20.x \
--compatible-architectures x86_64 arm64 \
--region "$REGION" \
--query 'Version' \
--output text)
echo " Published version $VERSION in $REGION"
# Grant organization-wide access in this region
aws lambda add-layer-version-permission \
--layer-name "$LAYER_NAME" \
--version-number "$VERSION" \
--statement-id allow-entire-org \
--action lambda:GetLayerVersion \
--principal "*" \
--principal-org-id "$ORG_ID" \
--region "$REGION"
echo " Granted org-wide access in $REGION"
echo " ARN: arn:aws:lambda:${REGION}:$(aws sts get-caller-identity --query Account --output text):layer:${LAYER_NAME}:${VERSION}"
done
echo "Layer replication complete."
An alternative approach for platforms with an existing S3 artifact bucket is to store the layer ZIP in S3 and reference it in --content S3Bucket=...,S3Key=... rather than uploading the file directly in each iteration. This is useful for very large layer ZIPs (approaching the 50 MB direct upload limit) or for audit trails of exactly which ZIP was deployed where.
# Upload layer ZIP to S3 once, publish from S3 in each region
# Note: the S3 bucket must be in the same region as the Lambda publish call
# — use a per-region S3 bucket or copy the object to each region's bucket first
ARTIFACT_BUCKET_PREFIX="mcp-platform-layers" # e.g. mcp-platform-layers-us-east-1
for REGION in us-east-1 eu-west-1 ap-southeast-1; do
S3_BUCKET="${ARTIFACT_BUCKET_PREFIX}-${REGION}"
S3_KEY="mcp-shared-deps/layer-v${NEW_VERSION}.zip"
# Copy to the region's artifact bucket
aws s3 cp layer.zip "s3://${S3_BUCKET}/${S3_KEY}" --region "$REGION"
# Publish using S3 reference
aws lambda publish-layer-version \
--layer-name mcp-shared-deps \
--content "S3Bucket=${S3_BUCKET},S3Key=${S3_KEY}" \
--compatible-runtimes nodejs22.x \
--region "$REGION"
done
Revocation and least-privilege
Removing cross-account access is straightforward — target the specific statement ID you used when granting. However, the timing impact on running functions requires careful coordination:
# Remove access for a specific account from a specific layer version
aws lambda remove-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-account-987654321098 \
--region us-east-1
# Remove org-wide access (e.g., before decommissioning the layer version)
aws lambda remove-layer-version-permission \
--layer-name mcp-shared-deps \
--version-number 5 \
--statement-id allow-entire-org \
--region us-east-1
# Verify the policy is now empty (or has only remaining statements)
aws lambda get-layer-version-policy \
--layer-name mcp-shared-deps \
--version-number 5 \
--region us-east-1
Functions that already have the layer ARN in their configuration and are running in warm execution environments will continue working until their environment is recycled. The permission check happens at cold start — specifically when the Lambda service fetches the layer content during environment initialization. After revocation, the next cold start of any function in the now-revoked account that references the layer will fail with AccessDeniedException: lambda:GetLayerVersion.
Before revoking access, always audit which accounts currently have permission by inspecting the layer version policy:
# Audit current permissions before revoking
aws lambda get-layer-version-policy \
--layer-name mcp-shared-deps \
--version-number 5 \
--query 'Policy' \
--output text | python3 -c "
import json, sys
policy = json.load(sys.stdin)
for stmt in policy['Statement']:
principal = stmt.get('Principal', {})
if isinstance(principal, dict):
account = principal.get('AWS', 'unknown')
else:
account = principal
condition = stmt.get('Condition', {})
org_id = condition.get('StringEquals', {}).get('aws:PrincipalOrgID', 'N/A')
print(f\"Sid: {stmt['Sid']} | Principal: {account} | OrgID: {org_id}\")
"
Best practice for MCP platforms: never revoke the immediately-previous layer version until you have confirmed (via CloudWatch metrics or function configuration audit) that all consumer account functions have migrated to the new version. A safe deprecation window is 2–4 weeks, communicated in advance.
AliveMCP and cross-account MCP server monitoring
In a cross-account MCP platform, a layer permission change in one account can silently break MCP tools in consumer accounts — functions fail at cold start with AccessDeniedException, but warm execution environments continue running, so the breakage is partial and intermittent at first. This is one of the hardest failure patterns to detect with traditional infrastructure monitoring, because CloudWatch alarms on the consumer account's functions may not capture cold-start failures that occur during low-traffic periods.
AliveMCP runs from its own monitoring account and probes each product account's MCP endpoints continuously using Function URLs or API Gateway. Because AliveMCP's probes are external HTTP requests that trigger function invocations — including cold starts — a layer permission revocation that causes cold-start failures will surface as a monitor alert within 60 seconds, regardless of whether the affected function is in us-east-1 or eu-west-1 and regardless of whether any real user traffic hit the function in that window.
Configure one AliveMCP Team workspace with monitors for all product account endpoints. When the platform team revokes a layer version permission as part of a deprecation cycle, AliveMCP will immediately catch any consumer account that missed the migration — before real users encounter the error. The AliveMCP Author tier's response-time history also shows the bimodal cold-start vs warm-invocation distribution, making it easy to confirm that consumer accounts have their functions warm and healthy after a layer version upgrade.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
AccessDeniedException: lambda:GetLayerVersion when attaching a cross-account layer | The consumer account (or the IAM principal making the UpdateFunctionConfiguration call) is not listed in the layer version's resource-based policy in the platform account | In the platform account, run add-layer-version-permission to grant lambda:GetLayerVersion to the consumer account ID or to the org ID; verify with get-layer-version-policy |
Layer works in us-east-1 but not in eu-west-1 | Layer was published only in us-east-1; cross-account permission was granted only in the source region; Lambda requires the function and layer to be in the same region | Replicate publish-layer-version and add-layer-version-permission to each required region using the shell loop pattern; Lambda does not support cross-region layer references |
| Functions begin failing after the security team revoked a layer permission | remove-layer-version-permission was run; warm execution environments continue working until recycled; subsequent cold starts fail with AccessDeniedException during layer fetch | Re-add permission immediately to restore function availability; then coordinate a proper migration window where consumer teams update to the new layer version before the next revocation |
InvalidParameterValueException: Layer arn references a different account when attaching the layer | The layer ARN in the function configuration uses the platform account ID, but the consumer account's IAM role does not have permission to call the Lambda API with a cross-account resource — or the layer's resource-based policy has not been granted for the consumer account | Verify the layer ARN is correct (platform account ID, correct region, correct version number); confirm the layer version policy includes the consumer account ID; the error message can sometimes appear when the policy is missing even if the IAM principal format is correct |
| Org-wide permission not working for a specific newly-created account | New accounts added to the AWS Organization are eligible for org-scoped layer permissions at the time they try to attach the layer — org membership is checked at attach time, not grant time; if the account is in the org, it should have access; if it is not working, the account may be in a suspended state or may not have been fully activated | Verify the account is fully active and in the correct org with aws organizations describe-account --account-id NEW_ACCOUNT_ID; confirm Status is ACTIVE and JoinedMethod is CREATED or INVITED; org-level permissions work for any fully-active account in the org at attach time |