Deploying Zscaler MCP Server to Amazon Bedrock AgentCore
This guide provides complete instructions for deploying the Zscaler MCP Server to Amazon Bedrock AgentCore with two deployment methods and two credential management approaches.
Topology at a glance. The shipped, production-tested topology is AgentCore Runtime alone, called via
bedrock-agentcore:InvokeAgentRuntime(boto3, theagentcoreCLI, the Sandbox playground, etc.). An optional AgentCore Gateway can be placed in front of the runtime for downstream agent platforms like Amazon Quick Suite β that path is currently experimental: end-to-end Runtime invocation through a Gateway works, buttools/listpropagation through the Gateway target is inconsistent in current testing. See Gateway integration (experimental) for the full status.
π Table of Contentsβ
- Deployment Methods Overview
- Credential Management Approaches
- Prerequisites
- Method 1: CloudFormation Deployment (Recommended)
- Method 2: Manual AWS CLI Deployment
- Gateway integration (experimental)
- Testing Your Deployment
- Configuring OAuth (Auth0) for AgentCore Identity
- Troubleshooting
- Environment variable reference
Deployment Methods Overviewβ
Choose the deployment method that best fits your needs:
| Method | Best For | Complexity | Security | Time |
|---|---|---|---|---|
| CloudFormation | Production, automation | ββ Moderate | βββββ Excellent | ~5 min |
| Manual AWS CLI | Testing, learning | β Simple | ββββ Good | ~10 min |
Credential Management Approachesβ
The Zscaler MCP Server supports two approaches for managing Zscaler API credentials:
Approach A: Container-Based Secrets Manager Integration (Recommended) πβ
How it works:
- Credentials stored in AWS Secrets Manager (encrypted with KMS)
- Container retrieves credentials at startup using boto3
- Zero credentials in infrastructure configuration
Security Benefits:
- β Credentials encrypted at rest (KMS)
- β Credentials encrypted in transit (TLS)
- β Credentials never visible in AgentCore config
- β Credentials never in deployment scripts
- β CloudTrail audit logging
- β Secret rotation support
When to use:
- β Production deployments
- β Compliance requirements (SOC2, ISO27001, PCI-DSS)
- β Multiple team members
- β Credential rotation needed
Approach B: Direct Environment Variablesβ
How it works:
- Credentials passed directly in
--environment-variablesparameter - No Secrets Manager required
Security Considerations:
- β οΈ Credentials visible in AgentCore configuration
- β οΈ Credentials in deployment scripts
- β οΈ Credentials in command history
When to use:
- β Development/testing
- β Quick proof-of-concept
- β Internal environments only
Prerequisitesβ
Required for All Deploymentsβ
-
AWS CLI configured with appropriate credentials
-
Zscaler API Credentials:
- Client ID
- Client Secret
- Vanity Domain
- Customer ID
- Cloud (optional, only for Beta tenant:
beta)
-
Docker Image pushed to Amazon ECR:
# Build and push the imagedocker build --platform linux/arm64 -t zscaler/zscaler-mcp-server .docker tag zscaler/zscaler-mcp-server:latest \YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/zscaler/zscaler-mcp-server:latestdocker push YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/zscaler/zscaler-mcp-server:latest
Additional for Secrets Manager Approachβ
- AWS Secrets Manager access
- IAM permissions to create/read secrets
Method 1: CloudFormation Deployment (Recommended)β
π One-Click Deployβ
Click the button below to launch the stack in the AWS Console:
What Gets Deployedβ
- β IAM Execution Role - With ECR, CloudWatch, Secrets Manager, and Bedrock permissions
- β Secrets Manager Secret - (Optional) Encrypted with AWS KMS
- β Lambda Custom Resource - Automates AgentCore deployment
- β Bedrock AgentCore Runtime - Container-based secret retrieval
Deployment Stepsβ
Step 1: Click Launch Stack Buttonβ
The button will open the AWS CloudFormation console with the template pre-loaded.
Step 2: Specify Stack Detailsβ
Stack Name: zscaler-mcp-server (or your preferred name)
Parameters:
Agent Runtime Configurationβ
- AgentRuntimeName:
zscalermcp(alphanumeric and underscores only, no hyphens) - AgentDescription:
Zscaler MCP Server with Secrets Manager Integration - ECRImageUri:
YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/zscaler/zscaler-mcp-server:latest- β οΈ Important: Replace
YOUR_ACCOUNT_IDwith your actual AWS account ID
- β οΈ Important: Replace
Credential Managementβ
Choose one of two options:
Option A: Create New Secret (First-Time Deployment)β
- CredentialSource: Select
CreateNew - Fill in your Zscaler credentials:
- ZscalerClientId: Your OAuth Client ID
- ZscalerClientSecret: Your OAuth Client Secret
- ZscalerVanityDomain: Your vanity domain (e.g.,
example) - ZscalerCustomerId: Your Customer ID
- ZscalerCloud: Leave empty for production, or select
betafor Beta tenant
Option B: Use Existing Secret (Recommended if Secret Already Exists)β
- CredentialSource: Select
UseExisting - ExistingSecretName:
zscaler/mcp/credentials(or your secret name) - Leave credential fields empty (they're not used)
Security Configurationβ
- EnableWriteTools:
false(recommended) ortrueto enable write operations - WriteToolsAllowlist: Leave empty, or specify tools (e.g.,
zpa_*,zia_*)
Step 3: Configure Stack Optionsβ
- Tags (optional): Add tags for cost tracking
- Permissions: CloudFormation will create IAM roles automatically
- Stack failure options: Use default settings
Step 4: Review and Createβ
- Review all parameters
- β Check the box: "I acknowledge that AWS CloudFormation might create IAM resources with custom names"
- Click "Submit"
Step 5: Monitor Deploymentβ
Wait 3-5 minutes for the stack to complete. The status will change from:
CREATE_IN_PROGRESSβCREATE_COMPLETEβ
Step 6: Get Outputsβ
Once complete, go to the Outputs tab to see:
- AgentRuntimeId: Your runtime ID
- AgentRuntimeArn: Your runtime ARN
- SecretName: The Secrets Manager secret name
- SecurityNote: Confirmation that credentials are not exposed
- VerificationCommand: Command to check runtime status
- LogsCommand: Command to view container logs
Verify Deploymentβ
# Get runtime ID from CloudFormation outputs
RUNTIME_ID="zscalermcp-AbCdEf1234" # Replace with your runtime ID
# Check runtime status
aws bedrock-agentcore-control list-agent-runtimes \
--region us-east-1 \
--query "agentRuntimes[?agentRuntimeId=='$RUNTIME_ID']"
# View container logs
aws logs tail /aws/bedrock-agentcore/runtimes/${RUNTIME_ID}-DEFAULT \
--region us-east-1 \
--follow
Look for these log entries:
INFO:zscaler_mcp.config:Successfully retrieved and parsed secret from Secrets Manager
INFO:zscaler_mcp.config:Set environment variable: ZSCALER_CLIENT_ID
INFO:zscaler_mcp.config:Zscaler credentials injected into environment variables
INFO:zscaler_mcp.server:Initializing Zscaler MCP Server
β Success! The container retrieved credentials from Secrets Manager.
Verify Securityβ
# Verify credentials are NOT in AgentCore config
aws bedrock-agentcore-control get-agent-runtime \
--region us-east-1 \
--agent-runtime-id $RUNTIME_ID \
--query 'environmentVariables'
Expected output:
{
"ZSCALER_SECRET_NAME": "zscalermcp-credentials",
"AWS_REGION": "us-east-1",
"ZSCALER_MCP_WRITE_ENABLED": "false",
"ZSCALER_MCP_WRITE_TOOLS": ""
}
β
Notice: No ZSCALER_CLIENT_ID or ZSCALER_CLIENT_SECRET!
Method 2: Manual AWS CLI Deploymentβ
For users who prefer manual deployment or need more control over the process.
Choose Your Credential Approachβ
Approach A: With Secrets Manager (Recommended for Production)β
Step 1: Create Secrets Manager Secretβ
# Create encrypted secret with KMS
aws secretsmanager create-secret \
--name zscaler/mcp/credentials \
--description "Zscaler API credentials for MCP Server" \
--kms-key-id alias/aws/secretsmanager \
--secret-string '{
"ZSCALER_CLIENT_ID": "your-client-id",
"ZSCALER_CLIENT_SECRET": "your-client-secret",
"ZSCALER_VANITY_DOMAIN": "your-vanity-domain",
"ZSCALER_CUSTOMER_ID": "your-customer-id",
"ZSCALER_CLOUD": "",
"ZSCALER_MCP_WRITE_ENABLED": "false",
"ZSCALER_MCP_WRITE_TOOLS": ""
}' \
--region us-east-1
Verify secret creation:
aws secretsmanager describe-secret \
--secret-id zscaler/mcp/credentials \
--region us-east-1 \
--query '{Name:Name,ARN:ARN,KmsKeyId:KmsKeyId}'
Expected output:
{
"Name": "zscaler/mcp/credentials",
"ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:zscaler/mcp/credentials-AbCdEf",
"KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/..."
}
β Encryption confirmed! The secret is encrypted with AWS KMS.
Step 2: Create IAM Execution Roleβ
Create a role with Secrets Manager permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRImageAccess",
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:us-east-1:YOUR_ACCOUNT_ID:repository/zscaler/zscaler-mcp-server"
},
{
"Sid": "ECRTokenAccess",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:YOUR_ACCOUNT_ID:secret:zscaler/mcp/credentials-*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:us-east-1:YOUR_ACCOUNT_ID:log-group:/aws/bedrock-agentcore/runtimes/*"
},
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "*"
}
]
}
Create the role:
# Create trust policy
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "bedrock-agentcore.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# Create role
aws iam create-role \
--role-name BedrockAgentExecutionRole \
--assume-role-policy-document file://trust-policy.json
# Attach permissions policy
aws iam put-role-policy \
--role-name BedrockAgentExecutionRole \
--policy-name BedrockAgentExecutionPolicy \
--policy-document file://execution-policy.json
Step 3: Deploy AgentCore Runtimeβ
aws bedrock-agentcore-control create-agent-runtime \
--region us-east-1 \
--agent-runtime-name zscalermcp \
--description "Zscaler MCP Server with Secrets Manager" \
--agent-runtime-artifact '{
"containerConfiguration": {
"containerUri": "YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/zscaler/zscaler-mcp-server:latest"
}
}' \
--role-arn "arn:aws:iam::YOUR_ACCOUNT_ID:role/BedrockAgentExecutionRole" \
--network-configuration '{"networkMode": "PUBLIC"}' \
--protocol-configuration '{"serverProtocol": "MCP"}' \
--environment-variables '{
"ZSCALER_SECRET_NAME": "zscaler/mcp/credentials",
"AWS_REGION": "us-east-1",
"ZSCALER_MCP_WRITE_ENABLED": "false",
"ZSCALER_MCP_WRITE_TOOLS": ""
}'
Key Points:
- β
Only
ZSCALER_SECRET_NAMEis passed (not actual credentials) - β Container retrieves credentials at startup
- β Credentials never visible in AgentCore config
Step 4: Verify Secrets Manager Integrationβ
# Check container logs
RUNTIME_ID="zscalermcp-AbCdEf1234" # Get from create-agent-runtime output
aws logs tail /aws/bedrock-agentcore/runtimes/${RUNTIME_ID}-DEFAULT \
--region us-east-1 \
--since 5m | grep -E "(config|secret|Secret)"
Expected log entries:
INFO:zscaler_mcp.config:Attempting to fetch secret: zscaler/mcp/credentials from region: us-east-1
INFO:zscaler_mcp.config:Successfully retrieved and parsed secret from Secrets Manager
INFO:zscaler_mcp.config:Set environment variable: ZSCALER_CLIENT_ID
INFO:zscaler_mcp.config:Set environment variable: ZSCALER_CLIENT_SECRET
INFO:zscaler_mcp.config:Zscaler credentials injected into environment variables
β Success! Container-based Secrets Manager integration is working.
Approach B: With Direct Environment Variablesβ
Step 1: Create IAM Execution Roleβ
Create a role without Secrets Manager permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRImageAccess",
"Effect": "Allow",
"Action": [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:us-east-1:YOUR_ACCOUNT_ID:repository/zscaler/zscaler-mcp-server"
},
{
"Sid": "ECRTokenAccess",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:YOUR_ACCOUNT_ID:log-group:/aws/bedrock-agentcore/runtimes/*"
},
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "*"
}
]
}
Notice: No secretsmanager:GetSecretValue permission needed.
Step 2: Deploy AgentCore Runtimeβ
aws bedrock-agentcore-control create-agent-runtime \
--region us-east-1 \
--agent-runtime-name zscalermcp \
--description "Zscaler MCP Server" \
--agent-runtime-artifact '{
"containerConfiguration": {
"containerUri": "YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/zscaler/zscaler-mcp-server:latest"
}
}' \
--role-arn "arn:aws:iam::YOUR_ACCOUNT_ID:role/BedrockAgentExecutionRole" \
--network-configuration '{"networkMode": "PUBLIC"}' \
--protocol-configuration '{"serverProtocol": "MCP"}' \
--environment-variables '{
"ZSCALER_CLIENT_ID": "your-client-id",
"ZSCALER_CLIENT_SECRET": "your-client-secret",
"ZSCALER_VANITY_DOMAIN": "your-vanity-domain",
"ZSCALER_CUSTOMER_ID": "your-customer-id",
"ZSCALER_CLOUD": "",
"ZSCALER_MCP_WRITE_ENABLED": "false",
"ZSCALER_MCP_WRITE_TOOLS": ""
}'
Key Points:
- β οΈ Credentials passed directly in the command
- β οΈ Credentials visible in AgentCore configuration
- β οΈ Credentials in shell history
Step 3: Verify Direct Environment Variable Approachβ
# Check container logs
aws logs tail /aws/bedrock-agentcore/runtimes/${RUNTIME_ID}-DEFAULT \
--region us-east-1 \
--since 5m | grep "ZSCALER_SECRET_NAME"
Expected log entry:
INFO:zscaler_mcp.config:ZSCALER_SECRET_NAME not set - using credentials from environment variables directly
β Success! Container is using direct environment variables.
Comparison: Secrets Manager vs Direct Environment Variablesβ
Security Comparisonβ
| Aspect | Secrets Manager | Direct Env Vars |
|---|---|---|
| Credentials in AgentCore config | β No - only secret name | β Yes - plaintext |
| Credentials in deployment scripts | β No - only secret name | β Yes - plaintext |
| Credentials in command history | β No - only secret name | β Yes - plaintext |
| Credentials in CloudFormation | β No - only secret name | β Yes - plaintext |
| Encryption at rest | β Yes - KMS encrypted | β οΈ AWS encrypts config |
| Encryption in transit | β Yes - TLS | β Yes - TLS |
| Audit logging | β Yes - CloudTrail | β οΈ Limited |
| Secret rotation | β Yes - automated | β Manual |
| Compliance | β SOC2, ISO27001, PCI-DSS | β οΈ May not meet requirements |
Operational Comparisonβ
| Aspect | Secrets Manager | Direct Env Vars |
|---|---|---|
| Setup complexity | βββ Moderate | β Simple |
| Deployment time | ~5 minutes | ~3 minutes |
| IAM permissions | More (Secrets Manager) | Fewer |
| Cost | ~$0.40/month | Free |
| Credential rotation | Update secret + restart | Redeploy runtime |
| Production ready | β Yes | β οΈ Not recommended |
Decision Matrixβ
Use Secrets Manager if:
- β Production environment
- β Security/compliance requirements
- β Multiple team members
- β Credential rotation needed
- β Audit logging required
Use Direct Environment Variables if:
- β Development/testing only
- β Quick proof-of-concept
- β Internal use only
- β No compliance requirements
- β Single user environment
Authentication, custom headers, and AgentCoreβ
InvokeAgentRuntime (the AgentCore data-plane API) does not forward arbitrary HTTP headers to the container by default. Only headers that have been added to the runtime's requestHeaderAllowlist are propagated, with a hard limit of 20 entries and 4 KB per value. This affects every McpAuthMode the Zscaler MCP Server supports.
The deployment configures the runtime automatically based on McpAuthMode:
McpAuthMode | Allowlist set by the deployment | customJwtAuthorizer configured? | Sandbox playground works without extra effort? |
|---|---|---|---|
none | (nothing) | no | yes β no auth |
api-key | X-Api-Key | no | yes β see container-side fallback below |
zscaler | X-Zscaler-Client-ID, X-Zscaler-Client-Secret | no | yes β see container-side fallback below |
jwt | Authorization | yes β provisioned with discovery URL + audience from JwtIssuer / JwtAudience (override with JwtDiscoveryUrl / JwtAllowedClients) | yes β via AgentCore Identity Sign-In button (see "Configuring OAuth" below) |
Container-side credential fallback (api-key and zscaler modes)β
The AWS Console Sandbox playground only accepts a JSON payload β there is no UI to attach custom headers. To keep the playground usable, the AWS variant of the MCP server also accepts credentials from its own environment in api-key and zscaler modes. The container reads:
ZSCALER_MCP_AUTH_API_KEY(forapi-keymode)ZSCALER_CLIENT_IDandZSCALER_CLIENT_SECRET(forzscalermode)
These are already populated from Secrets Manager at container startup. When an incoming request has neither the allowlisted header nor an Authorization value, the auth provider falls back to the env vars. The startup log notes when this fallback is active.
Note on the auth boundary. When the fallback is in play, every request that reaches your container passes the MCP-layer auth check because the container is comparing its own credentials against itself. The real per-caller authorization boundary in this configuration is AWS IAM β the caller must have
bedrock-agentcore:InvokeAgentRuntimeon the runtime ARN. This is consistent with the standard IAM-controlled posture for any AgentCore Runtime, and is the recommended pattern when fronting the runtime with AgentCore Gateway or Amazon Quick Suite (both authenticate the user upstream and then call AgentCore with their own IAM identity).
Invoking the runtime with allowlisted headersβ
When you need real per-caller authentication (Gateway, Quick Suite, custom boto3 clients, automation), attach the headers explicitly. boto3 exposes botocore events for this:
import json, boto3
RUNTIME_ARN = "arn:aws:bedrock-agentcore:us-east-1:111122223333:runtime/zscalermcp-XXXXXX"
client = boto3.client("bedrock-agentcore", region_name="us-east-1")
events = client.meta.events
EVENT = "before-sign.bedrock-agentcore.InvokeAgentRuntime"
# Pick one (matches your McpAuthMode):
# --- api-key ---
def attach_api_key(request, **_):
request.headers.add_header("X-Api-Key", "sk-...your-key...")
handler = events.register_first(EVENT, attach_api_key)
# --- zscaler ---
# def attach_zscaler(request, **_):
# request.headers.add_header("X-Zscaler-Client-ID", "<client_id>")
# request.headers.add_header("X-Zscaler-Client-Secret", "<client_secret>")
# handler = events.register_first(EVENT, attach_zscaler)
# --- jwt ---
# token = ... # obtained from your IdP / AgentCore Identity Sign-In
# def attach_bearer(request, **_):
# request.headers.add_header("Authorization", f"Bearer {token}")
# handler = events.register_first(EVENT, attach_bearer)
payload = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}).encode()
response = client.invoke_agent_runtime(agentRuntimeArn=RUNTIME_ARN, payload=payload)
for chunk in response.get("response", []):
print(chunk.decode("utf-8"), end="")
events.unregister(EVENT, handler)
The agentcore CLI exposes the same capability via repeated -H flags:
agentcore invoke '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
-H "X-Api-Key: sk-..."
Gateway integration (experimental)β
Status: experimental. End-to-end Runtime invocation through the Gateway has been verified, but tool discovery (
tools/listpropagation through the Gateway target) is inconsistent in current testing β downstream agent platforms see only a subset of the MCP server's tools, or none at all. The Gateway code paths are kept in this integration so you can evaluate the topology, but production deployments should use the Direct Runtime path until tool discovery is fully validated.What has been verified:
- Gateway creation succeeds (both
createandattachmodes)- Inbound
CUSTOM_JWTauthorizer +OAUTHoutbound credential provider wiring is correct- The runtime itself, called directly via
bedrock-agentcore:InvokeAgentRuntime, returns the full tool list β so the issue is on the Gateway target / discovery side, not the MCP serverThe rest of this section walks through the options for evaluation.
The Zscaler MCP Server runs perfectly well on AgentCore Runtime on its own β downstream callers reach it via bedrock-agentcore:InvokeAgentRuntime (boto3, the agentcore CLI, or the Console Sandbox). The AgentCore Gateway is an opt-in fronting layer that gives you a single OAuth-authenticated MCP URL that downstream agent platforms (Amazon Quick Suite, Bedrock Agents, custom MCP clients) can call without going through InvokeAgentRuntime.
The deploy script supports both an owned-Gateway and a bring-your-own-Gateway pattern. Pick whichever matches your operating model.
How the deploy script asks about the Gatewayβ
When you run python aws_mcp_operations.py deploy, the script reaches Step 7 β Architecture and always asks which topology you want:
[1] Direct runtime (no Gateway) [recommended]
[2] Provision a new AgentCore Gateway [experimental]
[3] Attach to an existing AgentCore Gateway [experimental]
Choice [1-3] (default 1):
- Pick 1 for Direct Runtime. The Gateway nested stack is skipped entirely and you don't need any of the
GATEWAY_*variables below β even if they happen to be set in.env, the script writesEnableAgentCoreGateway=falseinto the CloudFormation parameters and ignores them. - Pick 2 or 3 to enable the experimental Gateway. The script reads
GATEWAY_MODEand the rest of theGATEWAY_*variables from.envfor the CloudFormation parameters.
ENABLE_AGENTCORE_GATEWAY in .env is purely a default for the prompt: if it's true, the prompt defaults to option [2] (or [3] when GATEWAY_MODE=attach); if it's false or unset, the prompt defaults to [1]. You can always override interactively.
For non-interactive deploys (--non-interactive flag, CI/CD), the .env value is the only signal β the script silently picks based on ENABLE_AGENTCORE_GATEWAY and GATEWAY_MODE.
When to enable the Gatewayβ
| Use case | Need a Gateway? |
|---|---|
| Calling the runtime from your own boto3 / Lambda / EC2 / CodeBuild | No |
| Testing in the AgentCore Sandbox playground | No |
| Connecting the runtime to Amazon Quick Suite | Yes (Quick Suite expects an MCP URL, not InvokeAgentRuntime) |
| Connecting to Bedrock Agents via an MCP tool | Either path works; Gateway gives you centralized observability + OAuth brokerage |
| Demonstrating end-to-end OAuth 2.0 Authorization Code (3LO) flow | Yes (the Gateway is the OAuth surface) |
Mode 1 β Create a new Gateway (we own the lifecycle)β
Pick option [2] at the architecture prompt (or set GATEWAY_MODE=create for non-interactive deploys). The deploy script provisions:
- A new
AgentCore Gatewaynamed<resource-prefix>-gatewaywithprotocolType=MCPand aCUSTOM_JWTinbound authorizer - A dedicated
GatewayServiceRolewith the AWS-documented permission set for MCP-server targets - An
mcpServertarget pointing at the runtime's MCP URL, withJWT_PASSTHROUGHoutbound auth (default) or an OAuth credential provider you supply
destroy cascades: target β gateway β service role.
Required .env entries for Mode 1 (in addition to the baseline Zscaler credentials):
# Optional β sets the default for the architecture prompt to [2]. For
# interactive deploys, you can omit this and just choose [2] at the prompt.
ENABLE_AGENTCORE_GATEWAY=true
GATEWAY_MODE=create
# Required once Mode 1 is selected.
GATEWAY_INBOUND_DISCOVERY_URL=https://my-tenant.us.auth0.com/.well-known/openid-configuration
GATEWAY_OAUTH_CLIENT_ID=<client-id-quick-suite-uses>
GATEWAY_TOOL_SCHEMA_FILE=tool-schema.json
Mode 2 β Attach to an existing Gateway (you own the lifecycle)β
Pick option [3] at the architecture prompt (or set GATEWAY_MODE=attach for non-interactive deploys) and supply EXISTING_GATEWAY_ID. The deploy script:
- Looks up the existing Gateway via
bedrock-agentcore-control:GetGateway - Registers our runtime as one of its
mcpServertargets - Wires JWT passthrough or your supplied OAuth credential provider on the target
destroy removes only the target we created β never the Gateway itself. This is the right mode if you already operate a shared Gateway for multiple MCP servers, want to keep its IAM ownership outside this stack, or are demonstrating Zscaler-on-AgentCore on a Gateway provisioned by another team.
Required .env entries for Mode 2 (in addition to the baseline Zscaler credentials):
# Optional β sets the default for the architecture prompt to [3]. For
# interactive deploys, you can omit these and just choose [3] at the prompt.
ENABLE_AGENTCORE_GATEWAY=true
GATEWAY_MODE=attach
# Required once Mode 2 is selected.
EXISTING_GATEWAY_ID=<gateway-id-from-bedrock-agentcore-control:ListGateways>
GATEWAY_TOOL_SCHEMA_FILE=tool-schema.json
Inbound JWT claim contract (must read for non-Cognito IdPs)β
AgentCore Gateway's customJWTAuthorizer.allowedClients matcher is not RFC-7519 compliant: it reads the client_id claim specifically, which is a Cognito-native convention. Every other major OIDC IdP uses the RFC-standard azp claim ("authorized party"). When allowedClients can't find a client_id to compare against, the Gateway returns 403 insufficient_scope β a misleading error name that sent the entire industry chasing scope-config rabbit holes when the actual problem was the client-ID claim.
The deploy script works around this transparently by detecting your IdP from the discovery URL and either:
- Emitting
allowedClientswhen the IdP is Cognito (claim name =client_id), or - Emitting a
customClaimsmatcher on the right claim name for every other IdP (claim name =azpfor Auth0/Entra/Keycloak/Google,cidfor Okta).
| IdP | Claim name | Authorizer shape | Override env var if auto-detect picks wrong |
|---|---|---|---|
| Amazon Cognito | client_id | allowedClients | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=client_id |
| Auth0 | azp | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=azp |
| Okta | cid | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=cid |
| Microsoft Entra ID (v2.0) | azp | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=azp |
| Microsoft Entra ID (v1.0) | appid | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=appid |
| Keycloak | azp | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=azp |
| Google Identity | azp | customClaims | GATEWAY_INBOUND_CLIENT_CLAIM_NAME=azp |
To verify the matcher is going to work before deploying, decode a token your IdP issues for the M2M flow you'll use, and confirm the claim above is present and equal to your OAuth client ID. Auth0 example:
TOKEN=$(curl -sS -X POST "https://<tenant>.auth0.com/oauth/token" \
-H "content-type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=<CLIENT_ID>" -d "client_secret=<CLIENT_SECRET>" \
-d "audience=<API_IDENTIFIER>" | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])')
python3 -c "import sys,base64,json; p='$TOKEN'.split('.')[1]; p+='='*((4-len(p)%4)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)), indent=2))"
You should see "azp": "<CLIENT_ID>" in the output. If your IdP uses a non-standard claim name, set GATEWAY_INBOUND_CLIENT_CLAIM_NAME to that name.
Setting up the Gateway IdP (Auth0 example)β
The Gateway's CUSTOM_JWT authorizer needs an OIDC IdP to validate downstream agent tokens against. AWS does not provide one for you β you bring your own (Auth0, Cognito, Okta, Entra ID, Keycloak, anything OIDC-compliant). This subsection walks through the Auth0 setup as a concrete example; the same shape applies to any IdP.
You'll create two objects in Auth0: an API (the protected resource β the Gateway) and an Application (the client that calls the Gateway).
1. Create an Auth0 APIβ
In Auth0 dashboard β Applications β APIs β Create API:
| Auth0 field | Value | Used as |
|---|---|---|
| Name | Zscaler MCP Gateway (label, anything works) | β |
| Identifier | urn:zscaler-mcp:gateway or https://zscaler-mcp.example.com/api | Becomes aud claim in tokens. This is what you paste into GATEWAY_INBOUND_ALLOWED_AUDIENCE. A URI string β doesn't need to be reachable. |
| Signing algorithm | RS256 | Required (HS256 won't work β Gateway needs JWKS). |
2. Create an Auth0 Applicationβ
In Auth0 dashboard β Applications β Applications β Create Application:
| Auth0 field | Value | Used as |
|---|---|---|
| Application type | Machine-to-Machine (simplest for testing / demos). For real Quick Suite integration, use Single Page or Native later. | β |
| Authorize for the API above | Yes β select the API you just created | Without this, tokens won't have your audience. |
| Grant Types | client_credentials (auto-set for M2M) | Lets you mint a token via POST /oauth/token for testing. |
After saving, grab the Client ID from the application's Settings tab. This goes into GATEWAY_OAUTH_CLIENT_ID. The Client Secret stays in Auth0 β you only need it locally to mint test tokens.
3. Values to paste into .envβ
| Auth0 source | Goes into | What the Gateway does with it |
|---|---|---|
Tenant domain β https://<tenant>.auth0.com/ | GATEWAY_INBOUND_ISSUER (or paste the discovery URL into GATEWAY_INBOUND_DISCOVERY_URL) | Fetches JWKS to verify token signature |
| API Identifier from step 1 | GATEWAY_INBOUND_ALLOWED_AUDIENCE | Validates aud claim |
| Application Client ID from step 2 | GATEWAY_OAUTH_CLIENT_ID | Matched against the azp claim for Auth0 (the deploy script auto-detects this; see the table above) |
| Application Client Secret from step 2 | GATEWAY_OAUTH_CLIENT_SECRET | Used by the Gateway to mint runtime tokens via client_credentials |
You can leave GATEWAY_INBOUND_CLIENT_CLAIM_NAME unset β auto-detection from the Auth0 issuer URL sets it to azp for you.
Auth0 tenant setting (mandatory, one-time): Auth0 Dashboard β Settings β General β API Authorization Settings β Default Audience = the same value you used for GATEWAY_INBOUND_ALLOWED_AUDIENCE (e.g. urn:zscaler-mcp:gateway). Without this, Auth0 rejects client_credentials requests with "No audience parameter was provided, and no default audience has been configured" because AgentCore Identity cannot pass audience per request.
You only need either GATEWAY_INBOUND_ISSUER or GATEWAY_INBOUND_DISCOVERY_URL β the script derives one from the other.
4. Smoke-test after the deployβ
# Mint a token via Auth0 (M2M flow)
TOKEN=$(curl -s --request POST \
--url "https://<your-tenant>.us.auth0.com/oauth/token" \
--header "content-type: application/json" \
--data '{
"client_id":"<APP_CLIENT_ID>",
"client_secret":"<APP_CLIENT_SECRET>",
"audience":"<API_IDENTIFIER>",
"grant_type":"client_credentials"
}' | jq -r .access_token)
# Call the Gateway with that token
curl -s -X POST "$GATEWAY_MCP_URL" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
If the response lists Zscaler tools, the chain works end-to-end (Auth0 β Gateway β Runtime β MCP).
Outbound credential provider (Gateway β Runtime)β
The Gateway has to authenticate itself to the runtime when forwarding a tool call. AWS rejects JWT_PASSTHROUGH for mcpServer targets, so the only supported path is OAUTH via an AgentCore Identity OAuth2 credential provider. The deploy script handles this in one of two ways:
-
Auto-provision (default). Leave
GATEWAY_OAUTH_PROVIDER_ARNempty. The deploy Lambda callsbedrock-agentcore-control:create_oauth2_credential_providerfor you, using the same inbound IdP details (discovery URL +GATEWAY_OAUTH_CLIENT_ID) plus the secret you provide inGATEWAY_OAUTH_CLIENT_SECRET. The provider is created with grant typeCLIENT_CREDENTIALSand torn down on stack delete. -
Bring your own. Set
GATEWAY_OAUTH_PROVIDER_ARNto an existing provider ARN (e.g. one you created in the console forMyOrgAuth0Provider). The stack skips auto-provisioning entirely, ignoresGATEWAY_OAUTH_CLIENT_SECRET, and binds the target to that provider. Use this to share one provider across deployments or to enforce curated scopes.
IdP must accept a vanilla client_credentials requestβ
AgentCore Identity is intentionally minimal: it sends only the standard OAuth 2.0 client_credentials payload to your IdP's /token endpoint β
POST /token HTTP/1.1
content-type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=...&client_secret=...&scope=...
It does not expose any way to add audience, resource, or other IdP-specific parameters. Any query string you put on the configured tokenEndpoint URL is dropped before the POST. This is a hard limit of the AgentCore Identity API surface, not something the deploy script controls.
| IdP | What works out of the box | What you need to configure on the IdP side |
|---|---|---|
| Okta (custom auth servers) | Yes | Nothing β audience is bound to the auth server URL. |
| Amazon Cognito | Yes | Nothing β resource server inferred from scopes. |
| Microsoft Entra ID (v2) | Yes | Use scope=<api-app-uri>/.default (set via GATEWAY_OAUTH_PROVIDER_SCOPES). |
| Google Identity | Yes | Nothing β scopes alone. |
| Keycloak | Yes | Nothing β audience usually bound to client config. |
| Auth0 | No β Auth0 demands an explicit audience per request | Enable tenant-level Default Audience. See below. |
Auth0-specific one-time setupβ
Auth0 is the one common IdP that requires audience per request unless you enable a tenant-level default. The fix is a 30-second click:
- Auth0 Dashboard β Settings β tab General
- Scroll to API Authorization Settings
- Set Default Audience =
urn:zscaler-mcp:gateway(or whatever value you also set asGATEWAY_INBOUND_ALLOWED_AUDIENCE) - Click Save
After this, Auth0 mints client_credentials tokens with aud: "urn:zscaler-mcp:gateway" even when the request body has no audience param β which is exactly what AgentCore Identity sends.
The deploy script's GATEWAY_OAUTH_TOKEN_ENDPOINT_QUERY env var is preserved as an escape hatch for hypothetical future IdPs that accept extra params some other way, but it does not rescue Auth0 (AgentCore strips the query string before POSTing).
Tool schema (SchemaUpfront vs ImplicitSync)β
When GATEWAY_TOOL_SCHEMA_FILE is empty, the Gateway uses ImplicitSync β it crawls the runtime live during target creation, which requires interactive admin OAuth consent and does not work in CI. Generate a curated schema once with python aws_mcp_operations.py export-tool-schema and ship it via GATEWAY_TOOL_SCHEMA_FILE for any production deploy.
Verifying the Gateway after deployβ
python aws_mcp_operations.py status
Look for:
GatewayLifecycleMode = createorattachGatewayIdβ the ID downstream platforms referenceGatewayMcpUrlβ the URL Quick Suite, Bedrock Agents, etc. registerGatewayTargetIdβ the target we created on the Gateway
To inspect the live target shape:
aws bedrock-agentcore-control list-gateway-targets \
--gateway-identifier <GatewayId> --region us-east-1
When the Gateway path is genuinely overkillβ
If your only consumer is a Lambda / Step Function / Bedrock Agent inside the same AWS account, calling InvokeAgentRuntime directly is simpler and cheaper. Skip the Gateway entirely (leave ENABLE_AGENTCORE_GATEWAY=false) and use the boto3 patterns from the "Authentication, custom headers, and AgentCore" section above.
Testing Your Deploymentβ
Test via AWS Bedrock Sandboxβ
- Navigate to AWS Bedrock AgentCore Console
- Select your runtime
- Open the Test or Sandbox tab
- Send a test request:
Test 1: List Available Toolsβ
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Expected response:
{
"status": "success",
"tool": "tools/list",
"result": {
"tools": [
{"name": "zia_list_ssl_inspection_rules", "description": "..."},
{"name": "zpa_list_app_segments", "description": "..."},
...
]
}
}
Test 2: Call a Zscaler Toolβ
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "zia_list_ssl_inspection_rules",
"arguments": {}
}
}
Expected response:
{
"status": "success",
"tool": "zia_list_ssl_inspection_rules",
"result": [
"{\n \"id\": 184200,\n \"name\": \"Zscaler Recommended Exemptions\",\n ..."
]
}
β Success! Real data from Zscaler API.
Configuring OAuth (Auth0) for AgentCore Identityβ
When the Zscaler MCP Server is deployed with JWT authentication (Layer 1), the Bedrock AgentCore Test Sandbox cannot send Authorization: Bearer <token> headers by default. Configuring Auth0 as an identity provider in AgentCore enables the sandbox and agent to obtain tokens and forward them to your MCP runtime.
Prerequisitesβ
- MCP runtime deployed with JWT auth (e.g. via
deploy_with_jwt.sh) - Auth0 tenant with a Machine-to-Machine (M2M) application
- Client ID and Client Secret from the Auth0 app
- API audience configured (e.g.
zscaler-mcp-server)
Step-by-Step: Add Auth0 OAuth Clientβ
-
Open the Identity menu
- In the AWS console, go to Amazon Bedrock AgentCore
- In the left sidebar under Build, select Identity
-
Add OAuth Client
- Click Add OAuth Client
- You will see the "Add OAuth Client" form
-
Name
- Enter a name (e.g.
auth0-zscaler-mcpor accept the auto-generated one) - Valid characters:
a-z,A-Z,0-9,_(underscore),-(hyphen) - Max 50 characters
- Enter a name (e.g.
-
Provider
- Select Included provider (not Custom provider)
- From the dropdown, choose Auth0 by Okta
-
Provider configurations Fill in the fields with values from your Auth0 tenant. Example for acme.us.auth0.com:
Field Value Client ID x7hEmN5MikTZXhZkOnTMRlQlbtiiPCceClient secret (Use the value from Auth0 Dashboard β Applications, or from AUTH0_CLIENT_SECRETin your env)Issuer https://acme.us.auth0.com/Authorization endpoint https://acme.us.auth0.com/authorizeToken endpoint https://acme.us.auth0.com/oauth/tokenFor a different Auth0 tenant, use these URL patterns (replace
YOUR_TENANTwith your domain):- Issuer:
https://YOUR_TENANT/(trailing slash required) - Authorization endpoint:
https://YOUR_TENANT/authorize - Token endpoint:
https://YOUR_TENANT/oauth/token
- Issuer:
-
Save
- Click Add (or equivalent) to create the OAuth client
Step 2: Add the Callback URL to Auth0 (Required)β
After creating the OAuth client, Bedrock displays a Callback URL on the provider details page. You must add this URL to your Auth0 application.
- In the provider details (e.g. for
MyOrgAuth0Provider), locate the Callback URL (e.g.https://bedrock-agentcore.us-east-1.amazonaws.com/identities/oauth2/callback/76ae34e8-117e-4a20-989e-b8b0b27216fa). - Copy the full URL.
- In Auth0 Dashboard β Applications β your application β Settings.
- In Allowed Callback URLs, add the Bedrock callback URL (comma-separated if you have others).
- Click Save Changes.
Without this step, the OAuth flow will fail when the sandbox or agent tries to obtain tokens.
Step 3: Associate the OAuth Client for Sandbox Testingβ
The success message says: "Associate it within your local agent code or in a Gateway target."
For Agent Sandbox testing:
-
If using Agent Sandbox with a Runtime β When you open the sandbox and select your Zscaler MCP runtime, look for:
- A Sign in or Connect option that lets you authenticate with an identity provider.
- A dropdown or settings to select MyOrgAuth0Provider (or your provider name).
- After signing in, the sandbox should obtain tokens and include them in
Authorization: Bearerheaders when invoking the runtime.
-
If using a Gateway β When the Zscaler MCP runtime is exposed through a Gateway (as a target), add the OAuth credential provider when creating or editing the Gateway target:
- In the Gateway target configuration, set the credentials / credential provider to the ARN of your Auth0 provider (e.g.
arn:aws:bedrock-agentcore:us-east-1:123456789012:token-vault/default/oauth2credentialprovider/MyOrgAuth0Provider).
- In the Gateway target configuration, set the credentials / credential provider to the ARN of your Auth0 provider (e.g.
-
If using local agent code β Use the identity provider ARN in your agentβs configuration when it connects to the MCP runtime, so the agent can fetch tokens and attach them to MCP requests.
The provider ARN is shown on the provider details page (e.g. arn:aws:bedrock-agentcore:us-east-1:123456789012:token-vault/default/oauth2credentialprovider/MyOrgAuth0Provider).
Step 4: Verify Auth0 API and Audienceβ
- Ensure your Auth0 API has an identifier (e.g.
zscaler-mcp-server) that matches theAUDIENCEin your MCP deployment. - The
audienceparameter is required for Auth0 to return JWT (not opaque) tokens.
Troubleshooting Identity Configurationβ
- 401 Unauthorized from MCP β Ensure the OAuth clientβs issuer and endpoints match the values used by your MCP serverβs JWT validation (
ZSCALER_MCP_AUTH_ISSUER, JWKS URI, etc.). - Token not sent β Confirm the OAuth client is associated with the runtime/gateway used for testing.
- Audience mismatch β Ensure your Auth0 API has the same audience (e.g.
zscaler-mcp-server) as configured inAUDIENCEin your deployment. - OAuth redirect/callback errors β Add the Bedrock callback URL (from the provider details page) to Auth0 Allowed Callback URLs.
Troubleshootingβ
Issue: Stack Creation Fails with "AgentRuntimeDeployment CREATE_FAILED"β
Check Lambda logs:
aws logs tail /aws/lambda/zscalermcp-deployment \
--region us-east-1 \
--since 10m
Common causes:
- ECR Image URI incorrect - Verify your account ID is correct
- IAM permissions missing - Check Lambda role has
bedrock-agentcore:* - Secret not found - Verify secret name matches
Issue: Container Fails to Startβ
Check container logs:
aws logs tail /aws/bedrock-agentcore/runtimes/RUNTIME_ID-DEFAULT \
--region us-east-1 \
--since 10m
Common causes:
- "Failed to retrieve secret" - Check IAM role has Secrets Manager permissions
- "Secret not found" - Verify secret name is correct
- "AccessDenied" - Check IAM role permissions
Issue: Tools Return "Zscaler SDK failed to initialize"β
Check if credentials are loaded:
aws logs tail /aws/bedrock-agentcore/runtimes/RUNTIME_ID-DEFAULT \
--region us-east-1 \
--since 5m | grep "Set environment variable"
Expected: Should see all credential variables being set.
If using Secrets Manager:
- Verify secret contains all required fields
- Check IAM role has
secretsmanager:GetSecretValue
If using direct env vars:
- Verify all credentials are in the
--environment-variablesparameter
Issue: Credentials Visible in AgentCore Configβ
This is expected if using Direct Environment Variables approach.
If using Secrets Manager and still seeing credentials:
- Check that
ZSCALER_SECRET_NAMEis set in environment variables - Check container logs for "using credentials from environment variables directly"
Security Best Practicesβ
For Production Deploymentsβ
- β Use Secrets Manager - Container-based retrieval
- β Enable KMS encryption - Use AWS managed or customer managed keys
- β Restrict IAM permissions - Least privilege access
- β Enable CloudTrail - Audit all secret access
- β Rotate credentials regularly - Use Secrets Manager rotation
- β Monitor CloudWatch logs - Set up alarms for errors
- β Use VPC endpoints - For private network access (if needed)
For Development/Testingβ
- β Use separate credentials - Don't use production credentials
- β Consider Secrets Manager - Even for dev (good practice)
- β
Clear shell history - After using direct env vars:
history -c - β
Don't commit credentials - Never commit
.envfiles or scripts with credentials
Encryption Detailsβ
Secrets Manager Encryptionβ
Default Encryption:
- β AWS Secrets Manager always encrypts secrets at rest
- β
Uses AWS managed KMS key:
alias/aws/secretsmanager - β Encryption is automatic and transparent
Custom KMS Key (Optional):
# Create custom KMS key
aws kms create-key \
--description "Zscaler MCP Server credentials encryption key" \
--region us-east-1
# Create secret with custom key
aws secretsmanager create-secret \
--name zscaler/mcp/credentials \
--kms-key-id <your-kms-key-id> \
--secret-string '{...}' \
--region us-east-1
Verify encryption:
aws secretsmanager describe-secret \
--secret-id zscaler/mcp/credentials \
--region us-east-1 \
--query 'KmsKeyId'
CloudFormation Template Encryptionβ
The CloudFormation template explicitly specifies KMS encryption:
ZscalerCredentialsSecret:
Type: AWS::SecretsManager::Secret
Properties:
KmsKeyId: 'alias/aws/secretsmanager' # β AWS managed KMS key
SecretString: !Sub |
{ ... }
β All secrets created by the CloudFormation template are encrypted with KMS.
Migration Between Approachesβ
From Direct Environment Variables β Secrets Managerβ
-
Extract current credentials:
aws bedrock-agentcore-control get-agent-runtime \--region us-east-1 \--agent-runtime-id <runtime-id> \--query 'environmentVariables' > current-creds.json -
Create Secrets Manager secret:
aws secretsmanager create-secret \--name zscaler/mcp/credentials \--kms-key-id alias/aws/secretsmanager \--secret-string file://current-creds.json \--region us-east-1 -
Update IAM role - Add Secrets Manager permissions
-
Delete old runtime:
aws bedrock-agentcore-control delete-agent-runtime \--region us-east-1 \--agent-runtime-id <old-runtime-id> -
Deploy new runtime - Using Secrets Manager approach (see above)
From Secrets Manager β Direct Environment Variablesβ
-
Get credentials from secret:
aws secretsmanager get-secret-value \--secret-id zscaler/mcp/credentials \--region us-east-1 \--query 'SecretString' --output text > credentials.json -
Delete old runtime
-
Deploy new runtime - Using direct environment variables approach (see above)
Summaryβ
Recommended Approach for Productionβ
CloudFormation + Secrets Manager:
Click Launch Stack Button
β
Select "UseExisting" or "CreateNew"
β
Fill in parameters
β
Deploy (3-5 minutes)
β
β
Secure, encrypted, production-ready
Quick Approach for Testingβ
Manual CLI + Direct Environment Variables:
Create IAM role (basic permissions)
β
Run aws bedrock-agentcore-control create-agent-runtime
β
Pass credentials directly
β
β
Fast, simple, good for testing
Environment variable referenceβ
The integration deliberately keeps the user-facing surface small. The deploy script (via the runtime provisioner Lambda) and the container image set most plumbing values internally β you should rarely need to touch them. The tables below split what's intended for users versus what the system manages itself, and map to the same layout in integrations/aws/bedrock-agentcore/env.properties.
User-facing β requiredβ
These are the only variables strictly required for a baseline Direct Runtime deployment with CredentialSource=CreateNew. With CredentialSource=UseExisting you don't need to set them at all β only ZSCALER_SECRET_NAME.
| Variable | Purpose | Source |
|---|---|---|
ZSCALER_CLIENT_ID | OneAPI client ID from ZIdentity. | ZIdentity console |
ZSCALER_CLIENT_SECRET | OneAPI client secret. | ZIdentity console |
ZSCALER_VANITY_DOMAIN | ZIdentity vanity domain (e.g. acme). | ZIdentity console |
ZSCALER_CUSTOMER_ID | Zscaler customer/tenant ID. | ZIdentity console |
ZSCALER_CLOUD | Cloud override β production by default; set to beta for the Beta tenant. | ZIdentity console |
ZSCALER_SECRET_NAME | Pre-existing Secrets Manager secret name (alternative to inline creds). Recommended for production. | Secrets Manager |
User-facing β optional (tunables)β
Sensible defaults are baked in; override only when you need to.
| Variable | Default | Purpose |
|---|---|---|
AWS_REGION | us-east-1 | Region for all created resources. |
AWS_STACK_NAME | zscaler-mcp-agentcore | Root CloudFormation stack name. |
AWS_RESOURCE_NAME_PREFIX | zscaler-mcp | Prefix for all named resources. |
AWS_ASSET_BUCKET | auto-generated | S3 bucket for nested templates. Leave blank to auto-generate. |
ZSCALER_MCP_IMAGE_URI | bundled Marketplace tag | Override to pin a specific image tag, mirror, or dev build. |
ZSCALER_MCP_WRITE_ENABLED | false | Enable create/update/delete tools. |
ZSCALER_MCP_WRITE_TOOLS | (unset) | Wildcard allowlist when writes are on, e.g. zpa_create_*,zia_update_*. |
ZSCALER_MCP_DISABLED_TOOLS | (unset) | Wildcard blocklist for individual tools. |
ZSCALER_MCP_DISABLED_SERVICES | (unset) | Comma-separated service blocklist (zcc,zdx). |
ZSCALER_MCP_LOG_TOOL_CALLS | true | Audit every tool call to CloudWatch. |
User-facing β authentication (mode-dependent)β
Pick the block matching your ZSCALER_MCP_AUTH_MODE.
| Variable | Used by mode | Purpose |
|---|---|---|
ZSCALER_MCP_AUTH_MODE | always | jwt / zscaler / api-key / none. |
ZSCALER_MCP_AUTH_JWKS_URI | jwt | JWKS endpoint of your IdP. |
ZSCALER_MCP_AUTH_ISSUER | jwt | Token issuer URL (matched against iss claim). |
ZSCALER_MCP_AUTH_AUDIENCE | jwt | Expected aud claim value. |
ZSCALER_MCP_AUTH_JWT_DISCOVERY_URL | jwt (optional) | Override customJwtAuthorizer.discoveryUrl. Derived from issuer if blank. |
ZSCALER_MCP_AUTH_JWT_ALLOWED_CLIENTS | jwt (optional) | Comma-separated client_id allowlist on the AgentCore authorizer. |
ZSCALER_MCP_AUTH_API_KEY | api-key | API key. Auto-generated at deploy time if blank. |
Experimental β AgentCore Gateway (only when ENABLE_AGENTCORE_GATEWAY=true)β
See Gateway integration (experimental) for the current state of testing before setting these.
| Variable | Mode | Purpose |
|---|---|---|
ENABLE_AGENTCORE_GATEWAY | both | Default for the interactive architecture prompt: true defaults the prompt to option [2]/[3], false (or unset) defaults to [1] Direct Runtime. For non-interactive deploys this is the only signal. |
GATEWAY_MODE | both | create (stack provisions Gateway) or attach (use existing). Pairs with ENABLE_AGENTCORE_GATEWAY to pick the prompt default β create β [2], attach β [3]. |
EXISTING_GATEWAY_ID | attach | ID of a Gateway you already operate. |
GATEWAY_INBOUND_DISCOVERY_URL | create | IdP OIDC discovery URL for the Gateway's customJwtAuthorizer. |
GATEWAY_INBOUND_ISSUER | create | Alternative to discovery URL. |
GATEWAY_OAUTH_CLIENT_ID | create | Comma-separated OAuth client_id allowlist. |
GATEWAY_INBOUND_ALLOWED_AUDIENCE | create | Expected aud claim. |
GATEWAY_INBOUND_ALLOWED_SCOPES | create | Required scope(s); also published in scopes_supported metadata. |
GATEWAY_INBOUND_CLIENT_CLAIM_NAME | create (rare) | Override the client-id claim name when auto-detection picks wrong. |
GATEWAY_OAUTH_CLIENT_SECRET | create | Secret for the auto-provisioned outbound credential provider. |
GATEWAY_OAUTH_PROVIDER_ARN | create | Reuse an existing OAuth provider (alternative to auto-provisioning). |
GATEWAY_OAUTH_PROVIDER_SCOPES | create (rare) | Optional scope override for the outbound provider. |
GATEWAY_OAUTH_PROVIDER_GRANT_TYPE | create (rare) | CLIENT_CREDENTIALS (default) / AUTHORIZATION_CODE / TOKEN_EXCHANGE. |
GATEWAY_OAUTH_TOKEN_ENDPOINT_QUERY | create (rare) | Escape hatch for IdP-specific quirks. Auth0 ignores it β use tenant-level Default Audience instead. |
GATEWAY_TOOL_SCHEMA_FILE | both | Path to a JSON tool schema. Strongly recommended β puts the target in SchemaUpfront mode. |
Internal β set by the runtime provisioner / Dockerfile (do not set manually)β
These are documented for transparency only. The deploy script and the container image set them automatically; setting them yourself in env.properties either gets overwritten at deploy time or breaks the deployment. They map directly to behaviour the AgentCore Runtime topology requires.
| Variable | Set to | Why it's internal |
|---|---|---|
FASTMCP_STATELESS_HTTP | true (Dockerfile) | AgentCore Runtime replicas are ephemeral and may be replaced between requests; stateful sessions would pin to dead replicas. |
ZSCALER_MCP_ALLOW_HTTP | true (provisioner) | AgentCore terminates TLS upstream of the container, so the inner HTTP server runs plain HTTP. |
ZSCALER_MCP_DISABLE_HOST_VALIDATION | true (provisioner) | AgentCore is the sole ingress and has already authenticated the request before forwarding. The internal Host header is not predictable for an inner allowlist. |
ZSCALER_MCP_AUTH_ENABLED | true/false (provisioner) | Derived from McpAuthMode. |
ZSCALER_MCP_TRANSPORT / ZSCALER_MCP_HOST / ZSCALER_MCP_PORT | streamable-http / 0.0.0.0 / 8000 (Dockerfile CMD) | Required for AgentCore Runtime to forward MCP traffic correctly. |
AWS_REGION | injected by AgentCore | Used by the container's Secrets Manager loader. |
Defense-in-depth toggles (not surfaced in env.properties and not exposed by the deploy script): ZSCALER_MCP_DISABLE_OUTPUT_SANITIZATION, ZSCALER_MCP_DISABLE_ENTITLEMENT_FILTER, ZSCALER_MCP_SKIP_CONFIRMATIONS, ZSCALER_MCP_CONFIRMATION_TTL. These remove security layers and should only be set for short-lived debugging.
Additional Resourcesβ
Document Version: 2.0.0 Last Updated: November 22, 2025 Deployment Methods: CloudFormation (recommended), Manual AWS CLI Credential Approaches: Secrets Manager (recommended), Direct Environment Variables