What is actually true in this Terraform working directory from Claude Code, Cursor, or any MCP client - pinned provider schemas, saved plans, redacted state, local modules, and provider-upgrade impact.
@yawlabs/terraform-cwd-mcp answers from your working directory, not the registry. The source of truth is terraform providers schema -json run in your root module, with versions read from .terraform.lock.hcl - so the schema an agent checks against is the one you actually pinned, not the one the latest docs describe. It also reads your saved plans, your state (redacted), and your local and installed modules, and it computes what a provider upgrade would break. Built and maintained by Yaw Labs, MIT licensed, published on npm.
npx -y @yawlabs/terraform-cwd-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone who has watched an agent write Terraform against the wrong provider version. Your lock file says hashicorp/aws 5.31.0; the registry docs and the model's training data say whatever came later or earlier. The agent then writes arguments that were added after your pin, removed before it, or are computed-only and cannot be set at all. terraform validate catches it - after the code is written. This server lets the agent check before it writes:
tf_list_providers lists the root module's providers with their pinned versions and resource, data and ephemeral counts. Call it first.bucket a valid argument on this resource?" - tf_get_schema renders every argument and nested block of one type at the pinned version, with mode and flags, as a compact indented outline instead of raw JSON.tf_check_arguments returns ok, ok (block), READ-ONLY (computed), '<x>' is an attribute, not a block or UNKNOWN per path - so the agent never sets a read-only field.tf_summarize_plan summarizes a saved plan (binary or JSON), deletes and replaces first, with the attribute that forces each replace. It prints no values.tf_state_addresses lists resource instance addresses, and tf_show_resource returns one instance's attributes with sensitive values redacted.tf_upgrade_impact diffs your configuration against an exact target version and confirms it with terraform validate on a temp copy.1. Initialize the directory once. The schema tools read the provider packages Terraform has already downloaded, so the root module must have been initialized. The backend does not need to be reachable:
terraform init -backend=falseThe server itself never runs terraform init in your working directory. An uninitialized directory gets an actionable error telling you what to run.
2. Add the server. Claude Code, from the root module:
# macOS / Linux / WSL
claude mcp add terraform-cwd -- npx -y @yawlabs/terraform-cwd-mcp@latest
# Windows
claude mcp add terraform-cwd -- cmd /c npx -y @yawlabs/terraform-cwd-mcp@latestOr add it to .mcp.json in the root module. On macOS, Linux and WSL:
{
"mcpServers": {
"terraform-cwd": {
"command": "npx",
"args": ["-y", "@yawlabs/terraform-cwd-mcp@latest"]
}
}
}On Windows, wrap the call in cmd /c: since Node 20, child_process.spawn cannot directly execute .cmd files, and that is what npx is on Windows.
{
"mcpServers": {
"terraform-cwd": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/terraform-cwd-mcp@latest"]
}
}
}Point it at a module other than your current directory with TERRAFORM_CWD_MCP_DIR, or per call with the dir argument every tool takes. On Windows, env vars set in a bash or PowerShell profile are not inherited by MCP servers launched via cmd, so put them in the env block.
3. Tell your agent to use it. Agents reach for the server reliably when the project tells them to. Add this to the root module's CLAUDE.md or AGENTS.md:
## Terraform provider schemas
Call tf_list_providers once per session. Before writing any resource,
data or ephemeral block, call tf_get_schema for that type. Call
tf_check_arguments with every argument path you set, and never use one
it reports as UNKNOWN or READ-ONLY (computed).Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
| Tool | What it does |
|---|---|
tf_list_providers | Providers in the root module with their pinned versions and resource, data and ephemeral counts. Call this first. |
tf_search_types | Find type names containing a substring (for example s3_bucket), per kind. |
tf_get_schema | Every argument and nested block of one type at the pinned version, with mode and flags. Use before writing or editing a block. |
tf_check_arguments | Validate dotted argument paths and get a per-path verdict: ok, ok (block), READ-ONLY (computed), '<x>' is an attribute, not a block or UNKNOWN. |
tf_summarize_plan | Summarize a saved plan (binary or JSON): deletes and replaces first, with the forcing attribute; then updates, creates, imports, moves, drift, deferred changes and outputs. Prints no values. |
tf_module_interface | Variables (exact type, required/optional, default, flags), outputs and module calls of the root, a local module path, or an installed module by address. No subprocess. |
tf_state_addresses | Resource instance addresses in the current state, with a substring filter and id lookup. |
tf_show_resource | One instance's attribute values from state, with sensitive values redacted and a footer saying what was hidden and why. |
tf_upgrade_impact | What breaks if a pinned provider moves to an exact version: arguments, nested blocks, attribute reads and provider-block arguments that are removed, read-only, retyped, newly required or deprecated in the target - confirmed by terraform validate on a temp copy. |
Every tool takes an optional dir (the root module), and kind is resource (default), data or ephemeral. Dumps are cached in memory and on disk keyed on the root path plus the lock file's bytes, so a provider upgrade invalidates the cache automatically - the AWS provider's schema is tens of MB, and you pay for that dump once per lock-file change, not per question.
.terraform/, its lock file or its state, and it never runs terraform init in your directory. An uninitialized directory gets an actionable error.tf_state_addresses and tf_show_resource run terraform state list and terraform show -json, which never lock and never refresh. Selecting a non-existent workspace would make Terraform create it, so the server checks terraform workspace list first and refuses.terraform init in a throwaway temp directory and runs terraform validate on a temp copy of your configuration. State and *.tfvars are never copied, and calls are serialized because the plugin cache is not safe for concurrent installs.terraform child process gets CHECKPOINT_DISABLE=1, so no version check reaches HashiCorp.It answers what is true in the Terraform working directory you point it at, not in the public registry. The source of truth is terraform providers schema -json run in your root module, with versions read from .terraform.lock.hcl, so the tool surface describes the provider version you actually pinned. On top of that it reads your saved plans, your state (with sensitive values redacted), your local and installed modules, and it computes what a provider version bump would break.
They answer different questions. HashiCorp's server answers from the Terraform Registry docs, which describe the latest provider release, and from HCP Terraform / Terraform Enterprise. This server answers from terraform providers schema in your root module, which describes the version you locked. Run both: this one wins on argument validity for your pin and on what this directory's plan, state and modules contain, and the official one wins on usage docs, latest versions, public registry modules and HCP runs.
No. It never runs terraform init in your root module, never writes your configuration, your state or your lock file, and there is no apply path. The schema tools read from the provider packages you already downloaded, so the directory must have been initialized with terraform init -backend=false first; an uninitialized directory gets an actionable error telling you what to run. The provider-upgrade tool downloads a target version with terraform init inside a throwaway temp directory, never in yours.
The state tools redact sensitive values before returning anything. That includes Terraform's own sensitive markers, any string equal to or containing a marked value, values under secret-like keys such as password or client_secret, and secret-shaped strings like private keys and API tokens. There is deliberately no show-sensitive switch. Unmarked derived values that Terraform itself displays, such as a content hash of a sensitive file, are printed.
Given a pinned provider and an exact target version, it reports the arguments, nested blocks, attribute reads and provider-block arguments that your configuration uses and that are removed, read-only, retyped, newly required or deprecated in the target, along with version constraints that block the move. It confirms the result by running terraform validate on a temp copy whose provider entries link to packages you already have. State and *.tfvars are never copied, and calls are serialized because the plugin cache is not safe for concurrent installs.
Published by Yaw Labs.