·

What Is Terraform MCP

Learn what Terraform MCP is and how it lets your AI agent plan, review, and manage infrastructure as code.

Terraform MCP is the label for a growing family of Model Context Protocol servers that expose Terraform's CLI, provider registry, and state to an AI coding agent as callable tools instead of shell commands the agent has to guess the syntax for. The idea sounds trivial — terraform plan is one command — but the value isn't the command, it's the structured, machine-readable result the MCP tool returns: parsed plan JSON, typed diagnostics, provider schema lookups, all without the agent scraping colored terminal output or hallucinating a resource argument that doesn't exist in the provider version you pinned.

Two servers matter in practice today. HashiCorp ships an official terraform-mcp-server (Go binary, also distributed as a Docker image hashicorp/terraform-mcp-server) that focuses almost entirely on registry lookup: resolving provider IDs, fetching provider/resource documentation, searching modules on the public or private registry. It does not run terraform plan for you — it has no opinion about your working directory or state. The second category is community and in-house wrappers (often built on terraform-exec or a thin CLI shim) that add init/validate/plan/state tools by literally shelling out to your local terraform binary and structuring the output. Confusing the two is the single most common setup mistake — people install the registry-only server expecting plan review and wonder why the tool list is missing terraform_plan.

This topic — and the rest of this module — treats "Terraform MCP" as the composite capability set a well-configured agent should have: registry lookup from the HashiCorp server, plus init/validate/plan/state from a CLI-wrapping server, both connected at once. Check your server's tool list before assuming it does both; tools/list over the MCP connection tells you in five seconds.

Core Terraform MCP Tools: Init, Validate, Plan, State Inspection, and Registry Lookup

A capable Terraform MCP setup exposes roughly five tool families. Naming varies by implementation, but the shapes converge:

  • terraform_init — runs init in a given working directory, reports backend configuration used and any provider plugins downloaded. Read-only in effect (it only touches .terraform/ and the lock file), safe to let an agent call freely.
  • terraform_validate — syntax and internal-consistency check, no state or provider API calls. Cheapest sanity gate; run it on every HCL edit before anything else.
  • terraform_plan — the important one. A good implementation returns both the human-readable diff and the structured plan JSON (terraform show -json <planfile>), so the agent can reason over resource_changes[].change.actions programmatically instead of parsing +/-/~ glyphs from text.
  • terraform_state_list / terraform_state_show — enumerate resources tracked in state and show attributes for one address, without ever handing the agent the raw state file.
  • Registry lookupsearch_providers, get_provider_docs, search_modules, module_details — resolves the correct resource/attribute names and versions for a provider before the agent writes HCL, and finds published modules so the agent doesn't reinvent a VPC module from scratch.

Here's what calling the plan tool typically looks like from the agent's side, and what comes back:

// Tool call the agent issues
{
  "name": "terraform_plan",
  "arguments": {
    "working_dir": "infra/environments/staging",
    "var_file": "staging.tfvars",
    "out": "tfplan.staging"
  }
}
// Structured portion of the response (trimmed)
{
  "resource_changes": [
    {
      "address": "aws_instance.web[2]",
      "change": { "actions": ["create"] }
    },
    {
      "address": "aws_db_instance.primary",
      "change": { "actions": ["update"] }
    }
  ],
  "human_readable_summary": "Plan: 1 to add, 1 to change, 0 to destroy."
}

Notice there's no apply in that list on purpose — that's covered in the next section.

Tips
- Run terraform_validate before terraform_plan in every agent turn that touched HCL; it's near-instant and catches typos before you burn time on a full provider refresh.
- Ask the agent to call search_providers / get_provider_docs before writing a resource block for a provider version you haven't used recently — provider schemas change between major versions and the model's training data lags behind.
- If your server only exposes registry tools, don't ask the agent to "check the plan" — it literally can't; pair it with a CLI-wrapping server or fall back to pasting terraform plan output manually.


Terraform MCP Setup: Backends, Workspaces, and Provider Credentials

The MCP server itself doesn't manage your backend — Terraform does, from your terraform block, exactly as it always has. What changes is how you make sure the agent's tool calls run against the same backend and workspace state you expect, not some local scratch state it silently created.

A typical remote backend for a team using HCP Terraform (formerly Terraform Cloud):

terraform {
  required_version = ">= 1.7.0"

  cloud {
    organization = "acme-platform"

    workspaces {
      tags = ["env:staging"]
    }
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.60"
    }
  }
}

Or a self-managed S3 + DynamoDB backend, still common outside HCP Terraform:

terraform {
  backend "s3" {
    bucket         = "acme-tfstate-prod"
    key            = "platform/staging/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "acme-tf-locks"
    encrypt        = true
  }
}

When you launch the MCP server, its working directory and environment determine which backend it talks to. Pin both explicitly in the server config rather than trusting whatever shell the agent happened to inherit:

{
  "mcpServers": {
    "terraform": {
      "command": "terraform-mcp-server",
      "args": ["--allowed-dirs", "/Users/me/repos/acme-infra"],
      "env": {
        "TF_WORKSPACE": "staging",
        "TF_IN_AUTOMATION": "true",
        "AWS_PROFILE": "acme-staging-readonly"
      }
    }
  }
}

TF_IN_AUTOMATION=true matters more than it looks — it suppresses Terraform's "next steps" prompts that assume an interactive human, which otherwise pollute the tool output the agent has to parse. TF_WORKSPACE pins the workspace so a stray terraform workspace select never happens mid-session. For provider credentials, never inject long-lived static keys into the MCP server's env block if you can avoid it — use a credential process (aws_profile with SSO, or a short-lived AWS_SESSION_TOKEN refreshed by your existing tooling) so the blast radius of a leaked config file is small.

aws sts get-caller-identity --profile acme-staging-readonly

Tips
- Set TF_WORKSPACE and TF_IN_AUTOMATION=true in the server's own env block, not your shell's .zshrc — the agent should never be one unset away from targeting the wrong workspace.
- Use --allowed-dirs (or your server's equivalent) to fence the MCP server to the specific repo checkout you're working in; don't run it from $HOME.
- Prefer read-only or plan-only IAM/service credentials for the identity the MCP server runs as. Apply should go through a separate, more privileged, human-triggered path — CI/CD or a manual terraform apply.


What AI Should Do with IaC: Plan Review Yes, Blind Apply No

This is the load-bearing principle of the entire module: an AI agent reading and explaining a terraform plan is low-risk and genuinely useful; an AI agent calling terraform apply unattended on infrastructure that costs money or serves traffic is not a workflow, it's an incident waiting for a Tuesday. Plans are deterministic previews computed against real provider state — reviewing one is exactly the kind of structured, bounded reasoning task LLMs are good at. Applies are irreversible side effects against systems the agent cannot fully perceive (in-flight traffic, replication lag, a maintenance window someone forgot to mention).

Good agent behavior on a plan review turn looks like this prompt and this kind of response:

Review the plan in tfplan.staging. Call out any resource replacement
(not just update), anything touching aws_db_instance or aws_iam_*,
and flag if resource count changes by more than 2 in either direction.

A useful response classifies the diff instead of restating it:

Plan: 3 to add, 1 to change, 1 to destroy.

Flagged:
- aws_db_instance.primary: change action is REPLACE, not update — the
  `engine_version` bump from 15.4 to 16.1 forces a new instance because
  major version upgrades aren't in-place for this engine on this provider
  version. This will cause downtime unless you script a snapshot/restore.
- aws_iam_role_policy.app: destroy + create (name change) — verify no
  role assumption depends on the old ARN before applying.

Everything else in the diff is additive and matches the requested change.

That's the ceiling of what you want an agent doing on its own initiative. The floor — the thing to actively prevent — is an agent that treats terraform apply -auto-approve as just another shell command to run when a task says "deploy this." If your MCP server exposes an apply tool at all, gate it behind an explicit, separate confirmation step that a human triggers — never let it live in the same automatic tool-call loop as plan and validate.

{
  "name": "terraform_apply",
  "arguments": {
    "plan_file": "tfplan.staging",
    "require_confirmation_token": "user-approved-2026-08-21-a1"
  }
}

If your server doesn't support a confirmation token, don't wire the apply tool into the agent's toolset at all — run terraform apply tfplan.staging yourself, in your own terminal, after reading the same plan the agent reviewed.

Tips
- Never grant -auto-approve capability to an agent-callable apply tool. If the server has no confirmation gate, exclude the apply tool from the agent's allowed toolset entirely.
- Ask the agent to explicitly separate "replace" from "update" actions in its plan summary — replacements are where downtime and data loss risk actually live, and it's easy to skim past ~ glyphs that are secretly -/+.
- Treat agent plan review as a second reviewer, not the only reviewer, on anything touching production state — it catches a different class of mistakes than a human does, not a superset.


Protecting State Files and Secrets from Agent Exposure

Terraform state is a secrets leak waiting to happen even without an AI agent in the loop — it routinely contains database passwords, TLS private keys, and API tokens in plaintext unless every provider resource is marked sensitive (many aren't, especially on older provider versions). Handing an agent raw terraform.tfstate to "help debug" is one of the fastest ways to get credentials pasted into a chat transcript, a log line, or worse, a model provider's training pipeline if you're on a plan without a zero-retention agreement.

The fix is architectural, not a prompt instruction. Don't give the agent filesystem read access to *.tfstate or .terraform/ at all — scope the MCP server's file access so it can read your .tf source files but not state:

{
  "mcpServers": {
    "terraform": {
      "command": "terraform-mcp-server",
      "args": [
        "--allowed-dirs", "/repo/infra",
        "--deny-glob", "**/*.tfstate",
        "--deny-glob", "**/.terraform/**"
      ]
    }
  }
}

Instead, route state access exclusively through the terraform_state_show / terraform_state_list tools, and make sure your server implementation redacts values marked sensitive = true in the schema before returning them — this is a property of the server, not something you can enforce from the agent side, so test it once with a known secret:

terraform state show aws_db_instance.primary | grep -i password

If it prints the plaintext value, that server implementation is not safe to point an agent at for any resource carrying credentials — file an issue or patch it before adopting it further.

For secrets that live in variables rather than state — DB passwords passed via TF_VAR_db_password, tokens in a .tfvars file — keep them out of the MCP server's env entirely and out of any directory the agent can glob:

variable "db_password" {
  type      = string
  sensitive = true
}
*.auto.tfvars
secrets.tfvars

Marking a variable sensitive = true suppresses it from plan/apply console output and from the structured plan JSON's rendered diff — but it still lands in the state file unencrypted unless your backend encrypts at rest (S3 with encrypt = true and a KMS key, or HCP Terraform's server-side encryption). Sensitivity marking and state encryption are two different controls; you need both.

Tips
- Deny the MCP server filesystem access to *.tfstate and .terraform/ outright — force all state reads through the redacting terraform_state_show tool, then verify the redaction actually works against a known secret.
- Mark every credential-bearing variable sensitive = true, but don't stop there — it hides console/plan output, it does not encrypt the state file at rest.
- Rotate any credential that ever appeared in an agent's context window unredacted, the same way you would after a leaked CI log. Chat history is a log you don't fully control the retention of.


Tips

Tips
- Before wiring a Terraform MCP server into your agent, run tools/list over the connection and map what you actually got — registry-only, CLI-wrapping, or both — rather than assuming from the package name.
- Keep apply out of the autonomous tool-call loop. Plan review is the agent's job; pulling the trigger stays a deliberate, human-initiated action.
- Fence the server's filesystem access away from *.tfstate from day one — it's much easier to configure this correctly at setup than to audit chat history for leaked secrets after the fact.