@yawlabs/terraform-cwd-mcp
[Add to Yaw MCP]
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
What is actually true in THIS Terraform working directory: the exact provider schemas pinned in your .terraform.lock.hcl (so coding agents stop writing arguments that do not exist, were removed, or are read-only), plus your saved plans, your state (redacted), your local and installed modules, and what a provider upgrade would break. It complements HashiCorp's terraform-mcp-server rather than replacing it: that server answers from the Terraform Registry docs, which describe the latest provider release; this one answers from terraform providers schema -json in your root module, which describes the version you actually locked. Run both.
[npm version]
[License: MIT]
Built and maintained by Yaw Labs.
Why this one?
Your lock file says hashicorp/aws 5.31.0; the registry docs (and the model's training data) say whatever came later or earlier. Agents then write 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.
- Pinned, not latest -- the source of truth is
terraform providers schema -json run in your root module, with versions read from .terraform.lock.hcl.
- Read-only, never inits -- it never runs
terraform init (no downloads, no backend contact, no lock-file rewrites). An uninitialized directory gets an actionable error telling you what to run.
- Cached per lock file -- 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; you pay for that dump once per lock-file change, not per question.
- Small answers --
tf_get_schema renders one type as a compact indented outline with required / optional / optional+computed / computed (read-only), [deprecated], [sensitive] and [write_only] flags, instead of handing the model raw JSON.
- Cross-platform -- macOS, Linux and Windows. Terraform is spawned directly (no shell) and its output is streamed, so there is no buffer ceiling.
Tools
Every tool takes an optional dir (the root module); kind is resource (default), data or ephemeral.
Division of authority with HashiCorp's official server. This server wins on argument validity for the version you pinned and on what this directory's plan, state and modules contain. The official server wins on usage docs, latest versions, public registry modules and HCP/TFE runs. For an HCP run's plan, get its plan id there (get_run_details), fetch the plan with get_plan_json_output(plan_id) and pass the JSON to tf_summarize_plan as plan_json (those tools need the official server's terraform toolset and TFE_TOKEN).
What is read-only, and what touches the network
- Never written: your root module, its
.terraform/, its lock file and its state. terraform init is never run in it.
- State tools (
tf_state_addresses, tf_show_resource) run terraform state list / terraform show -json, which never lock and never refresh. With a remote backend they read S3/GCS/HCP with the credentials the server inherited from its MCP client. Selecting a non-default workspace that does not exist would make Terraform create it (the s3 backend writes an empty state), so the server checks terraform workspace list first and refuses.
tf_upgrade_impact downloads the target provider version with terraform init in a throwaway temp dir into a plugin cache (TERRAFORM_CWD_MCP_PLUGIN_CACHE, else your TF_PLUGIN_CACHE_DIR, else <cache>/plugins), then runs terraform validate on a temp copy of your configuration whose .terraform/providers entries are links to the packages you already have. State and *.tfvars are never copied. Calls are serialized (the plugin cache is not safe for concurrent installs).
- Redaction (
tf_show_resource): Terraform's sensitive markers, plus any string equal to (or containing) a marked value anywhere in state, plus values under secret-like keys (password, api_token, client_secret, ... but not password_length or secret_arn), plus secret-shaped strings (private keys, JWTs, GitHub/GitLab/Slack/npm tokens; URL passwords are masked in place). There is deliberately no "show sensitive" switch. Unmarked derived values Terraform itself shows (e.g. content_md5 of a sensitive file) are printed.
- Every terraform child gets
CHECKPOINT_DISABLE=1, so no version check phones home.
Example tf_summarize_plan output (real plan, abridged):
Example tf_upgrade_impact output (real validate run):
Example tf_get_schema output:
Quick start
1. Initialize the root module once (you, not the server):
-backend=false is enough: the schema only needs the providers, not state.
2. Add the server. Claude Code, from the root module:
Or add it to .mcp.json in the root module (or anywhere, and set TERRAFORM_CWD_MCP_DIR):
macOS / Linux / WSL:
Windows:
Why the extra step on Windows? Since Node 20, child_process.spawn cannot directly execute .cmd files (that's what npx is on Windows). Wrapping with cmd /c is the standard workaround.
3. Restart your MCP client and ask, for example: "Add a versioned S3 bucket -- check the arguments against our pinned AWS provider first."
Tell your agent to use it
Agents use the server reliably when the project tells them to. Paste this into the root module's CLAUDE.md (or AGENTS.md):
Pairs well with
HashiCorp's terraform-mcp-server
hashicorp/terraform-mcp-server answers registry and HCP questions; this server answers what is true in your working directory. Run both -- they do not overlap, and when they disagree the table says who wins:
The official server starts with only its registry toolset. The HCP / TFE rows need it started with --toolsets=registry,terraform (or all) and TFE_TOKEN. Its docs search tool is search_provider_docs from v1.3.1 on (search_providers before; a BREAKING rename), which matters if you allowlist tools by name.
Yaw Mode skills
Yaw Terminal's Yaw Mode ships four skills that drive both servers, degrade gracefully when either is missing, and stop at terraform plan (they never apply or destroy):
/yaw-tf -- everyday write / edit / fix: check every argument against the pinned schema, then fmt, validate, plan and summarize; hands off to the three below.
/yaw-tf-refactor -- renames, module moves and imports with moved / import / removed blocks, done only when the plan shows zero destroys and replaces.
/yaw-tf-plan-review -- go / no-go punch list for a local saved plan or an HCP run: destroys first, then checkov / trivy and IAM, network and encryption hot spots.
/yaw-tf-upgrade -- provider bumps: latest version, tf_upgrade_impact against the arguments you actually use, the upgrade guide, then edits, re-pin, validate and plan.
Configuration
All optional.
Runtime (oam or Node):
The terraform-cwd-mcp command prefers oam when it finds a
usable one and otherwise runs on Node. It never serves on an oam older than 0.19.1.
An outdated oam is fixed with oam self-update. Under Yaw MCP, which runs servers
on oam by default, yaw-mcp set <namespace> runtime=node switches this server to
Node. There is no --permission sandbox option: the server spawns terraform and
reads whatever directory a call names, so there is nothing narrow to grant.
Requirements
- Node.js 20+
- Terraform (or OpenTofu) on
PATH, recent enough to have terraform providers schema -json (0.12+; ephemeral resources need 1.10+)
- An initialized root module (
.terraform/ and .terraform.lock.hcl present) for every tool except tf_module_interface on the root or a local path, and tf_summarize_plan with plan JSON
- State tools: the backend initialized (
terraform init, not -backend=false) and its credentials in the server's environment
tf_upgrade_impact: registry access the first time a target version is requested (later runs for that version are offline)
.terraform/ at <dir>/.terraform: TF_DATA_DIR is not honoured
Contributing
See CONTRIBUTING.md for the full workflow, including release process.
License
MIT
[Follow @YawLabs on X]