terraform-cwd-mcp
Tells coding agents what is actually true in this Terraform working directory — the pinned provider schemas from .terraform.lock.hcl — so they stop writing arguments that do not exist. Read-only; it never runs terraform init on your root module.
tf_list_providers— list providers in the root module with their pinned versions and resource/data/ephemeral counts (call this first).tf_search_types— find resource, data source or ephemeral type names containing a substring (e.g.s3_bucket).tf_get_schema— every argument and nested block of one type at the pinned version, withrequired/optional/optional+computed/computed (read-only)and[deprecated],[sensitive],[write_only]flags.tf_check_arguments— validate dotted argument paths (e.g.["bucket", "versioning.enabled"]) before writing them; flagsUNKNOWN,READ-ONLY (computed)and[deprecated]paths.tf_summarize_plan— summarize a saved plan (binary or JSON, incl. HCP run JSON): deletes and replaces first with the forcing attribute, then updates, creates, imports, moves, forgets, drift, outputs, and a destroy warning. Prints no values.tf_module_interface— variables (exact type, required/optional, default, sensitive/nullable/ephemeral, validation count), outputs and module calls of the root, a local module, or an installed module by address; flags uninstalled/stale calls. No subprocess.tf_state_addresses— resource instance addresses in the current state, with substring filter andidlookup; never locks or refreshes, refuses non-existent workspaces.tf_show_resource— one instance's attribute values from state with sensitive values, secret-like keys and secret-shaped strings redacted, plus a footer explaining what was hidden.tf_upgrade_impact— what breaks if a pinned provider moves to an exact version: removed, read-only, retyped, newly required or deprecated arguments your config uses, blocking constraints, confirmed byterraform validateon a temp copy.Cross-cutting: cache keyed on the lock file's bytes, per-call
diroverride,kindselection (resource/data/ephemeral), OpenTofu support viaTERRAFORM_CWD_MCP_BINARY, and explicit division of labour with HashiCorp's official server (which owns registry docs, latest versions and HCP/TFE runs).
Answers from the hashicorp provider schemas actually locked in your working directory (e.g. hashicorp/aws, hashicorp/random) rather than from published registry docs, and complements HashiCorp's official terraform-mcp-server, which remains authoritative for usage docs, latest versions, public registry modules and HCP/TFE runs (whose plan JSON can be fed into this server's plan summarizer).
Reads the truth of a specific Terraform root module working directory: the exact provider schemas pinned in .terraform.lock.hcl (every argument, nested block, mode and flag of a type, with argument-path validation), saved plans (deletes, replaces, updates, creates, imports, moves, drift), state resource addresses and redacted instance attributes, local and installed module interfaces, and what a provider version upgrade would break. It never runs terraform init in the module and is read-only apart from throwaway temp copies used for upgrade validation.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@terraform-cwd-mcp check if aws_s3_bucket versioning.enabled is valid before I edit it"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@yawlabs/terraform-cwd-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.
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 -jsonrun 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_schemarenders one type as a compact indented outline withrequired/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.
Related MCP server: archview
Tools
Tool | What it does |
| Providers in the root module with their pinned versions and resource / data / ephemeral counts. Call this first. |
| Find type names containing a substring ( |
| Every argument and nested block of one type at the pinned version, with mode and flags. Use before writing or editing a block. |
| Validate dotted paths ( |
| Summarize a saved plan ( |
| Variables (exact type expression, required/optional, default, |
| Resource instance addresses in the current state ( |
| One instance's attribute values from state ( |
| What breaks if a pinned provider moves to an exact version: the arguments, nested blocks, attribute reads and |
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 initis never run in it.State tools (
tf_state_addresses,tf_show_resource) runterraform 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 checksterraform workspace listfirst and refuses.tf_upgrade_impactdownloads the target provider version withterraform initin a throwaway temp dir into a plugin cache (TERRAFORM_CWD_MCP_PLUGIN_CACHE, else yourTF_PLUGIN_CACHE_DIR, else<cache>/plugins), then runsterraform validateon a temp copy of your configuration whose.terraform/providersentries are links to the packages you already have. State and*.tfvarsare 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 notpassword_lengthorsecret_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_md5of 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):
plan (terraform 1.15.7, format 1.2): applyable, complete
totals: 1 delete, 5 replace, 1 update, 1 create, 0 read, 1 import, 1 move, 1 forget, 0 unchanged (terraform: 1 to import, 6 to add, 1 to change, 6 to destroy)
DELETE (1)
- null_resource.gone (not in configuration)
REPLACE (5)
-/+ local_sensitive_file.sf delete-then-create; forced by: content [sensitive]
+/- random_string.cbd create-before-destroy; forced by: keepers
...
UPDATE (1)
~ terraform_data.cfg: input.version, output (known after apply)
WARNING: 6 object(s) will be destroyed (1 delete, 5 replace). 1 moved block(s) applied; none covers these.Example tf_upgrade_impact output (real validate run):
upgrade hashicorp/random 2.3.1 -> 3.6.3 (major) for /work/infra
constraints: root pins 2.3.1 as local name rnd (change it to allow 3.6.3)
used types: resource random_password, random_id
affected arguments (2):
random_password.p.number (main.tf:18): DEPRECATED in 3.6.3
reads random_id.i.b64 (main.tf:36): REMOVED in 3.6.3
validate on a temp copy, 3.6.3 vs 2.3.1: 1 new error, 1 new warning, 1 warning resolved
error main.tf:36: Unsupported attribute - This object has no argument, nested block, or exported attribute named "b64".
warning main.tf:18 random_password.p: Attribute Deprecated - **NOTE**: This is deprecated, use `numeric` instead.
verdict: BREAKING (1 error after upgrade)Example tf_get_schema output:
resource random_password (registry.terraform.io/hashicorp/random 3.9.1)
bcrypt_hash: string computed (read-only) [sensitive] - A bcrypt hash of the generated random password. ...
keepers: ["map","string"] optional - Arbitrary map of values that, when changed, will trigger recreation of resource. ...
length: number required - The length of the password desired. ...
number: bool optional+computed [deprecated] - Include numeric characters in the result. ...
result: string computed (read-only) [sensitive] - The generated random password.Quick start
1. Initialize the root module once (you, not the server):
terraform init -backend=false-backend=false is enough: the schema only needs the providers, not state.
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 (or anywhere, and set TERRAFORM_CWD_MCP_DIR):
macOS / Linux / WSL:
{
"mcpServers": {
"terraform-cwd": {
"command": "npx",
"args": ["-y", "@yawlabs/terraform-cwd-mcp@latest"]
}
}
}Windows:
{
"mcpServers": {
"terraform-cwd": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@yawlabs/terraform-cwd-mcp@latest"]
}
}
}Why the extra step on Windows? Since Node 20,
child_process.spawncannot directly execute.cmdfiles (that's whatnpxis on Windows). Wrapping withcmd /cis 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):
## Terraform provider schemas
This repo pins provider versions in `.terraform.lock.hcl`; registry docs and your
training data describe other versions. Use the terraform-cwd MCP server:
- Call `tf_list_providers` once per session to see the pinned versions.
- Before writing or editing any `resource`, `data` or `ephemeral` block, call
`tf_get_schema` for that type.
- Call `tf_check_arguments` with every argument path you set (dotted for nested
blocks, e.g. `versioning.enabled`). Never use an argument it reports as
`UNKNOWN` or `READ-ONLY (computed)`; replace a `[deprecated]` one if the
schema offers an alternative.
- Before calling a module, read its inputs with `tf_module_interface`.
- After `terraform plan -out=tfplan`, review it with `tf_summarize_plan`
(plan_file: tfplan) and call out every DELETE and REPLACE.
- Before bumping a provider version, run `tf_upgrade_impact`.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:
Question | Authority |
Is argument X valid for the provider version I have pinned? | terraform-cwd-mcp |
Which arguments I use break if I upgrade to vN? | terraform-cwd-mcp ( |
What does this saved plan, this state, or this local / installed module contain? | terraform-cwd-mcp |
How is resource X meant to be used? What do the docs and examples say? | terraform-mcp-server |
What is the latest provider or module version? | terraform-mcp-server |
Which public registry module should I use? | terraform-mcp-server |
HCP Terraform / TFE workspaces and runs | terraform-mcp-server |
An HCP run's plan | terraform-mcp-server fetches it ( |
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 withmoved/import/removedblocks, 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_impactagainst the arguments you actually use, the upgrade guide, then edits, re-pin, validate and plan.
Configuration
All optional.
Env var | Default | Purpose |
| the server's working directory | Root module used when a tool call passes no |
|
| Where schema dumps are cached ( |
|
| Binary to run, e.g. a full path, or |
|
| Kill any terraform command (schema dump, state read, target init, validate) after this long. |
|
| Where |
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.
Env var | Default | Purpose |
|
|
|
| (unset) | Path to the oam binary to use. Otherwise the launcher checks |
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 haveterraform providers schema -json(0.12+; ephemeral resources need 1.10+)An initialized root module (
.terraform/and.terraform.lock.hclpresent) for every tool excepttf_module_interfaceon the root or a local path, andtf_summarize_planwith plan JSONState tools: the backend initialized (
terraform init, not-backend=false) and its credentials in the server's environmenttf_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_DIRis not honoured
Contributing
git clone https://github.com/YawLabs/terraform-cwd-mcp.git
cd terraform-cwd-mcp
npm install
npm run lint # Biome check
npm run lint:fix # Auto-fix
npm run build # tsup bundle + declarations
npm test # Vitest (the integration test runs only when terraform is on PATH)
npm run typecheck # oam check . when oam is installed, else tsc --noEmit
npm run check:oam-floor # the oam floor agrees across the repo and matches the latest oam releaseSee CONTRIBUTING.md for the full workflow, including release process.
License
MIT
Available Tools
9 toolstf_check_argumentsARead-onlyIdempotent
Validate argument / nested-block paths against the PINNED schema before writing them, e.g. ["bucket", "region", "versioning.enabled"]. Flags unknown, read-only and deprecated paths.
Inputs: arguments (required), type_name (required), dir, kind.
tf_get_schemaARead-onlyIdempotent
Exact arguments and nested blocks for one type at the PINNED provider version. Use BEFORE writing or editing any resource/data/ephemeral block; only arguments listed here exist. 'computed (read-only)' attributes cannot be set. kind: resource | data | ephemeral.
Inputs: type_name (required), dir, kind, max_depth, +1 more.
tf_list_providersARead-onlyIdempotent
List providers and their PINNED versions for a Terraform root module (default: cwd). Call this first so code targets the versions actually locked in .terraform.lock.hcl, not the latest release. Lists only the providers locked in THIS directory; it does not search the registry. For registry search, provider docs or the latest version, use the official terraform MCP server (search_provider_docs / get_latest_provider_version).
Inputs: dir.
tf_module_interfaceARead-onlyIdempotent
Variables (exact type expression, required/optional, default, sensitive/nullable/ephemeral, validation count), outputs and module calls of a module in THIS working directory: the root, a local module directory, or an installed module by address (module.network, resolved through .terraform/modules/modules.json, so downloaded registry/git modules work). Module calls are checked against what is installed. For public registry modules you have not installed, use the official terraform MCP server.
Inputs: dir, module, description_chars.
tf_search_typesARead-onlyIdempotent
Find resource / data source / ephemeral resource type names containing query (e.g. 's3_bucket') in the pinned providers. kind: resource | data | ephemeral.
Inputs: query (required), dir, kind, limit.
tf_show_resourceARead-onlyIdempotent
Show one resource instance's attribute values from the current Terraform state (terraform show -json). Read-only: never locks, never refreshes. Sensitive values are redacted: Terraform's own markers, plus values equal to a marked one, secret-like names and secret-shaped strings; a footer says what was hidden and why.
Inputs: address (required), dir, include_null, max_value_chars.
tf_state_addressesARead-onlyIdempotent
List resource instance addresses in the current Terraform state (terraform state list), optionally filtered. Read-only: never locks, never refreshes. With a remote backend this reads S3/GCS/HCP using the server's credentials; a non-default workspace that does not exist is refused (reading it would create it).
Inputs: id, dir, limit, filter.
tf_summarize_planARead-onlyIdempotent
Summarize a saved Terraform plan: deletes and replaces first (with the attribute that forces each replace), updates with changed paths, creates, imports, moves, forgets, drift and output changes, plus a destroy warning that names renames needing a moved block. Pass plan_file (binary from terraform plan -out, rendered with terraform show -json in dir, or a plan JSON file) or plan_json (e.g. from the official terraform MCP server's get_plan_json_output(plan_id) for an HCP run; get the plan id from its get_run_details). Prints no values: sensitive paths are truncated, import IDs omitted.
Inputs: dir, max_paths, plan_file, plan_json, +1 more.
tf_upgrade_impactARead-onlyIdempotent
What breaks if a pinned provider is upgraded (or downgraded) to an exact version: compares the pinned schema with the target's for the arguments, nested blocks and attribute reads this configuration uses (root and installed modules), checks version constraints, and confirms with terraform validate on a temp copy. Downloads the target provider from the registry into a cache (temp dirs only; never touches dir). For the latest version number or upgrade guides, use the official terraform MCP server.
Inputs: version (required), provider (required), dir, validate, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 9 tools
Each tool targets a distinct action on a clearly bounded resource: listing state addresses vs. showing a resource's attributes, listing pinned providers vs. searching type names vs. fetching a schema vs. validating argument paths, plus plan summary, module interface, and upgrade impact. Descriptions make the boundaries explicit, so an agent should not misselect.
All names share the tf_ prefix and snake_case, giving a predictable namespace. Most follow verb_noun (tf_list_providers, tf_get_schema), but a few use noun-oriented phrasing (tf_state_addresses, tf_module_interface, tf_upgrade_impact), a minor deviation rather than a real inconsistency.
Nine tools is well-scoped for a read-only, cwd-focused Terraform analysis server. Every tool has a clear niche, with no redundant wrappers or filler.
Coverage is strong for a read-only analysis surface: state inspection, pinned provider/type schema, argument validation, plan summarization, module interfaces, and upgrade impact. Minor gaps exist (e.g. no standalone workspace listing or outputs-only view), but the design intentionally defers mutation/execution and registry lookups to the official terraform MCP server.
Maintenance
Related MCP Connectors
Unified API to query AWS, GCP, Azure and generate Terraform/CLI execution kits for AI agents.
Read-only AI coding tools for change verification, release readiness, capacity, and guidance.
Run verified read-only code tools: quant diagnostics + agent-ops preflight, no source exposure.
A read-only verified record of agent-operable GTM tools: search, fetch, compare, track changes.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA read-only MCP server for inspecting and diagnosing HCP Terraform workspaces, runs, and plan summaries from ChatGPT and other MCP clients. It provides tools to list workspaces, inspect runs, and summarize plans without exposing sensitive data.MIT
- AlicenseNot gradedqualityBmaintenanceProvides agents with read-only access to local code architecture graphs (structure from tree-sitter, semantic summaries from LLMs), including per-workspace graph/config/status/rebuild tools, while enforcing that topology comes only from static analysis.6 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables safe, local DevOps inspection through a JSON-lines server with schema validation, path isolation, and redaction, supporting read-only Git operations, Kubernetes YAML validation, Terraform plan summaries, and sanitized log analysis.MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query a repository's verified semantic model, including capabilities, ownership, contracts, constraints, impact, health, and verification status, without re-deriving project facts. It exposes read-only tools for inspecting, reasoning about, and verifying the provenance-tracked surface.2Apache 2.0