Example: Keep sampled rows in your AWS account (customer-hosted data store) with Claude

Connect Snowflake directly from Monte Carlo's cloud while sampled rows and temporary query results are stored in an S3 bucket in your own AWS account. Terraform creates the bucket and role; the Monte Carlo CLI sends the key pair.

Use this when Monte Carlo may connect to your warehouse directly but sampled rows and temporary query results must stay in your cloud. Monte Carlo keeps connecting from its own cloud; a customer-hosted data store (an S3 bucket in your AWS account) receives the samples. Metadata, metrics, query logs and aggregated statistics still live in Monte Carlo: the data store only changes where samples and temporary results go.

If connections must also originate from your network, or credentials must stay in your own secret store, use a collection agent instead: see Create and register an AWS collection agent with Terraform (or with the AWS CLI).

WhoWhatHow
ClaudeCreate the data store deployment and read its External IDcreate_deployment, get_deployment
YouCreate the bucket and the role Monte Carlo assumesTerraform
ClaudeRegister the data store and confirm it's enabledregister_aws_collection_data_store, get_deployment
YouValidate and store the Snowflake key pairMonte Carlo CLI (montecarlo)
ClaudeCreate the warehouse and connection, validatecreate_warehouse, create_connection, validate_connection

Before you start

  • Claude with the Agent Toolkit plugin, signed in to the Monte Carlo MCP server. See Agent Toolkit.
  • A plan that includes customer-hosted data stores (Scale or Enterprise with ESP), and room for another deployment in your account. If create_deployment is refused with deployment_limit_reached, Monte Carlo support raises the limit.
  • The Collection AWS account ID from Settings → Account information (Monte Carlo's account, not yours).
  • Terraform and AWS credentials for the account the bucket goes in; the Monte Carlo CLI (montecarlo), with a profile for your account: montecarlo whoami shows it. The older montecarlodata Python CLI installs a command with the same name; montecarlo deployments --help succeeds only with the right one.
  • A Snowflake key pair: the public key set on the Monte Carlo user, the private key as a PEM file on your machine, and the user's grants from Snowflake. A Snowflake network policy, if you have one, must allow Monte Carlo's IP addresses.

1. Ask Claude

/monte-carlo-onboarding connect Snowflake; Monte Carlo can reach it directly, but sampled rows must stay in our AWS account

Claude reads your account and recommends the route: the cloud deployment with a customer-hosted data store, not an agent. It explains that an existing cloud warehouse can't be moved to the new deployment, and asks whether this Snowflake account is already connected: a second connection would collect the same data twice. It then asks for the region, the Collection AWS account ID, the Snowflake account, user and warehouse, the key file's path, names for the warehouse and connection, and whether you prefer Terraform or the Monte Carlo CLI for your steps.

2. Answer Claude's questions

Approved. Answers:
- Region: <region>, AWS account <account-id>. Collection AWS account ID: <collection-aws-account-id>.
- Deployment name: <deployment-name>.
- A Snowflake account we don't monitor yet: <account-identifier>.
- User <snowflake-user> (the public key is set on it), warehouse <snowflake-warehouse>. The private key is at <path-to-key.p8>, not encrypted. The grants script ran.
- Warehouse name <warehouse-name>, connection <connection-name>.
- Local steps: Terraform for the bucket and role, the Monte Carlo CLI for the Snowflake key.

Claude says up front that it won't open the key file: the path only goes into the command you run. It then creates the data store deployment and gives you its External ID. The deployment shows enabled: false until the bucket is registered.

3. Create the bucket and role

Claude writes the Terraform for the AWS side only; the deployment already exists in Monte Carlo:

terraform {
  required_version = ">= 1.11"
  required_providers {
    aws = { source = "hashicorp/aws", version = ">= 6.50" }
  }
}

variable "aws_region" { type = string }
variable "mcd_account_id" { type = string } # Collection AWS account ID (Account information)
variable "external_id" { type = string }    # the deployment's aws_external_id

provider "aws" { region = var.aws_region }

resource "aws_s3_bucket" "store" { bucket_prefix = "mcd-data-store-" }

resource "aws_s3_bucket_public_access_block" "store" {
  bucket                  = aws_s3_bucket.store.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "store" {
  bucket = aws_s3_bucket.store.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "store" {
  bucket = aws_s3_bucket.store.id
  rule {
    id     = "expire-temporary-data"
    status = "Enabled"
    filter { prefix = "" }
    expiration { days = 90 } # adjust to your retention policy
  }
}

# The role Monte Carlo's collection account assumes, only with this deployment's External ID.
resource "aws_iam_role" "store" {
  name_prefix = "mcd-data-store-"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Action    = "sts:AssumeRole"
      Principal = { AWS = "arn:aws:iam::${var.mcd_account_id}:root" }
      Condition = { StringEquals = { "sts:ExternalId" = var.external_id } }
    }]
  })
}

# Access to this bucket only.
resource "aws_iam_role_policy" "store" {
  role = aws_iam_role.store.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Action = ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket",
      "s3:GetBucketPublicAccessBlock", "s3:GetBucketPolicyStatus", "s3:GetBucketAcl"]
      Resource = [aws_s3_bucket.store.arn, "${aws_s3_bucket.store.arn}/*"]
    }]
  })
}

output "bucket_name" { value = aws_s3_bucket.store.bucket }
output "role_arn" { value = aws_iam_role.store.arn }
terraform init
terraform apply \
  -var 'aws_region=<region>' \
  -var 'mcd_account_id=<collection-aws-account-id>' \
  -var 'external_id=<external-id-from-claude>'
terraform output

Send Claude bucket_name and role_arn; neither is a secret. Claude registers the data store with register_aws_collection_data_store: Monte Carlo assumes the role and checks the bucket, and the deployment switches to enabled: true.

4. Store the Snowflake key pair

Monte Carlo stores this key, so it goes from your machine to Monte Carlo directly. Claude gives you these commands; validate first (it creates nothing), and create only if every check passed:

DEPLOYMENT_ID=<data-store-deployment-id>
SF_ACCOUNT=<account-identifier>
SF_USER=<snowflake-user>
SF_WH=<snowflake-warehouse>
KEY_FILE="${HOME}/<path-to-key.p8>"

montecarlo credentials validate snowflake \
  --deployment-id "${DEPLOYMENT_ID}" \
  --account "${SF_ACCOUNT}" \
  --user "${SF_USER}" \
  --warehouse "${SF_WH}" \
  --private-key "@${KEY_FILE}" \
  --output json

montecarlo credentials create snowflake \
  --account "${SF_ACCOUNT}" \
  --user "${SF_USER}" \
  --warehouse "${SF_WH}" \
  --private-key "@${KEY_FILE}" \
  --output json

@<path> makes the CLI read the key from the file; use a full path, because ~ doesn't expand after @. In this example the validation passed 13/13. Send Claude the run's result and the credentials id only.

5. Claude finishes

Claude creates the warehouse on the data store deployment and the connection with the new credentials, then validates it. In this example all 13 checks passed, including the check that Monte Carlo can write to your bucket. The run ends with:

Created in this run
  deployment    <id>  <deployment-name>  COLLECTION_DATA_STORE/AWS  enabled=true
  agent|store   <id>  S3 <bucket-name>
  credentials   <id>  snowflake  mc_managed  (created by your CLI step)
  warehouse     <id>  <warehouse-name>  snowflake
  connection    <id>  <connection-name>  snowflake
  validation    <id>  passed  13/13 passed

Cleanup if you abandon this: delete_connection → delete_warehouse → delete_snowflake_credentials →
delete_aws_collection_data_store → delete_deployment, then terraform destroy (deregistering
doesn't delete the bucket or its contents).

Collection starts on its own; the first metadata appears within about an hour. If you moved an existing connection to this deployment, remove the old one once the new one has collected, with the same tool that created it (Terraform, the CLI or Claude).

Troubleshooting

SymptomCause and fix
create_deployment is refused with deployment_limit_reachedThe account has used all its deployments. Remove an unused one, or ask Monte Carlo support to raise the limit (Claude gives you the request id).
Registration failsThe role's trust policy doesn't match: check mcd_account_id is the Collection AWS account ID (not yours) and external_id is the deployment's.
The connection check fails writing to storageThe role's policy doesn't allow the bucket, or a bucket policy or SCP blocks it.
Validation fails with JWT token is invalidThe user isn't the one the public key is set on, or the file isn't the matching private key.

Did this page help you?