> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aviatrix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure MCP Server Egress Containment with Obot on EKS

> Step-by-step instructions for enabling Aviatrix DCF egress containment for MCP servers managed by Obot on Amazon EKS: cluster onboarding, DCF policy setup, and per-server domain declaration.

When complete, every MCP server Obot deploys on EKS will automatically receive a
workload-scoped containment policy. Egress domain declarations are managed
through Obot's server catalog. The Aviatrix network policy controller translates
those declarations into `FirewallPolicy` CRDs, enforced at the VPC boundary by
the Aviatrix spoke gateway. When a server is removed, its policy is removed with
it.

For the business case and architecture overview, see <a href={"/docs/enterprise/" + "10.1" +
"/solutions/security-for-ai/mcp-server-egress-security"}>MCP Server Security
Through Containment Architecture</a>. For the complete Terraform reference
implementation, see the
[obot-mcp-egress-aws blueprint](https://github.com/AviatrixSystems/aviatrix-blueprints/tree/main/blueprints/obot-mcp-egress-aws).

<img src="https://mintcdn.com/aviatrix-14b37c43/SoyeMF4MpIwhhamV/images/guides/security/obot-enforcement-architecture-technical.png?fit=max&auto=format&n=SoyeMF4MpIwhhamV&q=85&s=e2f163b359a370eb6549ab4b6158a6a3" alt="Reference architecture: namespace layout, NPC controller, FirewallPolicy CRDs, and Aviatrix three-tier enforcement" width="2382" height="3369" data-path="images/guides/security/obot-enforcement-architecture-technical.png" />

## Prerequisites

* Aviatrix Controller 8.2 or later
* CoPilot 4.31 or later
* <a href={"/docs/enterprise/" + "10.1" + "/guides/security/dcf/dcf-enable-feature"}>DCF for Kubernetes enabled</a>
  with **Log Enrichment** turned on
* A <a href={"/docs/enterprise/" + "10.1" +
  "/guides/platform-administration/cloud-account-create"}>cloud account
  onboarded in CoPilot</a> for the AWS account hosting the EKS cluster
* [Obot](https://obot.ai) 0.21.0 or later (earlier versions do not include
  MCPNetworkPolicy support)

## Procedure

<Tabs>
  <Tab title="CoPilot UI">
    <Steps>
      <Step title="Deploy an Aviatrix spoke gateway in the cluster VPC">
        An Aviatrix spoke gateway in the same VPC as your Kubernetes cluster is the
        enforcement point where the Aviatrix controller applies containment policy to
        MCP server egress traffic.

        1. In CoPilot, navigate to **Cloud Fabric > Gateways > Spoke Gateways** and
           click **+ Spoke Gateway**.

        2. Provide the following details:

           | Parameter         | Value                                                                           |
           | ----------------- | ------------------------------------------------------------------------------- |
           | **Name**          | A name for the gateway, for example `mcp-containment-spoke`                     |
           | **Cloud**         | The cloud provider hosting the Kubernetes cluster                               |
           | **Account**       | The cloud account for the cluster                                               |
           | **Region**        | The region where the cluster runs                                               |
           | **VPC/VNet**      | The VPC or VNet where the cluster nodes run — this must match the cluster's VPC |
           | **Instance Size** | Select an instance size appropriate for your environment                        |

        3. In the **Instances** section, select a subnet in the cluster VPC for the
           gateway instance.

        4. Click **Save**.

        Wait for the gateway status to show **Up** before continuing. You can monitor
        progress at **CoPilot > Monitor > Notifications > Tasks**.
      </Step>

      <Step title="Confirm network topology">
        The spoke gateway must be deployed in the **public subnet**, with the EKS
        cluster in the **private subnet** of the same VPC. Pod egress routes via the
        node subnet route table to the gateway. Confirm your VPC CIDR, subnet
        assignments, and gateway placement before continuing.

        <img src="https://mintcdn.com/aviatrix-14b37c43/SoyeMF4MpIwhhamV/images/guides/security/obot-mcp-egress-aws-topology.svg?fit=max&auto=format&n=SoyeMF4MpIwhhamV&q=85&s=a6c56572a9074178dc50e169a20dae04" alt="EKS VPC topology: private subnets with EKS cluster, public subnet with Aviatrix spoke gateway, egress routing path" width="1200" height="560" data-path="images/guides/security/obot-mcp-egress-aws-topology.svg" />
      </Step>

      <Step title="Install the Aviatrix Kubernetes Firewall Helm chart">
        The Aviatrix Kubernetes Firewall Helm chart deploys the in-cluster components
        that allow the Aviatrix controller to reconcile and enforce containment policies
        on the cluster.

        Run the following command against the target cluster, replacing
        `<chart-version>` with the current chart version from the
        [Aviatrix k8s-firewall-charts repository](https://aviatrixsystems.github.io/k8s-firewall-charts):

        ```bash theme={null}
        helm install --repo https://aviatrixsystems.github.io/k8s-firewall-charts --version <chart-version> k8s-firewall k8s-firewall
        ```
      </Step>

      <Step title="Verify Aviatrix CRD installation">
        The Helm chart installs two Aviatrix CRDs on the cluster. Verify both are
        present before continuing:

        ```bash theme={null}
        kubectl get crds | grep aviatrix
        ```

        Expected output:

        ```
        firewallpolicies.networking.aviatrix.com        <timestamp>
        webgrouppolicies.networking.aviatrix.com        <timestamp>
        ```

        If either CRD is missing, re-run the Helm installation and check the output for
        errors.
      </Step>

      <Step title="Onboard the Kubernetes cluster to Aviatrix DCF">
        1. Navigate to **Cloud Resources > Cloud Assets > Kubernetes Clusters**.

        2. Click **Onboard** next to the target cluster.

        3. Choose the appropriate access method for your cloud provider:
           * **AWS (EKS):** Select **Terraform** or **Command Line**, apply the
             generated access entry and RBAC configuration, check the confirmation box,
             then click **Onboard**.
           * **Azure (AKS):** Select **Permissions on Cloud Account** if the Aviatrix
             service principal has the required permissions, or **Kubeconfig File** to
             upload a kubeconfig. Click **Onboard**.

        4. Wait for the cluster status to show **Yes** (green) on the Kubernetes
           Clusters tab.

        <Note>
          EKS clusters require a `view-nodes` ClusterRole so the Controller can discover
          node metadata. The CoPilot onboarding dialog generates the required YAML
          automatically — apply it before clicking **Onboard**.
        </Note>
      </Step>

      <Step title="Configure V1 DCF policies and default deny">
        Before enabling default deny, configure V1 infrastructure permit rules so the
        cluster continues to function. These rules allow node-level traffic (ECR image
        pulls, AWS platform services) that must be permitted independently of per-pod
        MCP containment policies.

        **Create SmartGroups**

        Navigate to **Security > Distributed Cloud Firewall > Groups > SmartGroups** and
        create:

        **`eks-vpc`**: source for infrastructure rules

        * Match type: **CIDR**
        * CIDR: the EKS VPC CIDR (for example, `10.0.0.0/16`)

        **`obot-system`**: Obot orchestration pods

        * Match type: **Kubernetes**
        * Namespace: the Obot system namespace (for example, `obot-system`)

        **Create WebGroups**

        Navigate to **Security > Distributed Cloud Firewall > Groups > WebGroups** and
        create:

        **`eks-infra-egress`**: EKS infrastructure domains required by all nodes and
        pods

        | Category       | Domains                                                                                    |
        | -------------- | ------------------------------------------------------------------------------------------ |
        | EKS            | `*.eks.amazonaws.com`                                                                      |
        | ECR            | `*.ecr.aws`, `*.dkr.ecr.*.amazonaws.com`, `ecr.*.amazonaws.com`, `api.ecr.*.amazonaws.com` |
        | S3             | `*.s3.*.amazonaws.com`, `*.s3.amazonaws.com`                                               |
        | EC2            | `ec2.*.amazonaws.com`                                                                      |
        | SSM            | `ssm.*.amazonaws.com`                                                                      |
        | STS            | `sts.amazonaws.com`, `sts.*.amazonaws.com`                                                 |
        | OCI registries | `ghcr.io`, `*.ghcr.io`, `pkg-containers.githubusercontent.com`                             |
        | Obot NPC chart | `charts.obot.ai`                                                                           |

        **`obot-pod-egress`**: domains required by Obot orchestration pods

        * `charts.obot.ai`, `api.anthropic.com`, `github.com`, `*.github.com`,
          `raw.githubusercontent.com`, `*.githubusercontent.com`

        **Create V1 policy rules**

        Navigate to **Security > Distributed Cloud Firewall > Policy** and create the
        following rules in priority order:

        | Priority | Name               | Source        | Destination                            | Protocol | Port | Action |
        | -------- | ------------------ | ------------- | -------------------------------------- | -------- | ---- | ------ |
        | 1        | `eks-infra-egress` | `eks-vpc`     | Anywhere, WebGroup: `eks-infra-egress` | TCP      | 443  | PERMIT |
        | 2        | `obot-pod-egress`  | `obot-system` | Anywhere, WebGroup: `obot-pod-egress`  | TCP      | 443  | PERMIT |

        **Enable default deny**

        Navigate to **Security > Distributed Cloud Firewall > Policy > Default Action**
        and set the default action to **Deny** with **Logging** enabled.

        <Note>
          Default deny must be set at POST\_RULES level, not as a V1 policy rule. A V1
          deny evaluates before the Kubernetes CRD enforcement block (K8S\_POLICY\_LIST).
          Setting deny in V1 would block all `FirewallPolicy` PERMIT rules generated by
          the network policy controller.
        </Note>
      </Step>

      <Step title="Configure Obot with the Aviatrix network policy provider">
        Add the Aviatrix network policy provider configuration to your Obot Helm
        release. Obot's PostStart hook reads these values and deploys the
        `aviatrix-network-policy-controller` chart into the cluster automatically.

        ```bash theme={null}
        helm upgrade obot obot \
          --repo https://charts.obot.ai \
          --namespace obot-system \
          --reuse-values \
          --set config.OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_REPO=https://charts.obot.ai \
          --set config.OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_NAME=aviatrix-network-policy-controller \
          --set config.OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_VERSION=<npc-version> \
          --set config.OBOT_SERVER_MCPDEFAULT_DENY_ALL_EGRESS=true
        ```

        Replace `<npc-version>` with the current chart version from
        [charts.obot.ai](https://charts.obot.ai).

        `OBOT_SERVER_MCPDEFAULT_DENY_ALL_EGRESS=true` ensures any MCP server deployed
        without declared egress domains starts with zero outbound access. Without this
        setting, servers with no egress declaration inherit the environment's default
        posture.

        After the upgrade, verify the network policy controller is running:

        ```bash theme={null}
        kubectl get pods -n obot-mcp
        ```

        <Note>
          On EKS, Kubernetes label-based SmartGroups (used by `FirewallPolicy`
          selectors) may register as **Partial** in the Aviatrix controller due to a
          current limitation in the asset discovery watcher. The `FirewallPolicy` CRDs
          are created and the default deny rule provides the baseline block. If strict
          per-pod permit enforcement is required before this limitation is resolved,
          contact Aviatrix support for the CIDR-based enforcement workaround.
        </Note>
      </Step>

      <Step title="Declare egress destinations per MCP server">
        Egress destinations are declared in the Obot server catalog, not in Kubernetes
        manifests. Each MCP server's allowed domains are part of its configuration in
        Obot.

        **Via the Obot admin UI:**

        When adding or editing an MCP server in the Obot admin catalog, set the **Egress
        Domains** field to a list of permitted destinations, for example
        `api.github.com` and `*.githubusercontent.com`.

        **Via the Obot API:**

        ```bash theme={null}
        curl -X POST http://<obot-host>/api/mcp-servers \
          -H "Content-Type: application/json" \
          -d '{
            "manifest": {
              "name": "github",
              "runtime": "npx",
              "npxConfig": {
                "package": "@modelcontextprotocol/server-github",
                "egressDomains": ["api.github.com", "*.githubusercontent.com"]
              }
            }
          }'
        ```

        Domain rules:

        * Bare hostname or leading wildcard only: `api.example.com` or `*.example.com`
        * No protocols, paths, ports, or IP addresses
        * `localhost`, `*.svc`, and bare `*` are rejected
        * Empty `egressDomains` with `OBOT_SERVER_MCPDEFAULT_DENY_ALL_EGRESS=true`
          results in zero outbound access for that server

        For the full egressDomains specification, see the
        [Obot MCP server egress control documentation](https://docs.obot.ai/configuration/mcp-server-egress-control/).
        Egress enforcement applies to HTTPS on TCP port 443 only; connections on other
        ports are blocked regardless of domain declarations.

        When Obot deploys the server, the network policy controller generates a
        `FirewallPolicy` CRD targeting that server's pods. Inspect generated policies:

        ```bash theme={null}
        kubectl get firewallpolicies -n obot-mcp
        ```

        Generated policy names follow the pattern `obot-<mnp-id>-fw`. Pod labels use an
        internal server ID (`app: <server-id>`, for example `app: ms16mfzb`), not the
        human-readable server name. This is expected.
      </Step>

      <Step title="Verify containment">
        1. Deploy an MCP server through Obot and confirm a `FirewallPolicy` was
           generated:

           ```bash theme={null}
           kubectl get firewallpolicies -n obot-mcp
           ```

        2. Check that the policy was applied successfully:

           ```bash theme={null}
           kubectl get events -n obot-mcp
           ```

           A successful apply produces an event with `Reason: UpdatePolicyListSuccess`.

        3. In CoPilot, navigate to **Security > Distributed Cloud Firewall** and select
           the cluster. Confirm the server's containment policy appears with the correct
           egress allow-list.

        4. To confirm egress blocking is active, attempt a connection from an MCP server
           pod to a destination not in its declared domains and verify it is denied. In
           CoPilot per-connection logs, a denied connection appears with **Action:
           Deny**. If no entry appears within a few seconds, verify that the spoke
           gateway is in the egress data path for the cluster's node subnet.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Terraform">
    <Steps>
      <Step title="Deploy an Aviatrix spoke gateway in the cluster VPC">
        An Aviatrix spoke gateway must be deployed in the same VPC as the target
        Kubernetes cluster. If a spoke gateway is already deployed in the cluster VPC,
        proceed to the next step.

        ```hcl theme={null}
        resource "aviatrix_spoke_gateway" "mcp_containment" {
          cloud_type   = var.cloud_type
          account_name = var.account_name
          gw_name      = "mcp-containment-spoke"
          vpc_id       = var.cluster_vpc_id
          vpc_reg      = var.region
          gw_size      = var.gateway_size
          subnet       = var.subnet_cidr
        }
        ```
      </Step>

      <Step title="Confirm network topology">
        The spoke gateway deploys into the public subnet; the EKS cluster sits in the
        private subnet of the same VPC. Use the diagram below to confirm VPC CIDR and
        subnet variable values before applying.

        <img src="https://mintcdn.com/aviatrix-14b37c43/SoyeMF4MpIwhhamV/images/guides/security/obot-mcp-egress-aws-topology.svg?fit=max&auto=format&n=SoyeMF4MpIwhhamV&q=85&s=a6c56572a9074178dc50e169a20dae04" alt="EKS VPC topology: private subnets with EKS cluster, public subnet with Aviatrix spoke gateway, egress routing path" width="1200" height="560" data-path="images/guides/security/obot-mcp-egress-aws-topology.svg" />
      </Step>

      <Step title="Install the Aviatrix Kubernetes Firewall Helm chart">
        The Aviatrix Kubernetes Firewall Helm chart deploys the in-cluster components
        that allow the Aviatrix controller to reconcile and enforce containment policies
        on the cluster.

        Run the following command against the target cluster, replacing
        `<chart-version>` with the current chart version from the
        [Aviatrix k8s-firewall-charts repository](https://aviatrixsystems.github.io/k8s-firewall-charts):

        ```bash theme={null}
        helm install --repo https://aviatrixsystems.github.io/k8s-firewall-charts --version <chart-version> k8s-firewall k8s-firewall
        ```
      </Step>

      <Step title="Verify Aviatrix CRD installation">
        The Helm chart installs two Aviatrix CRDs on the cluster. Verify both are
        present before continuing:

        ```bash theme={null}
        kubectl get crds | grep aviatrix
        ```

        Expected output:

        ```
        firewallpolicies.networking.aviatrix.com        <timestamp>
        webgrouppolicies.networking.aviatrix.com        <timestamp>
        ```

        If either CRD is missing, re-run the Helm installation and check the output for
        errors.
      </Step>

      <Step title="Verify EKS cluster discovery">
        EKS clusters are discovered automatically by the Aviatrix controller via CSP
        account scan. Do not create an `aviatrix_kubernetes_cluster` resource for EKS;
        explicit registration conflicts with the auto-discovered entry and returns
        HTTP 409.

        Confirm discovery in CoPilot under **Cloud Resources > Cloud Assets > Kubernetes
        Clusters**. The cluster appears automatically once the CSP account has synced.
        If it does not appear within a few minutes, verify the AWS account is onboarded
        in CoPilot and the controller IAM role has `eks:ListClusters` and
        `eks:DescribeCluster` permissions.
      </Step>

      <Step title="Enable Kubernetes enforcement feature flags">
        Five controller feature flags are required for Kubernetes CRD enforcement. The
        `dcf_multi_policies` flag activates the K8S\_POLICY\_LIST evaluation block;
        `k8s_discovery` and `log_enrichment` enable pod identity resolution and
        per-connection logging.

        ```hcl theme={null}
        resource "null_resource" "k8s_dcf_features" {
          triggers = { controller_ip = var.controller_ip }

          provisioner "local-exec" {
            command     = <<-EOT
              set -euo pipefail
              CID=$(curl -sk "https://$${CONTROLLER}/v2/api" \
                -d "action=login&username=$${USERNAME}&password=$${PASSWORD}" \
                | python3 -c "import sys,json; print(json.load(sys.stdin).get('CID',''))")
              if [ -z "$${CID}" ]; then echo "ERROR: controller login failed" >&2; exit 1; fi
              for feature in k8s k8s_dcf_policies dcf_multi_policies k8s_discovery log_enrichment; do
                curl -sk "https://$${CONTROLLER}/v2/api" \
                  --data-urlencode "action=enable_controller_feature" \
                  --data-urlencode "CID=$${CID}" \
                  --data-urlencode "feature=$${feature}" > /dev/null
              done
              echo "DCF feature flags enabled"
            EOT
            interpreter = ["/bin/bash", "-c"]
            environment = {
              CONTROLLER = var.controller_ip
              USERNAME   = var.controller_username
              PASSWORD   = var.controller_password
            }
          }

          depends_on = [aviatrix_distributed_firewalling_config.enabled]
        }
        ```
      </Step>

      <Step title="Configure V1 DCF policies and default deny">
        Create infrastructure SmartGroups, WebGroups, V1 permit rules, and the
        POST\_RULES default deny. Traffic evaluation order is: V1 policy list →
        K8S\_POLICY\_LIST (FirewallPolicy CRDs) → POST\_RULES (default deny). V1 rules
        permit cluster infrastructure traffic before the Kubernetes CRD block evaluates
        MCP server policies.

        ```hcl theme={null}
        # Allow the controller's Cloud Asset Inventory to sync Kubernetes workload metadata
        # before creating SmartGroups that use k8s selectors.
        resource "time_sleep" "cai_sync" {
          create_duration = "30s"
          depends_on      = [aviatrix_spoke_gateway.mcp_containment]
        }

        # SmartGroup: EKS VPC CIDR (source for infrastructure rules)
        resource "aviatrix_smart_group" "eks_vpc" {
          name = "eks-vpc"
          selector {
            match_expressions { cidr = var.vpc_cidr }
          }
          depends_on = [time_sleep.cai_sync]
        }

        # SmartGroup: Obot orchestration pods (K8s namespace selector)
        resource "aviatrix_smart_group" "obot_system" {
          name = "obot-system"
          selector {
            match_expressions {
              type          = "k8s"
              k8s_namespace = var.obot_namespace
            }
          }
          depends_on = [time_sleep.cai_sync]
        }

        # WebGroup: EKS infrastructure domains
        resource "aviatrix_web_group" "eks_infra_egress" {
          name = "eks-infra-egress"
          selector {
            match_expressions { snifilter = "*.eks.amazonaws.com" }
            match_expressions { snifilter = "*.ecr.aws" }
            match_expressions { snifilter = "*.dkr.ecr.*.amazonaws.com" }
            match_expressions { snifilter = "ecr.*.amazonaws.com" }
            match_expressions { snifilter = "api.ecr.*.amazonaws.com" }
            match_expressions { snifilter = "*.s3.*.amazonaws.com" }
            match_expressions { snifilter = "*.s3.amazonaws.com" }
            match_expressions { snifilter = "ec2.*.amazonaws.com" }
            match_expressions { snifilter = "ssm.*.amazonaws.com" }
            match_expressions { snifilter = "sts.amazonaws.com" }
            match_expressions { snifilter = "sts.*.amazonaws.com" }
            match_expressions { snifilter = "ghcr.io" }
            match_expressions { snifilter = "*.ghcr.io" }
            match_expressions { snifilter = "pkg-containers.githubusercontent.com" }
            match_expressions { snifilter = "charts.obot.ai" }
          }
        }

        # WebGroup: Obot orchestration pod domains
        resource "aviatrix_web_group" "obot_pod_egress" {
          name = "obot-pod-egress"
          selector {
            match_expressions { snifilter = "charts.obot.ai" }
            match_expressions { snifilter = "api.anthropic.com" }
            match_expressions { snifilter = "github.com" }
            match_expressions { snifilter = "*.github.com" }
            match_expressions { snifilter = "raw.githubusercontent.com" }
            match_expressions { snifilter = "*.githubusercontent.com" }
          }
        }

        # V1 policy list: infra permits evaluated before the K8S_POLICY_LIST block
        resource "aviatrix_distributed_firewalling_policy_list" "obot_mcp" {
          # P1: EKS infrastructure domain egress
          policies {
            name             = "eks-infra-egress"
            action           = "PERMIT"
            priority         = 1
            protocol         = "TCP"
            logging          = true
            port_ranges { lo = 443 }
            src_smart_groups = [aviatrix_smart_group.eks_vpc.uuid]
            dst_smart_groups = ["def000ad-0000-0000-0000-000000000001"] # Aviatrix anywhere group
            web_groups       = [aviatrix_web_group.eks_infra_egress.uuid]
          }

          # P2: Obot orchestration pod egress
          policies {
            name             = "obot-pod-egress"
            action           = "PERMIT"
            priority         = 2
            protocol         = "TCP"
            logging          = true
            port_ranges { lo = 443 }
            src_smart_groups = [aviatrix_smart_group.obot_system.uuid]
            dst_smart_groups = ["def000ad-0000-0000-0000-000000000001"] # Aviatrix anywhere group
            web_groups       = [aviatrix_web_group.obot_pod_egress.uuid]
          }

          depends_on = [aviatrix_distributed_firewalling_config.enabled]
        }
        ```

        <Note>
          Replace the placeholder UUID `def000ad-0000-0000-0000-000000000001` in the policy list above with the actual UUID for the **Aviatrix Anywhere** SmartGroup from your controller. Retrieve it from **Security > Distributed Cloud Firewall > Groups > SmartGroups** in CoPilot, or via the Aviatrix API.
        </Note>

        ```hcl theme={null}
        # Default deny at POST_RULES level.
        # POST_RULES evaluates after both V1 and K8S_POLICY_LIST. A V1 deny would
        # evaluate before K8S_POLICY_LIST and block all MCPNetworkPolicy PERMIT rules.
        resource "aviatrix_distributed_firewalling_default_action_rule" "deny_all" {
          action  = "DENY"
          logging = true

          depends_on = [aviatrix_distributed_firewalling_policy_list.obot_mcp]
        }
        ```
      </Step>

      <Step title="Configure Obot with the Aviatrix network policy provider">
        Add the Aviatrix network policy provider values to the Obot Helm release. Set
        `obot_version` to `0.21.0` or later. Replace `var.npc_chart_version` with the
        current chart version from [charts.obot.ai](https://charts.obot.ai).

        ```hcl theme={null}
        resource "helm_release" "obot" {
          name             = "obot"
          repository       = "https://charts.obot.ai"
          chart            = "obot"
          version          = var.obot_version  # minimum 0.21.0
          namespace        = var.obot_namespace
          reuse_values     = true

          values = [
            yamlencode({
              config = {
                OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_REPO    = "https://charts.obot.ai"
                OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_NAME    = "aviatrix-network-policy-controller"
                OBOT_SERVER_MCPNETWORK_POLICY_PROVIDER_CHART_VERSION = var.npc_chart_version
                OBOT_SERVER_MCPDEFAULT_DENY_ALL_EGRESS               = "true"
              }
            })
          ]

          depends_on = [
            helm_release.aviatrix_crds,  # CRDs must exist before NPC starts
            aviatrix_distributed_firewalling_policy_list.obot_mcp,
            aviatrix_distributed_firewalling_default_action_rule.deny_all,
            null_resource.k8s_dcf_features,
          ]
        }
        ```
      </Step>

      <Step title="Declare egress destinations per MCP server">
        Egress destinations are declared in the Obot server catalog, not in Kubernetes
        manifests. Set them via the Obot API when creating or updating a server:

        ```bash theme={null}
        curl -X POST http://<obot-host>/api/mcp-servers \
          -H "Content-Type: application/json" \
          -d '{
            "manifest": {
              "name": "github",
              "runtime": "npx",
              "npxConfig": {
                "package": "@modelcontextprotocol/server-github",
                "egressDomains": ["api.github.com", "*.githubusercontent.com"]
              }
            }
          }'
        ```

        Domain rules:

        * Bare hostname or leading wildcard only: `api.example.com` or `*.example.com`
        * No protocols, paths, ports, or IP addresses
        * `localhost`, `*.svc`, and bare `*` are rejected
        * Empty `egressDomains` with `OBOT_SERVER_MCPDEFAULT_DENY_ALL_EGRESS=true`
          results in zero outbound access for that server

        For the full egressDomains specification, see the
        [Obot MCP server egress control documentation](https://docs.obot.ai/configuration/mcp-server-egress-control/).
        Egress enforcement applies to HTTPS on TCP port 443 only; connections on other
        ports are blocked regardless of domain declarations.
      </Step>

      <Step title="Verify containment">
        1. Apply the Terraform configuration and confirm no errors in the output.

        2. Deploy an MCP server through Obot and confirm a `FirewallPolicy` was
           generated:

           ```bash theme={null}
           kubectl get firewallpolicies -n obot-mcp
           ```

        3. Check that the policy was applied successfully:

           ```bash theme={null}
           kubectl get events -n obot-mcp
           ```

           A successful apply produces an event with `Reason: UpdatePolicyListSuccess`.

        4. In CoPilot, navigate to **Security > Distributed Cloud Firewall** and select
           the cluster. Confirm the server's containment policy appears with the correct
           egress allow-list.

        5. To confirm egress blocking is active, attempt a connection from an MCP server
           pod to a destination not in its declared domains and verify it is denied. In
           CoPilot per-connection logs, a denied connection appears with **Action:
           Deny**. If no entry appears within a few seconds, verify that the spoke
           gateway is in the egress data path for the cluster's node subnet.
      </Step>
    </Steps>
  </Tab>
</Tabs>
