Example: Create and register an AWS collection agent with Terraform and Claude

Claude writes one Terraform configuration that creates the Monte Carlo deployment, deploys the agent in your AWS account and registers it. You run terraform apply; integrations are connected to the agent later.

A collection agent runs in your AWS account, so connections originate from your network and credentials can stay in your own secret store. In this example Claude writes a single Terraform configuration that does everything: it creates the deployment in Monte Carlo, deploys the agent with Monte Carlo's mcd-agent module, and registers it. You run terraform apply once. No integration is connected yet; the other examples connect integrations to an agent like this one.

Don't use Terraform? See Create and register an AWS collection agent with the AWS CLI: Claude creates the deployment and you deploy the agent with Monte Carlo's CloudFormation template.

WhoWhatHow
ClaudeRead your account and write the TerraformMCP tools, then files in your working directory
YouCreate the deployment, the agent and its registrationterraform apply
ClaudeConfirm the deployment is enabledget_deployment

Before you start

  • Claude with the Agent Toolkit plugin, signed in to the Monte Carlo MCP server. See Agent Toolkit.
  • Terraform 1.11 or later, with the Monte Carlo provider (monte-carlo-data/montecarlo) authenticated through your Monte Carlo CLI profile (~/.mcd/profiles.ini) or MCD_DEFAULT_API_ID / MCD_DEFAULT_API_TOKEN. montecarlo whoami shows which account the profile reaches.
  • AWS credentials for the account the agent runs in, able to create IAM roles, Lambda functions, security groups and S3 buckets.
  • The Collection AWS account ID from Settings → Account information (Monte Carlo's account, not yours).
  • For databases inside a VPC: at least two private subnets that can reach them and that have outbound internet access through NAT. Without subnets, the agent reaches only publicly accessible endpoints.

1. Ask Claude

/monte-carlo-onboarding we need a Monte Carlo collection agent in our AWS account, all with Terraform; we'll connect databases to it later

Claude reads your account first. If you already have an enabled AWS agent, it asks whether to reuse it: a second agent is only needed for a different AWS account, region, or a network the first one can't reach. It proposes one configuration with one apply, and asks for the region, the Collection AWS account ID, a deployment name, the private subnets, and where your Terraform state lives.

2. Answer Claude's questions

Answers:
- AWS account <account-id>, region <region>. Collection AWS account ID: <collection-aws-account-id>.
- We need a new agent for the databases in VPC <vpc-id>; run it in the private subnets <subnet-a> and <subnet-b> (they have a NAT route).
- Deployment name: <deployment-name>.
- I run Terraform myself; the Monte Carlo provider uses my default CLI profile. Write the files into the current directory.
- State: <your state backend>.

3. The Terraform Claude writes

Claude writes the files and checks them with terraform validate. No variable has a default, so a missing value stops terraform plan and names it. Nothing in these files, the plan or the state is secret.

versions.tf:

terraform {
  # Write-only arguments need Terraform 1.11 or later.
  required_version = ">= 1.11"

  required_providers {
    montecarlo = { source = "monte-carlo-data/montecarlo" }
    # 6.50 is the first with `secret_string_wo`, used when databases are connected later.
    aws = { source = "hashicorp/aws", version = ">= 6.50" }
  }
}

# Endpoint from the profile's mcd_api_endpoint when set; otherwise pass it in (no default).
variable "montecarlo_endpoint" {
  type    = string
  default = null # null means "use the profile's endpoint"; never a URL here
}

provider "montecarlo" {
  endpoint = var.montecarlo_endpoint
}

provider "aws" {
  region = var.aws_region
}

variables.tf:

variable "aws_region" { type = string }

# The AWS account the agent is deployed into; checked against the caller's identity.
variable "aws_account_id" { type = string }

# Account information → Collection → AWS account ID: Monte Carlo's account, not yours.
variable "monte_carlo_collection_account_id" { type = string }

variable "deployment_name" { type = string }

# Private subnets with a route to the databases (and a NAT route out).
variable "private_subnets" { type = list(string) }

main.tf:

data "aws_caller_identity" "current" {}

resource "montecarlo_deployment" "agent" {
  name             = var.deployment_name
  type             = "COLLECTION_AGENT"
  runtime_platform = "AWS"

  lifecycle {
    precondition {
      condition     = data.aws_caller_identity.current.account_id == var.aws_account_id
      error_message = "The AWS credentials in use are not for aws_account_id."
    }
    precondition {
      condition     = var.monte_carlo_collection_account_id != var.aws_account_id
      error_message = "monte_carlo_collection_account_id must be Monte Carlo's Collection AWS account ID, not the account the agent runs in."
    }
  }
}

module "mcd_agent" {
  source  = "monte-carlo-data/mcd-agent/aws"
  version = "1.0.7"

  region           = var.aws_region
  cloud_account_id = var.monte_carlo_collection_account_id
  external_id      = montecarlo_deployment.agent.aws_external_id
  private_subnets  = var.private_subnets
}

resource "montecarlo_aws_collection_agent" "agent" {
  deployment_id       = montecarlo_deployment.agent.id
  name                = var.deployment_name
  lambda_function_arn = module.mcd_agent.mcd_agent_function_arn
  role_arn            = module.mcd_agent.mcd_agent_invoker_role_arn
}

# Non-secret ids and names only.
output "created_ids" {
  value = {
    deployment = montecarlo_deployment.agent.id
    agent      = montecarlo_aws_collection_agent.agent.id
  }
}

output "agent" {
  value = {
    lambda_function_arn = module.mcd_agent.mcd_agent_function_arn
    invoker_role_arn    = module.mcd_agent.mcd_agent_invoker_role_arn
    security_group_name = module.mcd_agent.mcd_agent_security_group_name
    storage_bucket_name = module.mcd_agent.mcd_agent_storage_bucket_name
  }
}

The deployment's External ID goes straight from montecarlo_deployment into the module, so you never copy it by hand. The two preconditions stop the run before anything is created if your AWS credentials are for the wrong account, or if the Collection AWS account ID is your own.

Your values go in terraform.tfvars:

aws_region                        = "<region>"
aws_account_id                    = "<account-id>"
monte_carlo_collection_account_id = "<collection-aws-account-id>"
deployment_name                   = "<deployment-name>"
private_subnets                   = ["<subnet-a>", "<subnet-b>"]

4. Apply

montecarlo whoami
aws sts get-caller-identity --query Account --output text
terraform init
terraform plan -out=agent.tfplan
terraform apply agent.tfplan
terraform output created_ids

In this example the plan created 21 resources: the deployment, the agent's Lambda function, IAM roles, security group and S3 bucket, and the registration. The apply took about five minutes, most of it creating the Lambda function inside the VPC. The registration took about a minute, because Monte Carlo checks it can reach the agent before registering it. Send Claude the created_ids output; the ids aren't secret.

5. Claude confirms

Claude reads the deployment and agent and confirms both are enabled. The run ends with:

Created in this run (by your terraform apply, 21 resources)
  deployment    <id>  <deployment-name>  COLLECTION_AGENT/AWS  enabled=true
  agent         <id>  <deployment-name>  enabled=true
  credentials / warehouse / connection   none (databases to be connected later)

Pending on your side
  - Later: Secrets Manager secret ARNs for the databases to connect
  - Later: database security groups allow inbound traffic from the agent's security group

Cleanup if you abandon this: Terraform manages all of it, so don't use the delete tools.

To connect a database later, ask Claude to add it to this same configuration: a reference to its secret in your Secrets Manager, a read grant for the agent, then the warehouse and connection. See Onboard Snowflake through a new AWS agent, all with Terraform for what that looks like. Each database's security group also needs an inbound rule from the agent's security group (terraform output agent names it).

Removing the agent

Terraform manages everything here, so remove it with Terraform, not with Claude's delete tools. The agent's S3 bucket is versioned, and terraform destroy fails while it still holds old object versions: empty it first, then destroy. The steps are in Remove an integration and its agent. Deleting a Lambda function that runs inside a VPC can take 15–40 minutes while AWS releases its network interfaces.

Troubleshooting

SymptomCause and fix
The plan stops at a preconditionThe AWS credentials are for another account, or monte_carlo_collection_account_id is your own account ID. Fix the value and plan again.
montecarlo_deployment fails with User is not authorized … explicit deny (sometimes reported as no value given for required property type)The provider is calling a different Monte Carlo environment than the one your API key belongs to. Leave montecarlo_endpoint unset so the profile's endpoint is used, or set it to your environment's API URL.
Registration fails with "Could not connect to Agent"The Collection AWS account ID is wrong, so Monte Carlo can't assume the invoker role. Check it against Settings → Account information.
The apply stops partwayWhat it created stays in the Terraform state: fix the cause and run terraform apply again.

Did this page help you?