<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sanjeev Kumar</title>
    <description>The latest articles on DEV Community by Sanjeev Kumar (@sanjeevkkansal).</description>
    <link>https://dev.to/sanjeevkkansal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3944644%2Fdc739745-788c-42e6-a963-1f0aa7fe5e0f.png</url>
      <title>DEV Community: Sanjeev Kumar</title>
      <link>https://dev.to/sanjeevkkansal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanjeevkkansal"/>
    <language>en</language>
    <item>
      <title>HashiCorp built an MCP server for writing Terraform. I built one for reviewing it</title>
      <dc:creator>Sanjeev Kumar</dc:creator>
      <pubDate>Thu, 21 May 2026 18:11:24 +0000</pubDate>
      <link>https://dev.to/sanjeevkkansal/hashicorp-built-an-mcp-server-for-writing-terraform-i-built-one-for-reviewing-it-9mn</link>
      <guid>https://dev.to/sanjeevkkansal/hashicorp-built-an-mcp-server-for-writing-terraform-i-built-one-for-reviewing-it-9mn</guid>
      <description>&lt;p&gt;A few weeks ago HashiCorp shipped &lt;a href="https://github.com/hashicorp/terraform-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-mcp-server&lt;/code&gt;&lt;/a&gt;. It's an official MCP server that lets a model lean on the Terraform Registry: search providers, pull module docs, manage HCP Terraform workspaces. The shape is "help the model author IaC." That's a genuinely useful tool and a clear signal that Terraform-via-MCP is now a category, not a curiosity.&lt;/p&gt;

&lt;p&gt;But it doesn't help with the part of Terraform I spend the most time stressed about: reviewing somebody else's plan.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt; outputs are long. Real production plans run into the thousands of lines. The risky changes (an IAM grant going &lt;code&gt;*&lt;/code&gt;, a security group opening to &lt;code&gt;0.0.0.0/0&lt;/code&gt;, an RDS instance being replaced) hide between a hundred routine attribute updates. Code review tools don't help because the danger isn't in the HCL diff, it's in the planned actions.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/sanjeevkkansal/tf-review-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;tf-review-mcp&lt;/code&gt;&lt;/a&gt;. It's an MCP server scoped to one job: parse &lt;code&gt;terraform show -json&lt;/code&gt; output and surface what a human reviewer actually cares about, structured for an LLM to quote and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;review_plan(plan_json_path)&lt;/code&gt; returns a structured summary: action counts, high-blast-radius resource changes, stateful destroys, and diff-aware public-exposure findings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suggest_review_comments(plan_json_path)&lt;/code&gt; returns a list of &lt;code&gt;{address, severity, comment}&lt;/code&gt; objects ready to drop into a PR review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server flags three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-risk resource types&lt;/strong&gt; (warn). A conservative built-in list across AWS, GCP, and Azure: IAM, KMS, RDS, security groups, S3, EKS, GKE, Cloud SQL, GCS, Cloud DNS, GCE firewalls, Key Vault. Easy to extend in source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateful destroys&lt;/strong&gt; (blocker). When a stateful resource like &lt;code&gt;aws_db_instance&lt;/code&gt;, &lt;code&gt;google_sql_database_instance&lt;/code&gt;, or &lt;code&gt;google_compute_instance&lt;/code&gt; is scheduled for delete or replace. The GCE case matters more than people realize: a &lt;code&gt;replace&lt;/code&gt; on a Compute Engine VM with local SSDs nukes both the boot disk and any local-SSD attachments. Silent data loss if a reviewer misses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public exposure changes&lt;/strong&gt; (blocker). Diff-aware. The server compares &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; on every &lt;code&gt;google_compute_firewall&lt;/code&gt; change and fires when &lt;code&gt;source_ranges&lt;/code&gt; newly contains &lt;code&gt;0.0.0.0/0&lt;/code&gt; or &lt;code&gt;::/0&lt;/code&gt;. That single check has caught more dumb mistakes for me than any static analyzer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Given a plan that deletes a Cloud SQL instance, replaces a GCE VM, and widens a firewall to the public internet, the server returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"counts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stateful_destroys"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_compute_instance.op_geth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_sql_database_instance.indexer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"public_exposure_changes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"google_compute_firewall.rpc_public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"finding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source_ranges now includes 0.0.0.0/0 (public exposure)"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"2 stateful resource(s) scheduled for destroy/replace. Verify backups and migration plan before applying."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"1 firewall change(s) widen public exposure. Confirm intent before applying."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asked to summarize, Claude reads this and produces:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Blocker-severity items (3)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google_compute_instance.op_geth&lt;/code&gt;: Delete/recreate of a stateful compute instance. Confirm backup, migration, and rollback plan before merging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google_sql_database_instance.indexer&lt;/code&gt;: Cloud SQL instance is being deleted. Confirm backup, migration, and rollback plan before merging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google_compute_firewall.rpc_public&lt;/code&gt;: &lt;code&gt;source_ranges&lt;/code&gt; now includes &lt;code&gt;0.0.0.0/0&lt;/code&gt; (public exposure). Confirm this is intentional and matches firewall policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a PR review comment I'd actually merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP, not a CLI
&lt;/h2&gt;

&lt;p&gt;The same parser could be a CLI. The reason MCP matters is the conversational shape. When I'm reviewing a plan, I want to ask follow-up questions: "is the SQL delete a replace or a hard destroy?", "what changed about that firewall?", "draft me a PR comment for the IAM grant." The model can pull the right tool, narrow on the right resource, and write something useful, because the underlying tool returns &lt;em&gt;data&lt;/em&gt;, not prose. Lower hallucination surface. Higher signal per token.&lt;/p&gt;

&lt;p&gt;Most community Terraform-MCP experiments wrap the CLI ("run &lt;code&gt;terraform plan&lt;/code&gt; for me"). That's the wrong abstraction for review. You don't want the model running plan; you want it reasoning about a plan that already ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture and deployment
&lt;/h2&gt;

&lt;p&gt;The parser is in &lt;code&gt;review.py&lt;/code&gt; as pure functions with no MCP imports: plan JSON in, &lt;code&gt;ReviewSummary&lt;/code&gt; out. The MCP wrapper is in &lt;code&gt;server.py&lt;/code&gt;, about a hundred lines. The split means the parser can be unit-tested, dropped into CI, or audited without touching the transport.&lt;/p&gt;

&lt;p&gt;Deployment is local stdio. Always. The MCP client launches the server as a child process. Plan JSON never leaves your laptop. No auth, no shared state, no network listener.&lt;/p&gt;

&lt;p&gt;Plan JSON contains IAM relationships, account IDs, security group rules, and resource counts. Hosted "send us your plan" services are a recon goldmine waiting to happen. The architecture should make leakage impossible by default. A self-hosted HTTP variant for CI is a v2 idea, with auth and a path allowlist, not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is differentiated
&lt;/h2&gt;

&lt;p&gt;HashiCorp's official server covers Registry lookup and HCP workspace management. Helpful for writing.&lt;/p&gt;

&lt;p&gt;This one covers plan review. Helpful for reviewing.&lt;/p&gt;

&lt;p&gt;Both ship as MCP servers. Both can be installed in the same client. The model picks the right tool per task. They're complementary by design, not competitive.&lt;/p&gt;

&lt;p&gt;I haven't found another MCP server scoped specifically to plan review with LLM-friendly structured output, as of writing. If one exists, I want to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;estimate_cost_delta&lt;/code&gt; wrapping Infracost.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_policy&lt;/code&gt; wrapping Conftest, so teams can bring their own Rego.&lt;/li&gt;
&lt;li&gt;Configurable &lt;code&gt;HIGH_RISK_TYPES&lt;/code&gt; via a YAML file so each team can codify its own blast-radius rules.&lt;/li&gt;
&lt;li&gt;More diff-aware checks: &lt;code&gt;aws_security_group&lt;/code&gt; ingress widening, IAM &lt;code&gt;*&lt;/code&gt; grants, &lt;code&gt;google_storage_bucket&lt;/code&gt; &lt;code&gt;force_destroy&lt;/code&gt; toggles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo lives here: &lt;a href="https://github.com/sanjeevkkansal/tf-review-mcp" rel="noopener noreferrer"&gt;github.com/sanjeevkkansal/tf-review-mcp&lt;/a&gt;. It's MIT-licensed, v0.2, and very much open to PRs, especially if you have a war story about a plan that should have been caught.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sanjeev Kumar is an infrastructure engineer working on platform tooling, AI-assisted ops, and infra that doesn't wake people up at 3am.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terraform</category>
      <category>mcp</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
