Example: Onboard Snowflake through a new AWS agent, all with Terraform
Claude writes the Terraform for a Monte Carlo AWS collection agent and a Snowflake connection that reads its key pair from your AWS Secrets Manager. You run two applies; Claude checks the secret between them and validates the connection at the end.
In this example Claude writes a Terraform configuration that creates everything: the Monte Carlo deployment, the agent in your AWS account (with Monte Carlo's mcd-agent module), its registration, the IAM grant on your secret, the credentials reference, the warehouse and the connection. You run terraform apply; Claude checks the result through the Monte Carlo MCP tools. The Snowflake private key stays in your Secrets Manager: Terraform and Monte Carlo only ever see the secret's ARN.
| Who | What | How |
|---|---|---|
| Claude | Read your account, check for duplicates, write the Terraform | MCP tools, then files in your working directory |
| You | Apply 1: deployment, agent, registration, IAM grant on the secret | terraform apply |
| Claude | Check that the new agent can read the secret, before anything reads it for real | validate_aws_secrets_manager_credentials |
| You | Apply 2: credentials reference, warehouse, connection | terraform apply |
| Claude | Validate the connection | validate_connection, get_validation_run |
For the same result with the AWS CLI and CloudFormation instead, see Create and register an AWS collection agent.
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) orMCD_DEFAULT_API_ID/MCD_DEFAULT_API_TOKEN. Runmontecarlo whoamito confirm which account and environment the profile reaches. - AWS credentials for the account the agent runs in, able to create IAM roles, Lambda functions and S3 buckets.
- The Collection AWS account ID from Settings → Account information (Monte Carlo's account, not yours).
- The Snowflake key pair in AWS Secrets Manager, as JSON with
user,account,private_key(the unencrypted PKCS#8 body without itsBEGIN/ENDlines) andwarehouseunderconnect_args. See Onboard Postgres and Snowflake with self-hosted credentials for a command that creates it without exposing the key, and Snowflake for the user's grants.
1. Ask Claude
/monte-carlo-onboarding connect our Snowflake through a new AWS agent, all with Terraform; the key-pair secret is already in AWS Secrets Manager
Claude reads your account first. If the same Snowflake account, or the same secret, is already connected, it says so: a second connection would collect the same data twice. It also points out an existing agent you could reuse. It then proposes the two-apply plan and a checklist of inputs, each marked as coming from you or from your account.
2. Answer Claude's questions
- Region: <region>, AWS account <account-id>.
- Collection AWS account ID: <collection-aws-account-id>.
- Deployment name: <deployment-name>.
- Network: Snowflake over the public internet, no PrivateLink, so the agent doesn't need a VPC.
- Secret: <snowflake-secret-arn>. Default KMS key. Contents are connect_args with user, account, private_key and warehouse. The grants script ran.
- Warehouse name: <warehouse-name>. Connection name: <connection-name>.
- I run Terraform myself; the Monte Carlo provider uses my default profile.
If Snowflake is only reachable privately, give the private subnets instead: the agent then runs in them, and the module takes them as private_subnets.
3. The Terraform Claude writes
Claude writes the files into your working directory and checks them with terraform fmt and 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 {
required_version = ">= 1.11"
required_providers {
montecarlo = { source = "monte-carlo-data/montecarlo" }
aws = { source = "hashicorp/aws", version = ">= 6.50" }
}
# Use a backend that encrypts state and limits who can read it, for example:
# backend "s3" { bucket = "…", key = "monte-carlo/snowflake-agent.tfstate", region = "…", encrypt = true }
}
# Credentials come from your Monte Carlo CLI profile or MCD_DEFAULT_API_ID / MCD_DEFAULT_API_TOKEN.
provider "montecarlo" {
# The API endpoint of the environment your account lives in; the profile's key only works there.
endpoint = var.montecarlo_endpoint
}
provider "aws" {
region = var.aws_region
allowed_account_ids = [var.aws_account_id] # refuses to plan against any other account
}variables.tf
variable "montecarlo_endpoint" { type = string }
variable "aws_region" { type = string }
variable "aws_account_id" { type = string } # the account the agent is deployed into
# Settings → Account information → Collection AWS account ID. Not the account the agent runs in.
variable "monte_carlo_collection_account_id" {
type = string
validation {
condition = var.monte_carlo_collection_account_id != var.aws_account_id
error_message = "The Collection AWS account ID is Monte Carlo's account, not the one the agent is deployed into."
}
}
variable "deployment_name" { type = string }
variable "snowflake_secret_arn" {
type = string
validation {
condition = can(regex("^arn:aws:secretsmanager:", var.snowflake_secret_arn))
error_message = "Use the full Secrets Manager ARN."
}
}
variable "warehouse_name" { type = string }
variable "connection_name" { type = string }terraform.tfvars (non-secret values)
montecarlo_endpoint = "<api-endpoint>" # e.g. https://api.getmontecarlo.com
aws_region = "<region>"
aws_account_id = "<account-id>"
monte_carlo_collection_account_id = "<collection-aws-account-id>"
deployment_name = "<deployment-name>"
snowflake_secret_arn = "<snowflake-secret-arn>"
warehouse_name = "<warehouse-name>"
connection_name = "<connection-name>"agent.tf (apply 1)
# Created first: it generates the External ID the agent's invoker role must trust.
resource "montecarlo_deployment" "agent" {
name = var.deployment_name
type = "COLLECTION_AGENT"
runtime_platform = "AWS"
}
# https://registry.terraform.io/modules/monte-carlo-data/mcd-agent/aws
# No private_subnets: Snowflake is reached over the public internet.
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
}
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
}
# The agent reads the secret at query time. Add kms:Decrypt if the secret uses a customer-managed key.
resource "aws_iam_role_policy" "agent_read_snowflake_secret" {
name = "mcd-read-snowflake-secret"
role = module.mcd_agent.mcd_agent_execution_role.name
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "secretsmanager:GetSecretValue"
Resource = var.snowflake_secret_arn
}]
})
}
output "deployment_id" { value = montecarlo_deployment.agent.id }
output "collection_agent_id" { value = montecarlo_aws_collection_agent.agent.id }connection.tf.phase2 (apply 2; renamed to connection.tf after the secret check)
resource "montecarlo_self_hosted_aws_credentials" "snowflake" {
connection_type = "snowflake"
aws_secret = var.snowflake_secret_arn
depends_on = [aws_iam_role_policy.agent_read_snowflake_secret]
}
resource "montecarlo_warehouse" "snowflake" {
name = var.warehouse_name
type = "snowflake"
deployment_id = montecarlo_deployment.agent.id
depends_on = [montecarlo_aws_collection_agent.agent]
}
resource "montecarlo_connection" "snowflake" {
name = var.connection_name
warehouse_id = montecarlo_warehouse.snowflake.id
credentials_id = montecarlo_self_hosted_aws_credentials.snowflake.id
# job_types omitted: Snowflake's defaults
}
output "created_ids" {
value = {
deployment = montecarlo_deployment.agent.id
agent = montecarlo_aws_collection_agent.agent.id
credentials = montecarlo_self_hosted_aws_credentials.snowflake.id
warehouse = montecarlo_warehouse.snowflake.id
connection = montecarlo_connection.snowflake.id
}
}4. Apply 1: the agent
terraform init
terraform applyThis creates the deployment, the agent's Lambda function, roles and bucket, the IAM grant, and the registration: Monte Carlo reaches the new Lambda through the invoker role, and the deployment becomes enabled. Send Claude the deployment_id output.
If the apply stops partway, what it already created stays in the Terraform state: fix the cause and run terraform apply again, and it continues from there instead of starting over.
Claude then runs a check that creates nothing: it asks the new agent to read the secret and test Snowflake with it. In this example all 14 checks passed (connect, storage, query history, databases, tables, columns and a sample SELECT). An IAM, KMS or network problem is cheapest to fix at this point, before any credentials or connection exist.
5. Apply 2: the connection
mv connection.tf.phase2 connection.tf
terraform applySend Claude the connection id from the created_ids output. It validates the connection and ends with a summary:
Created in this run (by your Terraform)
deployment <id> <deployment-name> COLLECTION_AGENT/AWS enabled=true
agent <id>
credentials <id> snowflake aws_secrets_manager
warehouse <id> <warehouse-name> snowflake
connection <id> <connection-name> snowflake
validation <id> passed 14/14 passed
Reused (excluded from cleanup)
Secrets Manager secret <secret-name> (only referenced)
Cleanup if you abandon this: terraform destroy (connection, warehouse and credentials, then the
agent registration, the deployment and the module's AWS resources). The secret is untouched.
Collection starts on its own; the first metadata appears within about an hour. If you're moving an existing connection to this Terraform, remove the old connection, then its warehouse and credentials, once metadata arrives on the new one. Keep the secret if the new connection uses it.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
Apply 1 fails creating montecarlo_deployment 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. Set montecarlo_endpoint to the endpoint your profile uses (montecarlo whoami confirms the account) and apply again: the AWS resources already created stay in state and aren't recreated. |
| Registration fails with "Could not connect to Agent" | monte_carlo_collection_account_id or the External ID in the invoker role's trust policy is wrong. Check it against Settings → Account information. |
| The secret check fails with "Could not connect" | The execution role can't read the secret: check aws_iam_role_policy.agent_read_snowflake_secret, and add kms:Decrypt for a customer-managed key. |
The secret check connects but Snowflake rejects the login (JWT token is invalid) | The secret's user isn't the user the public key is set on, or private_key still has its BEGIN/END lines. |
| Snowflake refuses the connection | A Snowflake network policy blocks the agent. Without a VPC the agent uses AWS Lambda's public IPs; to allowlist a fixed IP, run the agent in private subnets with a NAT gateway. |
Updated about 1 hour ago
