

# Advanced features and topics for Amazon Bedrock AgentCore Gateway
<a name="gateway-advanced"></a>

This chapter covers some advanced topics and additional information that can help supplement your knowledge of gateways and how you can use them effectively in your applications.

**Topics**
+ [Encrypt your AgentCore gateway with a customer-managed KMS key](gateway-encryption.md)
+ [Setting up custom domain names for Gateway endpoints](gateway-custom-domains.md)
+ [Using interceptors with Gateway](gateway-interceptors.md)
+ [Header propagation with Gateway](gateway-headers.md)
+ [Performance optimization](gateway-advanced-performance.md)

# Encrypt your AgentCore gateway with a customer-managed KMS key
<a name="gateway-encryption"></a>

By default, Gateway encrypts your data at rest using a service-managed AWS Key Management Service key. However, you can optionally provide your own customer managed KMS key for encrypting data at rest when you:
+ Create a gateway.
+ Update a gateway’s configurations.

Using a customer managed key gives you more control over the encryption process, including the ability to:
+ Rotate the key on your own schedule
+ Control access to the key through IAM policies
+ Disable or delete the key when it’s no longer needed
+ Audit key usage through CloudWatch logs and AWS CloudTrail

For more information, see [AWS Key Management Service Developer Guide](https://docs.aws.amazon.com/kms/latest/developerguide/).

**Note**  
If you choose to use a customer managed key, you are responsible for managing the key and its permissions. If the key is disabled or deleted, or if Gateway loses permission to use the key, you will lose access to the encrypted data.

## Prerequisites for encrypting your AgentCore gateway
<a name="gateway-encryption-prereqs"></a>

Before encrypting your gateway, ensure that you have fulfilled the following prerequsites:
+ You have access to a KMS key. For information about creating a KMS key, see [Create a KMS key](https://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html).
+ The KMS key has a key policy attached to it that allows the following permissions:
  + Permissions that allow the gateway service role to perform the following actions:
    + kms:CreateGrant
    + kms:DescribeKey
    + kms:Decrypt
    + kms:GenerateDataKey
  + (If you enable CloudWatch Logs for your gateway) Permissions that allow the CloudWatch Logs service to decrypt the key.

    For more information about controlling IAM permissions for a KMS key, see [KMS key access and permissions](https://docs.aws.amazon.com/kms/latest/developerguide/control-access.html) in the AWS Key Management Service Developer Guide.

 **Example key policy** 

The following example policy provides the necessary permissions to encrypt a gateway and use an encrypted gateway. The fourth statement also allows CloudWatch Logs logging of key usage for the encrypted gateway. The policy contains condition keys to conform to security best practices.

```
{
"Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowServiceRoleDescribeKey",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/GatewayServiceRole"
      },
      "Action": [
        "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": [
            "bedrock-agentcore.us-east-1.amazonaws.com"
          ]
        }
      }
    },
    {
      "Sid": "AllowServiceRoleDecryptKey",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/GatewayServiceRole"
      },
      "Action": [
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": [
            "bedrock-agentcore.us-east-1.amazonaws.com"
          ]
        },
        "StringLike": {
          "kms:EncryptionContext:aws:bedrock-agentcore-gateway:arn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/GatewayId"
        }
      }
    },
    {
      "Sid": "AllowServiceRoleCreateGrant",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/GatewayServiceRole"
      },
      "Action": "kms:CreateGrant",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": [
            "bedrock-agentcore.us-east-1.amazonaws.com"
          ],
          "kms:GrantConstraintType": "EncryptionContextSubset"
        },
        "ForAllValues:StringEquals": {
          "kms:GrantOperations": [
            "Decrypt",
            "GenerateDataKey"
          ]
        },
        "StringLike": {
          "kms:EncryptionContext:aws:bedrock-agentcore-gateway:arn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:gateway/GatewayId"
        }
      }
    },
    {
      "Sid": "AllowKMSDecryptionLogging",
      "Effect": "Allow",
      "Principal": {
        "Service": "delivery.logs.amazonaws.com"
      },
      "Action": [
        "kms:GenerateDataKey",
        "kms:Decrypt"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:EncryptionContext:SourceArn": "arn:aws:logs:us-east-1:123456789012:*"
        }
      }
    }
  ]
}
```

The policy contains the following statements:
+  **AllowServiceRoleDescribeKKey** – Allows the specified principal to describe the key if the request is made through the AgentCore service. Replace values in the following fields:
  +  `Principal` – Replace the `AWS` value with the actual ARN of your gateway service role.
  +  `Condition` – In the `kms:ViaService` array, replace *us-east-1* with the actual AWS Region for which you want to allow the key to be described.
+  **AllowServiceRoleDecryptKey** – Allows the specified principal to decrypt the key if the request is made through the AgentCore service and if the ARN of the gateway to which the request is made matches the one in the `kmsEncryptionContext:aws:bedrock-agentcore-gateway-arn` field. Replace the following values:
  +  `Principal` – Replace the `AWS` value with the actual ARN of your gateway service role.
  +  `Condition` – Do the following:
    + In the `kms:ViaService` array, replace *us-east-1* with the actual AWS Region for which you want to allow the key to be decrypted.
    + Replace the `kmsEncryptionContext:aws:bedrock-agentcore-gateway-arn` value with the actual ARN of your gateway.
+  **AllowServiceRoleCreateGrant** – Allows the specified principal to create a grant for a key if the request is made through the AgentCore service and if the ARN of the gateway to which the request is made matches the one in the `kmsEncryptionContext:aws:bedrock-agentcore-gateway-arn` field. Replace the following values:
  +  `Principal` – Replace the `AWS` value with the actual ARN of your gateway service role.
  +  `Condition` – Do the following:
    + In the `kms:ViaService` array, replace *us-east-1* with the actual AWS Region for which you want to allow the key to be decrypted.
    + Replace the `kmsEncryptionContext:aws:bedrock-agentcore-gateway-arn` value with the actual ARN of your gateway (if you want to allow).
+  **AllowKMSDecryptionLogging** – Allows the specified principal to decrypt a customer-managed KMS key for auditing key usage through CloudWatch Logs. In the `kms:EncryptionContext:SourceArn` value, replace *us-east-1* and *123456789012* values with your actual AWS Region and account ID.

**Example**  

1. ====== To encrypt your gateway with a customer-managed KMS key

1. Follow the **Console** steps at [Create an Amazon Bedrock AgentCore gateway](gateway-create.md) and expand the **KMS key - optional** section.

1. Choose **Customize encryption settings (advanced)**.

1. Select a KMS key and confirm its details.
**Note**  
If you don’t see your KMS key, go over the [Prerequisites for encrypting your AgentCore gateway](#gateway-encryption-prereqs) and check that the permissions are properly configured.

1. Continue through the remaining console steps.

1. To encrypt your gateway using the AWS CLI, include the `kms-key-arn` when sending either of the following requests through an [AgentCore control plane](https://docs.aws.amazon.com/cli/latest/reference/bedrock-agentcore-control/) client.:
   +  [create-gateway](https://docs.aws.amazon.com/cli/latest/reference/bedrock-agentcore-control/create-gateway.html) 
   +  [update-gateway](https://docs.aws.amazon.com/cli/latest/reference/bedrock-agentcore-control/update-gateway.html) 

     The following example shows an example CLI request to create a gateway with a AWS KMS key specified using the `kms-key-arn` argument:

     ```
     aws bedrock-agentcore-control create-gateway \
       --name "MyGateway" \
       --role-arn "arn:aws:iam::123456789012:role/GatewayRole" \
       --protocol-type "MCP" \
       --kms-key-arn "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab" \
       --authorizer-type "CUSTOM_JWT" \
       --authorizer-configuration '{
         "customJWTAuthorizer": {
           "allowedAudience": ["myAudience"],
           "discoveryUrl": "https://example.com/.well-known/openid-configuration"
         }
       }'
     ```

1. To encrypt your gateway using the AWS Python SDK (Boto3), include the `kms-key-arn` when sending either of the following requests through an [AgentCore control plane](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agentcore-control.html) client.:
   +  [create\$1gateway](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agentcore-control/client/create_gateway.html) 
   +  [update\$1gateway](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agentcore-control/client/update_gateway.html) 

     The following example shows an example Boto3 request to create a gateway with a AWS KMS key specified using the `kmsKeyArn` argument:

     ```
     import boto3
     # Create client
     agentcore_client = boto3.client('bedrock-agentcore-control')
     
     # Create gateway and specify
     gateway = agentcore_client.create_gateway(
       name="MyGateway",
       roleArn="arn:aws:iam::123456789012:role/GatewayRole",
       protocolType="MCP",
       kmsKeyArn="arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab"
       authorizerType="CUSTOM_JWT",
       authorizerConfiguration= {
         "customJWTAuthorizer": {
           "allowedAudience": ["myAudience"],
           "discoveryUrl": "https://example.com/.well-known/openid-configuration"
         }
       }
     )
     ```

# Setting up custom domain names for Gateway endpoints
<a name="gateway-custom-domains"></a>

By default, Gateway endpoints are provided with an AWS-managed domain name in the format `<gateway-id>.gateway.bedrock-agentcore.<region>.amazonaws.com` . For production environments or to create a more user-friendly experience, you may want to use a custom domain name for your gateway endpoint. This section guides you through setting up a custom domain name using Amazon CloudFront as a reverse proxy.

## Prerequisites
<a name="gateway-custom-domains-prereq"></a>

Before you begin, ensure you have:
+ A working Gateway endpoint
+ DNS delegation (if your Route 53 domain needs to be publicly reachable)
+  AWS CDK installed and configured (if following the CDK approach)
+ Appropriate IAM permissions to create and manage CloudFront distributions, Route 53 hosted zones, and ACM certificates

## Solution overview
<a name="gateway-custom-domains-overview"></a>

The solution involves the following components:
+  **Route 53 Hosted Zone** : Manages DNS records for your custom domain
+  **ACM Certificate** : Provides SSL/TLS encryption for your custom domain
+  **CloudFront Distribution** : Acts as a reverse proxy, forwarding requests from your custom domain to the Gateway endpoint
+  **Route 53 A Record** : Maps your custom domain to the CloudFront distribution

The following steps will guide you through setting up these components using AWS CDK.

## Implementation steps
<a name="gateway-custom-domains-steps"></a>

### Step 1: Create a Route 53 hosted zone
<a name="gateway-custom-domains-hosted-zone"></a>

First, create a Route 53 hosted zone for your custom domain:

```
import { RemovalPolicy } from 'aws-cdk-lib';
import { PublicHostedZone } from 'aws-cdk-lib/aws-route53';

const domainName = 'my.example.com';

const hostedZone = new PublicHostedZone(this, 'HostedZone', {
    zoneName: domainName,
});
this.hostedZone.applyRemovalPolicy(RemovalPolicy.RETAIN);
```

**Note**  
We apply a removal policy of `RETAIN` to prevent accidental deletion of the hosted zone during stack updates or deletion.

### Step 2: Create a DNS-validated certificate
<a name="gateway-custom-domains-certificate"></a>

Next, create an SSL/TLS certificate for your custom domain using AWS Certificate Manager (ACM) with DNS validation:

```
import { RemovalPolicy } from 'aws-cdk-lib';
import { Certificate, CertificateValidation } from 'aws-cdk-lib/aws-certificatemanager';

const certificate = new Certificate(this, 'SSLCertificate', {
    domainName: domainName, // route53 hosted zone domain name from step 1
    validation: CertificateValidation.fromDns(hostedZone), // route53 hosted zone from step 1
});
this.certificate.applyRemovalPolicy(RemovalPolicy.RETAIN);
```

DNS validation automatically creates the necessary validation records in your Route 53 hosted zone.

### Step 3: Create a CloudFront distribution
<a name="gateway-custom-domains-cloudfront"></a>

Create a CloudFront distribution to act as a reverse proxy for your Gateway endpoint:

```
import {
  AllowedMethods,
  CachePolicy,
  Distribution,
  OriginProtocolPolicy,
  ViewerProtocolPolicy
} from 'aws-cdk-lib/aws-cloudfront';
import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';

const bedrockAgentCoreGatewayHostName = '<mymcpserver>.gateway.bedrock-agentcore.<region>.amazonaws.com'
const bedrockAgentCoreGatewayPath = '/mcp' // can also be left undefined, depending on your requirement

const distribution = new Distribution(this, 'Distribution', {
    defaultBehavior: {
        origin: new HttpOrigin(bedrockAgentCoreGatewayHostName, {
            protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
            originPath: bedrockAgentCoreGatewayPath,
        }),
        viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY,
        cachePolicy: CachePolicy.CACHING_DISABLED, // important since caching is enabled by default and hence is not suitable for a reverse proxy
        allowedMethods: AllowedMethods.ALLOW_ALL,
    },
    domainNames: [domainName], // route53 hosted zone domain name from step 1
    certificate: certificate, // ssl certificate for the route53 domain from step 2
});
```

**Important**  
Set `cachePolicy: CachePolicy.CACHING_DISABLED` to ensure that CloudFront doesn’t cache responses from your Gateway endpoint, which is important for dynamic API interactions.

Replace `<mymcpserver>` with your gateway ID and `<region>` with your AWS Region (e.g., `us-east-1` ).

### Step 4: Create a Route 53 A record
<a name="gateway-custom-domains-dns-record"></a>

Create a Route 53 A record that points your custom domain to the CloudFront distribution:

```
import { ARecord, RecordTarget } from 'aws-cdk-lib/aws-route53';
import { CloudFrontTarget } from 'aws-cdk-lib/aws-route53-targets';

const aRecord = new ARecord(this, 'AliasRecord', {
    zone: hostedZone, // route53 hosted zone from step 1
    recordName: domainName, // route53 hosted zone domain name from step 1
    target: RecordTarget.fromAlias(new CloudFrontTarget(distribution)), // cloufront distribution from from step 3
});
```

This creates an alias record that maps your custom domain to the CloudFront distribution.

### Step 5: Deploy your infrastructure
<a name="gateway-custom-domains-deploy"></a>

Deploy your CDK stack to create the resources:

```
cdk deploy
```

The deployment process may take some time, especially for the certificate validation and CloudFront distribution creation.

## Testing your custom domain
<a name="gateway-custom-domains-testing"></a>

After deploying your infrastructure, verify that your custom domain is properly configured:

### Verify DNS resolution
<a name="gateway-custom-domains-testing-dns"></a>

Use the `dig` command to verify that your custom domain resolves to the CloudFront distribution:

```
dig my.example.com
```

The output should show that your domain resolves to CloudFront’s IP addresses.

### Verify SSL certificate
<a name="gateway-custom-domains-testing-ssl"></a>

Use `curl` to verify that the SSL certificate is properly configured:

```
curl -v https://my.example.com
```

The output should show a successful SSL handshake with no certificate errors.

## Configuring MCP clients
<a name="gateway-custom-domains-client-config"></a>

Once your custom domain is set up and verified, you can configure your MCP clients to use it:

### Cursor configuration
<a name="gateway-custom-domains-client-config-cursor"></a>

For Cursor, update your configuration file:

```
{
  "mcpServers": {
    "my-mcp-server": {
      "url": "https://my.example.com"
    }
  }
}
```

### Other MCP clients
<a name="gateway-custom-domains-client-config-other"></a>

For MCP clients that don’t natively support streamable HTTP:

```
{
  "mcpServers": {
    "my-mcp-server": {
      "command": "/path/to/uvx",
        "args": [
            "mcp-proxy",
            "--transport",
            "streamablehttp",
            "https://my.example.com"
        ]
    }
  }
}
```

## Additional considerations
<a name="gateway-custom-domains-considerations"></a>

 **Cost implications**   
Using CloudFront as a reverse proxy incurs additional costs for data transfer and request handling. Review the CloudFront pricing model to understand the cost implications for your specific use case.

 **Security considerations**   
Consider implementing additional security measures such as:  
+ WAF rules to protect your endpoint from common web exploits
+ Geo-restrictions to limit access to specific geographic regions
+ Custom headers or request signing to add an extra layer of authentication

 **Monitoring and logging**   
Enable CloudFront access logs and configure CloudWatch alarms to monitor the health and performance of your custom domain setup.

 **Certificate renewal**   
ACM certificates issued through DNS validation are automatically renewed as long as the DNS records remain in place. Ensure that you don’t delete the validation records.

 **OAuth protected resource endpoint with custom domains**   
By default, the `/.well-known/oauth-protected-resource` endpoint returns a resource URL that contains the gateway domain instead of your custom domain. This can cause OAuth clients to fail authentication when using custom domains.  
To resolve this issue, you can implement a Lambda@Edge function that intercepts the OAuth discovery response and generates a new response with the correct custom domain URL. Here’s the approach:  
+  **Use Lambda@Edge with ORIGIN\$1RESPONSE event type** : Create a function that triggers on origin responses to intercept the OAuth protected resource endpoint response.
+  **Generate a new response** : Lambda@Edge cannot read origin response bodies, so instead of modifying the existing response, generate a completely new JSON response with the custom domain.
+  **Associate with CloudFront behavior** : Configure the Lambda@Edge function to trigger specifically for the `/.well-known/oauth-protected-resource` path pattern.

  After implementing this solution, the OAuth protected resource endpoint will return the correct custom domain:

  ```
  curl https://my-custom-domain.com/.well-known/oauth-protected-resource
  {
    "authorization_servers": ["https://my-org.okta.com/oauth2/default"],
    "resource": "https://my-custom-domain.com/mcp"
  }
  ```
**Note**  
While Lambda@Edge provides a solution for this issue, implementing custom domains for AgentCore Gateway without built-in support requires additional complexity that may not be optimal for all customers. Consider this approach as a workaround until native support for OAuth discovery with custom domains becomes available.

## Troubleshooting
<a name="gateway-custom-domains-troubleshooting"></a>

 **DNS resolution issues**   
If your custom domain doesn’t resolve correctly:  
+ Verify that the A record is correctly configured in your Route 53 hosted zone
+ Check that your domain’s name servers are correctly set at your domain registrar
+ Allow time for DNS propagation (up to 48 hours in some cases)

 **SSL certificate issues**   
If you encounter SSL certificate errors:  
+ Verify that the certificate is issued and active in the ACM console
+ Check that the certificate is correctly associated with your CloudFront distribution
+ Ensure that the certificate covers the exact domain name you’re using

 **Gateway connectivity issues**   
If your custom domain doesn’t connect to your gateway:  
+ Verify that the origin domain and path in your CloudFront distribution are correct
+ Check that your gateway endpoint is accessible directly
+ Review CloudFront distribution logs for any errors

## Conclusion
<a name="gateway-custom-domains-conclusion"></a>

Setting up a custom domain name for your Gateway endpoint enhances the professional appearance of your application and provides flexibility in managing your API endpoints. By following the steps outlined in this guide, you can create a secure and reliable custom domain configuration using CloudFront as a reverse proxy.

For more information about Gateway features and capabilities, see [Amazon Bedrock AgentCore Gateway: Securely connect tools and other resources to your Gateway](gateway.md).

# Using interceptors with Gateway
<a name="gateway-interceptors"></a>

Configuring interceptors on your gateway allows you to run custom code during each invocation of your gateway. This section provides guidance on implementing and configuring interceptors for your Gateway.

**Topics**
+ [Overview](#gateway-interceptors-overview)
+ [Security best practices](#gateway-interceptors-security)
+ [Permissions for interceptors](gateway-interceptors-permissions.md)
+ [Types of interceptors](gateway-interceptors-types.md)
+ [Configuration](gateway-interceptors-configuration.md)
+ [Examples](gateway-interceptors-examples.md)

## Overview
<a name="gateway-interceptors-overview"></a>

Configuring interceptors on your gateway allows you to run custom code during each invocation of your gateway. This is useful for the following use cases:
+ Implementing fine-grained access control over tools or MCP operations
+ Transforming the target request and gateway response
+ Implementing custom authorization logic

There are two types of interceptors that can be configured on your gateway:
+  **REQUEST interceptors** – Execute before the gateway makes a call to the target. These are useful for request validation, transformation, or custom authorization.
+  **RESPONSE interceptors** – Execute after the target responds but before the gateway sends the response back to the caller. These are useful for response transformation, filtering, or adding custom headers.

A gateway can have at most one REQUEST interceptor and at most one RESPONSE interceptor configured. You can configure both types on the same gateway, but you cannot have multiple interceptors of the same type.

Currently, interceptors can only be configured with Lambda functions.

## Security best practices
<a name="gateway-interceptors-security"></a>

When implementing interceptors, it’s important to follow security best practices to protect sensitive information and maintain proper access control.

1. By default, request headers will not be passed to an interceptor unless the `passRequestHeaders` field is set to true. Be careful when using this field as request headers can contain sensitive information such as authentication tokens and credentials. Be sure to verify your interceptor is not logging this sensitive information.

1. Be sure to restrict your gateway execution role to have permissions only to invoke the specific lambda functions you are using as interceptors and not to give wild card lambda permissions to your execution role.

1. Implement idempotent Lambda functions for your interceptors. The gateway may retry requests to interceptor Lambda functions in case of failures or timeouts. Ensure your interceptor logic can handle duplicate invocations safely by implementing idempotency keys, tracking processed requests, or designing stateless operations that produce consistent results when executed multiple times with the same input.

# Permissions for interceptors
<a name="gateway-interceptors-permissions"></a>

When configuring interceptors, your gateway service role must have the lambda:InvokeFunction IAM permissions to invoke the Lambda functions that serve as interceptors. The service role needs specific permissions to execute interceptor functions during request and response processing.

For detailed information about configuring the required permissions for your gateway service role, including permissions to Lambda, see [Gateway service role permissions](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-prerequisites-permissions.html#gateway-service-role-permissions).

# Types of interceptors
<a name="gateway-interceptors-types"></a>

There are two types of interceptors that can be configured on your gateway:

## Request interceptors
<a name="gateway-interceptors-types-request"></a>

The REQUEST interceptor gets invoked before gateway makes a call to the target configured on the gateway. You can use the REQUEST interceptor to perform any custom validations or authorizations.

### Request interceptor input payload example
<a name="gateway-interceptors-types-request-payload"></a>

The following example shows the input payload structure that a request interceptor Lambda function receives:

```
{
  "interceptorInputVersion": "1.0",
  "mcp": {
    "rawGatewayRequest": {
        "body": "<raw_request_body>"
    },
    "gatewayRequest" : {
        "path": "/mcp",
        "httpMethod": "POST",
        "headers": {
            "Accept": "application/json",
            "Authorization": "<bearer_token>",
            "SomeHeader": "headerValue1",
            "Mcp-Session-Id": "<session_id>",
            "User-Agent": "<client_user_agent>"
        },
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/list"
        }
    }
  }
}
```

**Note**  
The `headers` field is only included if `passRequestHeaders` is set to `true` in the interceptor configuration. The `gatewayResponse` field is not present for request interceptors since the response has not been generated yet.

### Request interceptor output payload example
<a name="gateway-interceptors-types-request-output"></a>

The following example shows the output payload structure that a request interceptor Lambda function should return:

```
{
  "interceptorOutputVersion": "1.0",
  "mcp": {
    "transformedGatewayRequest" : {
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/list"
        }
    },
    "transformedGatewayResponse" : {
        "statusCode": 200,
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
                "<result_content>": "<result_value>"
            }
        }
    }
  }
}
```

**Important**  
Important notes about request interceptor output: \$1 The `interceptorOutputVersion` must be set to `"1.0"` . \$1 If the interceptor output contains a `transformedGatewayResponse` , the gateway will respond with that content immediately, even if `transformedGatewayRequest` is also provided. \$1 If both REQUEST and RESPONSE interceptors are configured and the REQUEST interceptor output contains a `transformedGatewayResponse` , the RESPONSE interceptor will still be invoked.

## Response interceptors
<a name="gateway-interceptors-types-response"></a>

The RESPONSE interceptor gets invoked before the gateway responds to the caller. You can use the RESPONSE interceptor to perform any custom redactions or additions to the response back to the caller.

### Response interceptor input payload example
<a name="gateway-interceptors-types-response-payload"></a>

The following example shows the input payload structure that a response interceptor Lambda function receives:

```
{
  "interceptorInputVersion": "1.0",
  "mcp": {
    "rawGatewayRequest": {
        "body": "<raw_request_body>"
    },
    "gatewayRequest" : {
        "path": "/mcp",
        "httpMethod": "POST",
        "headers": {
            "Accept": "application/json",
            "Authorization": "<bearer_token>",
            "SomeHeader": "headerValue1",
            "Mcp-Session-Id": "<session_id>",
            "User-Agent": "<client_user_agent>"
        },
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/list"
        }
    },
    "gatewayResponse" : {
        "statusCode": 200,
        "headers": {
            "SomeHeader": "headerValue1",
            "Mcp-Session-Id": "<session_id>"
        },
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
                "tools": []
            }
        }
    }
  }
}
```

**Note**  
The `headers` field in `gatewayRequest` is only included if `passRequestHeaders` is set to `true` in the interceptor configuration. Response interceptors receive both the original request and the gateway’s response, allowing you to modify the response before it’s returned to the caller.

### Response interceptor output payload example
<a name="gateway-interceptors-types-response-output"></a>

The following example shows the output payload structure that a response interceptor Lambda function should return:

```
{
  "interceptorOutputVersion": "1.0",
  "mcp": {
    "transformedGatewayRequest" : {
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/list"
        }
    },
    "transformedGatewayResponse" : {
        "statusCode": 200,
        "body": {
            "jsonrpc": "2.0",
            "id": 1,
            "result": {
                "<result_content>": "<result_value>"
            }
        }
    }
  }
}
```

**Important**  
Important notes about response interceptor output: \$1 The `interceptorOutputVersion` must be set to `"1.0"` . \$1 If the interceptor output contains a `transformedGatewayResponse` , the gateway will respond with that content immediately, even if `transformedGatewayRequest` is also provided. \$1 If both REQUEST and RESPONSE interceptors are configured and the REQUEST interceptor output contains a `transformedGatewayResponse` , the RESPONSE interceptor will still be invoked.

# Configuration
<a name="gateway-interceptors-configuration"></a>

Interceptors can be configured with an input parameter called `passRequestHeaders` 

## Interceptor input configuration
<a name="gateway-interceptors-configuration-input"></a>

When configuring interceptors, you can specify whether request headers should be passed to the interceptor function using the `passRequestHeaders` field:

 **passRequestHeaders**   
A boolean value that determines whether request headers are included in the interceptor input payload. When set to `true` , all request headers will be passed to your interceptor Lambda function. When set to `false` (default), headers are not included.  
Use caution when setting this to `true` as request headers may contain sensitive information such as authentication tokens and credentials.

## Configuring interceptors during gateway creation
<a name="gateway-interceptors-configuration-creation"></a>

The following examples show how to create a gateway with interceptors that have `passRequestHeaders` set to `true` :

**Example**  

1. With the AgentCore CLI, first create and deploy the gateway, then configure interceptors using the AWS CLI or AWS Python SDK (Boto3).

   Create the gateway:

   ```
   agentcore add gateway \
     --name my-gateway-with-headers \
     --authorizer-type CUSTOM_JWT \
     --discovery-url "https://cognito-idp.us-west-2.amazonaws.com/some-user-pool/.well-known/openid-configuration" \
     --allowed-audience "api.example.com"
   agentcore deploy
   ```

   After deployment, configure interceptors on the gateway using the AWS CLI `update-gateway` command or the AWS Python SDK (Boto3) as shown in the other tabs.

1. Use the following AWS CLI command to create a gateway with interceptors configured to pass request headers:

   ```
   aws bedrock-agentcore-control create-gateway \
     --name my-gateway-with-headers \
     --role-arn arn:aws:iam::123456789012:role/my-gateway-service-role \
     --protocol-type MCP \
     --authorizer-type CUSTOM_JWT \
     --authorizer-configuration '{
       "customJWTAuthorizer": {
         "discoveryUrl": "https://cognito-idp.us-west-2.amazonaws.com/some-user-pool/.well-known/openid-configuration",
         "allowedClients": ["clientId"]
       }
     }' \
     --interceptor-configurations '[{
         "interceptor": {
             "lambda": {
               "arn":"arn:aws:lambda:us-west-2:123456789012:function:my-interceptor-lambda"
             }
         },
         "interceptionPoints": ["REQUEST", "RESPONSE"],
         "inputConfiguration": {
           "passRequestHeaders": true
         }
     }]'
   ```

1. Use the following Python code with the AWS Python SDK (Boto3) to create a gateway with interceptors configured to pass request headers:

   ```
   import boto3
   
   # Initialize the AgentCore client
   client = boto3.client('bedrock-agentcore-control')
   
   # Create a gateway
   gateway = client.create_gateway(
     name="my-gateway-with-headers",
     roleArn="arn:aws:iam::123456789012:role/my-gateway-service-role",
     protocolType="MCP",
     authorizerType="CUSTOM_JWT",
     authorizerConfiguration={
         "customJWTAuthorizer": {
             "discoveryUrl": "https://cognito-idp.us-west-2.amazonaws.com/some-user-pool/.well-known/openid-configuration",
             "allowedClients": ["clientId"]
         }
     },
     interceptorConfigurations=[{
         "interceptor": {
             "lambda": {
               "arn":"arn:aws:lambda:us-west-2:123456789012:function:my-interceptor-lambda"
             }
         },
         "interceptionPoints": ["REQUEST", "RESPONSE"],
         "inputConfiguration": {
           "passRequestHeaders": True
         }
     }]
   )
   
   print(f"MCP Endpoint: {gateway['gatewayUrl']}")
   ```

## Updating interceptor configurations
<a name="gateway-interceptors-configuration-update"></a>

You can update existing gateway interceptor configurations to modify the `passRequestHeaders` setting or other parameters using the update gateway API operations.

# Examples
<a name="gateway-interceptors-examples"></a>

The following example shows a Python Lambda function that can handle both REQUEST and RESPONSE interceptor types. The REQUEST interceptor logs the MCP method and passes the request through unchanged, while the RESPONSE interceptor passes the response through unchanged.

## Pass-through interceptor
<a name="gateway-interceptors-examples-passthrough"></a>

This example demonstrates a simple interceptor that logs the MCP method for REQUEST interceptors and passes all requests and responses through unchanged:

```
import json
import logging

# Configure logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    """
    Lambda function that handles both REQUEST and RESPONSE interceptor types.

    For REQUEST interceptors: logs the MCP method and passes request through unchanged
    For RESPONSE interceptors: passes response through unchanged
    """
    # Extract the MCP data from the event
    mcp_data = event.get('mcp', {})

    # Check if this is a REQUEST or RESPONSE interceptor based on presence of gatewayResponse
    if 'gatewayResponse' in mcp_data and mcp_data['gatewayResponse'] != None:
        # This is a RESPONSE interceptor
        logger.info("Processing RESPONSE interceptor - passing through unchanged")

        # Pass through the original request and response unchanged
        response = {
          "interceptorOutputVersion": "1.0",
          "mcp": {
              "transformedGatewayResponse": {
                  "body": mcp_data.get('gatewayResponse', {}).get('body', {}),
                  "statusCode": mcp_data.get('gatewayResponse', {}).get('statusCode', 200)
              }
          }
        }
    else:
        # This is a REQUEST interceptor
        gateway_request = mcp_data.get('gatewayRequest', {})
        request_body = gateway_request.get('body', {})
        mcp_method = request_body.get('method', 'unknown')

        # Log the MCP method
        logger.info(f"Processing REQUEST interceptor - MCP method: {mcp_method}")

        # Pass through the original request unchanged
        response = {
          "interceptorOutputVersion": "1.0",
          "mcp": {
              "transformedGatewayRequest": {
                  "body": request_body,
              }
          }
        }

    return response
```

This Lambda function can be configured as both a REQUEST and RESPONSE interceptor. When configured as a REQUEST interceptor, it will log the MCP method from the incoming request. When configured as a RESPONSE interceptor, it will simply pass the response through unchanged. Both interceptor types return the original data without modification, making this a "pass-through" interceptor.

# Header propagation with Gateway
<a name="gateway-headers"></a>

## What is header and query parameter propagation
<a name="gateway-headers-overview"></a>

Header propagation refers to the systematic forwarding of selective HTTP headers from incoming requests through your gateway to configured targets, and selective forwarding of response headers back to the client. Similar to header propagation, query parameter propagation enables forwarding of URL query parameters from incoming requests to configured targets. This feature can be used for use cases where you need to exchange context, authentication, tracing, and other critical information between client and targets. The pre-allowlisted headers provided in the invoke tool call to gateway or sent from custom interceptor lambda, will be forwarded to the specific targets.

This feature operates as a shared responsibility model:
+  AWS responsibility is to securely pass the headers and query parameters which you have allowlisted for your targets.
+ Your responsibility is to exercise caution and **only allowlist those headers for propagation which are essential to the targets** , ensuring they meet your security and functional requirements.

### Header restrictions
<a name="gateway-headers-restrictions"></a>

To maintain security and prevent exposure of sensitive information, the following headers are restricted and cannot be configured for propagation:


|  | 
| --- |
|  Authorization\$1  | 
|  Proxy-Authorization  | 
|  WWW-Authenticate  | 
|  Accept  | 
|  Accept-Charset  | 
|  Accept-Encoding  | 
|  Accept-Language  | 
|  Content-Type  | 
|  Content-Length  | 
|  Content-Encoding  | 
|  Content-Language  | 
|  Content-Location  | 
|  Content-Range  | 
|  Cache-Control  | 
|  ETag  | 
|  Expires  | 
|  If-Match  | 
|  If-Modified-Since  | 
|  If-None-Match  | 
|  If-Range  | 
|  If-Unmodified-Since  | 
|  Last-Modified  | 
|  Pragma  | 
|  Vary  | 
|  Connection  | 
|  Keep-Alive  | 
|  Proxy-Connection  | 
|  Upgrade  | 
|  Host  | 
|  User-Agent  | 
|  Referer  | 
|  From  | 
|  Range  | 
|  Accept-Ranges  | 
|  Transfer-Encoding  | 
|  TE  | 
|  Trailer  | 
|  Server  | 
|  Date  | 
|  Location  | 
|  Retry-After  | 
|  Set-Cookie  | 
|  Cookie  | 
|  Content-Security-Policy  | 
|  Content-Security-Policy-Report-Only  | 
|  Strict-Transport-Security  | 
|  X-Content-Type-Options  | 
|  X-Frame-Options  | 
|  X-XSS-Protection  | 
|  Referrer-Policy  | 
|  Permissions-Policy  | 
|  Cross-Origin-Embedder-Policy  | 
|  Cross-Origin-Opener-Policy  | 
|  Cross-Origin-Resource-Policy  | 
|  Access-Control-Allow-Origin  | 
|  Access-Control-Allow-Methods  | 
|  Access-Control-Allow-Headers  | 
|  Access-Control-Allow-Credentials  | 
|  Access-Control-Expose-Headers  | 
|  Access-Control-Max-Age  | 
|  Access-Control-Request-Method  | 
|  Access-Control-Request-Headers  | 
|  Origin  | 
|  Accept-CH  | 
|  Accept-CH-Lifetime  | 
|  DPR  | 
|  Width  | 
|  Viewport-Width  | 
|  Downlink  | 
|  ECT  | 
|  RTT  | 
|  Save-Data  | 
|  Clear-Site-Data  | 
|  Feature-Policy  | 
|  Expect-CT  | 
|  Public-Key-Pins  | 
|  Public-Key-Pins-Report-Only  | 
|  X-Forwarded-For  | 
|  X-Forwarded-Host  | 
|  X-Forwarded-Proto  | 
|  X-Real-IP  | 
|  X-Requested-With  | 
|  X-CSRF-Token  | 
|  CF-Ray  | 
|  CF-Connecting-IP  | 
|  X-Amz-Cf-Id  | 
|  X-Cache  | 
|  X-Served-By  | 
|  :method  | 
|  :path  | 
|  :scheme  | 
|  :authority  | 
|  :status  | 
|  Link  | 
|  Sec-WebSocket-Key  | 
|  Sec-WebSocket-Accept  | 
|  Sec-WebSocket-Version  | 
|  Sec-WebSocket-Protocol  | 
|  Sec-WebSocket-Extensions  | 
+ Authorization header cannot be allowlisted during target creation. However it will be forwarded to the target when provided by an interceptor lambda. See [Header propagation from interceptor lambda](#gateway-headers-interceptor-propagation) for details.

**Important**  
In addition to restricted headers mentioned above, headers provided in API keys and REST API schema cannot be configured for header propagation.

Additional validation rules apply to allowed headers:
+ Maximum of 10 request headers, 10 response headers, and 10 query parameters per target to prevent abuse and maintain performance
+ Header names must contain only alphanumeric characters, hyphens, and underscores (regex: `^[a-zA-Z0-9_-]+$` )
+ Header values are limited to 4KB maximum to prevent memory exhaustion
+ Header values must contain only printable ASCII characters
+ Headers starting with `X-Amzn-` are prohibited (except for X-Amzn-Bedrock-AgentCore-Runtime-Custom-\$1 headers)

## Configuring header and query parameter propagation
<a name="gateway-headers-configuration"></a>

You can configure header and query parameters at the target level when creating or updating gateway targets. Headers and query parameters are specified per target, ensuring that each target receives only the headers it needs.

### Target-level configuration
<a name="gateway-headers-target-level"></a>

Configure header propagation by adding `allowedRequestHeaders` , `allowedResponseHeaders` , and `allowedQueryParameters` fields to your target’s `metadataConfiguration` :

```
{
    "name": "my-target",
    "description": "my target description",
    "credentialProviderConfigurations": [{
        "credentialProviderType": "OAUTH",
        "credentialProvider": {
            "oauthCredentialProvider": {
                "providerArn": "arn:aws:bedrock-agentcore:us-west-2:123456789012:credential-provider/example",
                "scopes": []
            }
        }
    }],
    "targetConfiguration": {
        "mcp": {
            "mcpServer": {
                "endpoint": "https://example.com/mcp"
            }
        }
    },
    "metadataConfiguration": {
        "allowedRequestHeaders": [
            "request-header"
        ],
        "allowedResponseHeaders": [
            "response-header"
        ],
        "allowedQueryParameters": [
            "query-param"
        ]
    }
}
```

Using the Python SDK:

```
import boto3

# Initialize the client
client = boto3.client('bedrock-agentcore', region_name='us-west-2')

# Create target with header propagation
response = client.create_gateway_target(
    gatewayId='gateway-123',
    name='mcp-target-with-headers',
    description='MCP target with header propagation',
    targetConfiguration={
        'mcp': {
            'mcpServer': {
                'endpoint': 'https://example.com/mcp'
            }
        }
    },
    metadataConfiguration={
        'allowedRequestHeaders': ['x-correlation-id', 'x-tenant-id'],
        'allowedResponseHeaders': ['x-rate-limit-remaining'],
        'allowedQueryParameters': ['version']
    }
)
```

## Header propagation from interceptor lambda
<a name="gateway-headers-interceptor-propagation"></a>

When using custom interceptor lambdas with your gateway, you can dynamically control header propagation by including headers in your interceptor lambda response.

### How interceptor header propagation works
<a name="gateway-headers-interceptor-overview"></a>

Interceptor lambdas can influence header propagation in the following ways:
+  **Authorization header override:** The `Authorization` header from the interceptor lambda response is automatically propagated to the target. While the `Authorization` header cannot be configured in the target’s allowlist, it will be forwarded to the target when provided by an interceptor lambda.

  For example, if you have added a credential provider to the target which provides an authorization token like `Authorization: Bearer client-token` and the interceptor lambda provides `Authorization: Bearer refreshed-token` , the value `Bearer refreshed-token` from the interceptor lambda will be forwarded to the target.
+  **Custom header injection:** Additional headers from the interceptor lambda response are merged with the configured target header allowlist.
+  **Header precedence:** Interceptor lambda-provided headers take precedence over client-provided headers in case of conflicts.

  For example, if you allowlist header `x-tenant-id` in the target configuration, and the incoming request provides `x-tenant-id: tenant-123` while the interceptor lambda provides `x-tenant-id: tenant-456` , the value `tenant-456` from the interceptor lambda will be forwarded to the target.
+  **Security validation:** All lambda-provided headers are subject to the same validation rules as configured headers. Except for Authorization header, all other headers must be allowlisted during target creation for them to be forwarded to the targets.

### Implementing header propagation in interceptors
<a name="gateway-headers-interceptor-implementation"></a>

Configure your interceptor lambda to return headers that should be propagated to the target:

```
import json
import boto3

def lambda_handler(event, context):
    # Extract request context
    request_context = event.get('requestContext', {})
    user_identity = request_context.get('identity', {})

    # Fetch credentials from secure store (example)
    credentials_client = boto3.client('secretsmanager')
    secret = credentials_client.get_secret_value(
        SecretId=f"mcp-credentials/{user_identity.get('userId')}"
    )

    credentials = json.loads(secret['SecretString'])

    # Return response with headers to propagate
    return {
        "interceptorOutputVersion": "1.0",
        "mcp": {
            "transformedGatewayRequest": {
                "headers": {
                    # Authorization header will be propagated automatically
                    "Authorization": f"Bearer {credentials['access_token']}",
                    # Custom headers (must be in target allowlist)
                    "x-tenant-id": user_identity.get('tenantId'),
                    "x-correlation-id": request_context.get('requestId')
                },
                "body": event['mcp']['gatewayRequest']['body']
            }
        }
    }
```

 **Common use cases for interceptor header propagation include:** 

 **Credential fetching**   
Retrieve short-lived tokens from secure vaults and inject them as Authorization headers, preventing credential exposure in client applications.

 **Context injection**   
Add tenant identifiers, organization context, or user attributes derived from authenticated user claims rather than trusting client-provided values.

 **Header transformation**   
Transform or sanitize headers based on business logic, compliance requirements, or security policies before they reach the target.

 **Dynamic routing**   
Inject routing hints, feature flags, or A/B testing headers based on real-time analysis of user attributes or system state.

### Security considerations
<a name="gateway-headers-interceptor-security"></a>

When implementing header propagation with interceptor lambdas, follow these security best practices:
+  **Validate header sources:** Only propagate headers that are explicitly configured in your target allowlist or returned by trusted interceptor lambdas
+  **Sanitize sensitive data:** Remove or mask PII and sensitive information before forwarding headers to external MCP servers
+  **Use least privilege:** Configure interceptor lambda IAM roles with minimal permissions required for credential fetching and context retrieval
+  **Implement audit logging:** Log header transformations and credential fetching activities for security monitoring and compliance
+  **Validate header content:** Ensure that lambda-generated headers meet the same validation rules as configured headers

## Best practices
<a name="gateway-headers-best-practices"></a>

Follow these best practices when implementing header propagation:

 **Use target-specific configuration**   
Configure headers per target rather than globally. Different targets may require different headers, and target-specific configuration provides better security isolation.

 **Minimize header count**   
Only propagate headers that are actually needed by the target. Excessive headers increase request size and processing overhead.

 **Use semantic header names**   
Choose descriptive header names that clearly indicate their purpose, such as `x-correlation-id` for tracing or `x-tenant-id` for multi-tenancy.

 **Implement proper error handling**   
Handle cases where required headers are missing or invalid. Consider whether to fail the request or provide default values.

 **Monitor header usage**   
Use gateway observability features to monitor which headers are being propagated and identify any issues with header validation or processing.

 **Test header propagation**   
Verify that headers are correctly propagated to your targets during development and testing. Use tools like request logging or debugging endpoints to validate header flow.

# Performance optimization
<a name="gateway-advanced-performance"></a>

To optimize the performance of your Gateway implementations, consider the following best practices:

 **Minimize tool latency**   
The overall latency of your gateway is largely determined by the latency of the underlying tools. To minimize latency:  
+ Use Lambda functions in the same region as your gateway
+ Optimize your Lambda functions for fast cold starts
+ Use provisioned concurrency for Lambda functions that require low latency
+ Ensure that REST APIs have low latency and high availability

 **Use efficient tool schemas**   
Well-designed tool schemas can improve the performance of your gateway:  
+ Keep schemas as simple as possible
+ Use appropriate data types for parameters
+ Include clear descriptions for parameters to help agents use the tools correctly
+ Use required fields to ensure that agents provide necessary parameters

 **Enable semantic search**   
Semantic search helps agents find the right tools for their tasks, improving the overall performance of your agent-gateway interactions. Enable semantic search when creating your gateway:  

```
import boto3

# Initialize the AgentCore control client
client = boto3.client('bedrock-agentcore-control', region_name="us-west-2")

# Create a gateway with semantic search enabled
gateway = client.create_gateway(
    name="semantic-search-gateway",
    description="A gateway with semantic search enabled",
    protocolConfiguration={
        "mcp": {
            "searchType": "SEMANTIC"
        }
    }
)
```

 **Monitor and optimize**   
Use the observability features described in the previous section to monitor the performance of your gateway and identify opportunities for optimization:  
+ Set up CloudWatch alarms for key metrics
+ Analyze logs to identify patterns and issues
+ Regularly review performance metrics and make adjustments as needed