<?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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3944644%2F4638c93d-8a74-49c1-8d71-d335513f2b7a.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>There are MCP servers for building on Solana. I built one for operating the validators underneath.</title>
      <dc:creator>Sanjeev Kumar</dc:creator>
      <pubDate>Fri, 26 Jun 2026 21:10:36 +0000</pubDate>
      <link>https://dev.to/sanjeevkkansal/there-are-mcp-servers-for-building-on-solana-i-built-one-for-operating-the-validators-underneath-2bc9</link>
      <guid>https://dev.to/sanjeevkkansal/there-are-mcp-servers-for-building-on-solana-i-built-one-for-operating-the-validators-underneath-2bc9</guid>
      <description>&lt;p&gt;There are plenty of MCP servers for building on Solana: querying chain data, sending transactions, talking to programs. They all assume the nodes underneath are simply there. But someone has to run those nodes, and for independent operators that someone is doing it by hand. That is the part I spend the most time stressed about.&lt;/p&gt;

&lt;p&gt;agave v3.0 dropped prebuilt validator binaries, so now I build from source. Restart a voting validator during its own leader slots and it skips blocks. Remove the last record from a DNS pool and the endpoint goes dark. Push a binary whose geyser plugin does not match and the node will not start. None of these have an undo button.&lt;/p&gt;

&lt;p&gt;MCP made me want to hand that work to an agent. The hard part was never wiring up tools. It was making sure an agent operating a live validator could not break it. So I built solfleet, an MCP server (and CLI) for operating independent Solana validators and RPC nodes, designed so an agent can drive it without being able to cause an outage by accident.&lt;/p&gt;

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

&lt;p&gt;solfleet describes a fleet across devnet, testnet, and mainnet in one config file, and exposes a set of operations: Solana-aware status, in-place upgrades that build agave from source and distribute it, voting-validator provisioning, and health-driven DNS failover. The read operations are open. Every operation that changes a node is dry-run by default, checked against a policy, written to an audit log, and never goes near a keypair.&lt;/p&gt;

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

&lt;p&gt;Status is the first place the design shows up. A generic health check sees HTTP 200 and calls it healthy. A Solana node can be 500 slots behind and still return 200, so the status is Solana-aware: slot lag against the cluster head, delinquency, and version drift.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLUSTER  NODE   ROLE  HEALTH  VERSION     SLOT LAG  VOTE
devnet   rpc-1  rpc   ok      4.1.0-rc.1  0         -
devnet   rpc-2  rpc   ok      4.1.0-rc.1  0         -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more important behavior is what happens when you ask for a change. Asking to upgrade a node does not upgrade it. It returns the ordered plan and the gate decision:&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;"decision"&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;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"upgrade"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rpc-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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dry-run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"plan"&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;"on builder 'build-1': build agave 4.1.0 from source"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"distribute artifact set to rpc-1; checksum-verify each (abort on mismatch)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"stop solana-validator, swap, start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"swap /usr/local/bin/agave-validator + geyser .so + version marker atomically"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"wait until healthy + caught up to https://api.devnet.solana.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"verify reported version == 4.1.0; record before/after"&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;"reasons"&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="s2"&gt;"dry-run: preflight checks pass; pass confirm=true to execute"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To actually run it, the call needs &lt;code&gt;confirm=true&lt;/code&gt;. A model that forgets to confirm, or decides it already did, produces a plan, not an outage.&lt;/p&gt;

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

&lt;p&gt;I wrote the CLI first, and it still exists. But the reason I reached for MCP is that the work I want help with is conversational and judgment-heavy, not scripted. "Is anything in the fleet behind?" "Plan an upgrade of the Frankfurt RPC to 4.1.0 and tell me what it would do." "One node looks delinquent, what does its vote account show?" Those are questions, and an agent that can call &lt;code&gt;fleet_status&lt;/code&gt;, &lt;code&gt;vote_status&lt;/code&gt;, and &lt;code&gt;plan_node_upgrade&lt;/code&gt; and reason over the results is genuinely useful in a way a flag-heavy CLI is not.&lt;/p&gt;

&lt;p&gt;The CLI is the right tool for cron and CI. MCP is the right tool for the operator sitting with the fleet at 2am trying to understand what is actually wrong. solfleet ships both over the same core.&lt;/p&gt;

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

&lt;p&gt;solfleet runs on the operator's machine (or a small VM), not on the nodes. It talks to the fleet over JSON-RPC to read and over SSH to act. Upgrades are build-and-distribute: a dedicated builder host compiles agave together with the ABI-matched Yellowstone geyser plugin (the &lt;code&gt;.so&lt;/code&gt; is locked to the agave version, so the two move in lockstep or the node bricks), caches the artifact set, and the executor distributes it. Each target re-computes the sha256 and compares it to the builder before any swap. Only verified files are swapped, atomically, then the node is cycled and watched until it catches up.&lt;/p&gt;

&lt;p&gt;The MCP server is stdio, so it runs locally next to your keys and config. There is no hosted endpoint holding your fleet's access.&lt;/p&gt;

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

&lt;p&gt;The interesting part is not that it manages Solana nodes. It is the set of rules that make it safe to put in front of an agent, and those rules are the whole point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dry-run by default.&lt;/strong&gt; Every mutation returns its plan and preflight and changes nothing without &lt;code&gt;confirm=true&lt;/code&gt;. This single default removes most of the risk of pointing an agent at the tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A policy gate that runs in both modes.&lt;/strong&gt; Per-cluster allowed versions, a disk-free floor, and a minimum leader-free window for voting validators. The gate runs in dry-run too, so the preview predicts whether the real run would be allowed. There is no "looked fine in dry-run, failed on execute" gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never touches keys.&lt;/strong&gt; solfleet does not read, move, or generate identity or vote keypairs. Voting-validator identity failover is deliberately out of scope, because automating it invites double-signing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything is audited.&lt;/strong&gt; Every call, dry-run or execute, goes to a SQLite log: operation, node, decision, reasons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The domain knowledge is in the safety, not just the status.&lt;/strong&gt; Restarts are leader-aware. The failover loop refuses to ever empty a pool, even if every member is failing, because serving a degraded node beats serving NXDOMAIN.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is what a generic "run commands over SSH" MCP server cannot give you. The safety has to understand validators to be safe.&lt;/p&gt;

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

&lt;p&gt;The read path, upgrades, provisioning, and the DNS driver are tested live on a disposable devnet node and a real Cloudflare zone. The autonomous failover loop and the Route53 driver are still unit-tested only, and an HTTP transport (for a team sharing one server over a tailnet) is the next milestone. The README has an honest status breakdown of what is proven live versus not.&lt;/p&gt;

&lt;p&gt;It is open source under Apache-2.0, on PyPI, and in the official MCP registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;solfleet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source and design notes: &lt;a href="https://github.com/sanjeevkkansal/solfleet" rel="noopener noreferrer"&gt;github.com/sanjeevkkansal/solfleet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you operate your own validators, I would like to hear how you handle upgrades and failover today, and what a tool like this would have to do before you would let an agent near it. And if you have a story about a validator upgrade that went sideways, those are the cases I want to design against.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>devops</category>
      <category>mcp</category>
      <category>showdev</category>
    </item>
    <item>
      <title>What one week of new Ethereum contracts looks like through a scam-detection lens</title>
      <dc:creator>Sanjeev Kumar</dc:creator>
      <pubDate>Sat, 06 Jun 2026 06:46:02 +0000</pubDate>
      <link>https://dev.to/sanjeevkkansal/what-one-week-of-new-ethereum-contracts-looks-like-through-a-scam-detection-lens-599n</link>
      <guid>https://dev.to/sanjeevkkansal/what-one-week-of-new-ethereum-contracts-looks-like-through-a-scam-detection-lens-599n</guid>
      <description>&lt;p&gt;I pulled every top-level contract deployed on Ethereum mainnet in the week ending 2026-04-10. There were 8,257 of them. I enriched each one with its deployed bytecode, verified Solidity source where available, and its deployer's first-tx history. Then I scored each contract against five rule families (contract name, bytecode shape, source patterns, deployer reputation, known-drainer hashes) and used Claude Opus 4.7 to write structured walkthroughs for the top scores.&lt;/p&gt;

&lt;p&gt;192 contracts came back as high-confidence scam patterns. All 192 are one specific scam family: "FlashUSDT" and its liquidity-bot variant, a fake-Tether template used in off-chain social engineering. Manual review of a stratified sample confirmed 100% precision in the very-high score band. Cross-referencing against ScamSniffer's public blacklist found zero overlap, because public scam lists track established drainer infrastructure, not freshly-deployed scam tokens. The lead time is the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;The pipeline is four stages. Each stage writes JSON to disk so the run is resumable and the dataset is inspectable end-to-end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ingest.&lt;/strong&gt; Block-by-block scan of mainnet blocks 24,795,371 to 24,845,605 (seven days). For every transaction with &lt;code&gt;to == null&lt;/code&gt;, record the deployed contract address, deployer, gas used, and init bytecode. ~17 minutes against QuickNode at concurrency 10. The v0.1 ingest catches only top-level deploys; contracts spawned by internal &lt;code&gt;CREATE&lt;/code&gt; and &lt;code&gt;CREATE2&lt;/code&gt; from factories are out of scope for this run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enrich.&lt;/strong&gt; For each of the 8,257 contracts, call &lt;code&gt;eth_getCode&lt;/code&gt; for the deployed runtime bytecode, then Etherscan's &lt;code&gt;getsourcecode&lt;/code&gt; for the verified Solidity if it exists, and &lt;code&gt;txlist&lt;/code&gt; against the deployer for their first-tx timestamp. Throttled to 2.5 calls per second to stay safely under Etherscan's free-tier rate cap. Per-contract files on disk make the ~75-minute run resumable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score.&lt;/strong&gt; Five rule families produce findings, each tagged with a severity (blocker / warn / info) and a confidence (0.0 to 1.0). The score is a weighted sum, capped at 1.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt;: contract-name pattern match against a known-scam dictionary&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bytecode&lt;/code&gt;: empty runtime (deploy-and-destruct), tiny runtime, known-drainer sha256&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;source&lt;/code&gt;: regex over verified Solidity for blacklist, pause, owner-mint, asymmetric fees&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deployer&lt;/code&gt;: fresh EOA, no prior tx, high-volume deployer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drainer-hash&lt;/code&gt;: optional bytecode-hash list (empty in v0.1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Narrate.&lt;/strong&gt; For the top 20 contracts by score, send the source code + heuristic findings + deployer info to Claude Opus 4.7. The model returns structured output via tool use: a plain-English summary, an attack narrative, a confidence assessment, a suggested user action, and a verdict from &lt;code&gt;{matches_scam_pattern, ambiguous, likely_legitimate}&lt;/code&gt;. The model explains; it does not score. ~$3 total cost for the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headline numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top-level deployments in the week&lt;/td&gt;
&lt;td&gt;8,257&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique deployers&lt;/td&gt;
&lt;td&gt;2,428&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified source rate&lt;/td&gt;
&lt;td&gt;32.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contracts with empty runtime bytecode&lt;/td&gt;
&lt;td&gt;580 (7.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total findings&lt;/td&gt;
&lt;td&gt;~6,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKER-severity contracts&lt;/td&gt;
&lt;td&gt;192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-blocker (score &amp;gt;= 0.8)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Score distribution:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;Contracts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.0 (no findings)&lt;/td&gt;
&lt;td&gt;2,698 (32.7%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.0 - 0.2&lt;/td&gt;
&lt;td&gt;4,441 (53.8%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.2 - 0.4&lt;/td&gt;
&lt;td&gt;911 (11.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.4 - 0.6&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6 - 0.8&lt;/td&gt;
&lt;td&gt;153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8 - 1.0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shape is what you'd expect: a long tail of low-signal contracts, a thin cluster of obvious bad ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 192 are one family
&lt;/h2&gt;

&lt;p&gt;Top 10 verified contract names in the dataset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlashUSDT&lt;/td&gt;
&lt;td&gt;134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERC1967Proxy&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MintBurnTeamToken&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EVMToken&lt;/td&gt;
&lt;td&gt;111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SimpleSwapToken&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TransparentUpgradeableProxy&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlashUSDTLiquidityBot&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forwarder&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ProxyAdmin&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;134 FlashUSDT + 58 FlashUSDTLiquidityBot = 192. That is the entire BLOCKER tier. The other top-frequency names (ERC1967Proxy, TransparentUpgradeableProxy, ProxyAdmin) are OpenZeppelin upgrade-pattern boilerplate and largely benign; MintBurnTeamToken / EVMToken / SimpleSwapToken / Token are generic token-generator boilerplate that scores as WARN, not BLOCKER.&lt;/p&gt;

&lt;p&gt;The FlashUSDT family was distributed across 59 unique deployer EOAs in the seven-day window. The top deployer accounts for 12 FlashUSDT deployments and zero non-scam deployments. The next four account for 11, 8, 7, and 5 respectively. This looks like a campaign with at least dozens of operators using a shared template, not one author.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: FlashUSDT
&lt;/h2&gt;

&lt;p&gt;Contract: &lt;a href="https://etherscan.io/address/0x07d0aacdb2603ed053f2a362c0d4ac618a46f1dc" rel="noopener noreferrer"&gt;0x07d0aacdb2603ed053f2a362c0d4ac618a46f1dc&lt;/a&gt;&lt;br&gt;
Score: 0.90 (&lt;code&gt;blocker&lt;/code&gt;)&lt;br&gt;
Findings: &lt;code&gt;name:high-confidence:FlashUSDT&lt;/code&gt;, &lt;code&gt;deployer:fresh-eoa&lt;/code&gt; (deployer's first tx 0.2 days before deployment), &lt;code&gt;source:owner-mint&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Claude's walkthrough, verbatim:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Summary.&lt;/strong&gt; The contract is a minimal ERC20-like token that hardcodes the name "Tether USD" and symbol "USDT" with 6 decimals, mints an initial 1,000,000 units to the deployer, and exposes an owner-only &lt;code&gt;mint&lt;/code&gt; function that can create unlimited additional supply at any time. It is not affiliated with Tether; it is an independent token deployed by a fresh externally-owned account that simply impersonates USDT's metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attack narrative.&lt;/strong&gt; This contract matches the well-known "Flash USDT" scam template, which is typically used in social-engineering schemes rather than as a direct on-chain drainer. The mechanism by which a user loses funds generally proceeds as follows: (1) The operator sends a victim a large balance of this token, which appears in wallets and block explorers as "Tether USD (USDT)" because the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;symbol&lt;/code&gt; strings are spoofed. (2) The victim is convinced the transfer is real USDT. Common pretexts include OTC trades, "test" payments, escrow proofs, job-offer signing bonuses, or "flash loan" demonstrations. (3) The victim then sends real value (real USDT on the legitimate Tether contract, ETH, goods, services, or grants approvals) to the operator in exchange. (4) Because this token lives at a different contract address than canonical USDT, it has zero market value and cannot be sold or redeemed; the victim is left holding worthless balances while the operator keeps the real assets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confidence assessment.&lt;/strong&gt; Confidence that this matches the fake-USDT scam pattern is very high. The structural signals are unambiguous: the contract name is literally "FlashUSDT", the public metadata is verbatim "Tether USD"/"USDT"/6 decimals, an owner-mint function with no cap exists, the deployer EOA is hours old, and the source comment itself describes mint as "allows owner to create tokens out of thin air."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggested user action.&lt;/strong&gt; Treat any balance of this token as worthless and do not send real assets, USDT, or ETH to anyone who points to it as "proof of funds" or payment. There is no need to revoke approvals; the contract cannot pull tokens from your wallet. Remove or hide the token in your wallet UI so it does not get confused with real USDT (canonical Tether on Ethereum is at &lt;code&gt;0xdAC17F958D2ee523a2206206994597C13D831ec7&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict.&lt;/strong&gt; &lt;code&gt;matches_scam_pattern&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Worked example: FlashUSDTLiquidityBot
&lt;/h2&gt;

&lt;p&gt;Contract: &lt;a href="https://etherscan.io/address/0x42ca40f4d556c50d943de4b6bf427925bba8adc4" rel="noopener noreferrer"&gt;0x42ca40f4d556c50d943de4b6bf427925bba8adc4&lt;/a&gt;&lt;br&gt;
Score: 0.76 (&lt;code&gt;blocker&lt;/code&gt;)&lt;br&gt;
Findings: &lt;code&gt;name:high-confidence:FlashUSDTLiquidityBot&lt;/code&gt;, &lt;code&gt;deployer:fresh-eoa&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The companion contract to the FlashUSDT class. The narrative (in the full dataset) describes the operator simulating a "liquidity bot" that the victim is sold access to or convinced is generating real yield; it pairs with a FlashUSDT deployment to make the demo look credible. Same family, same operator profile, same deploy-time signal.&lt;/p&gt;
&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Manual stratified sample.&lt;/strong&gt; I picked 30 random contracts (6 from each of five score bands) and opened each one on Etherscan. Verdict: 6/6 confirmed scam in the very-high band (score &amp;gt;= 0.8), 1/1 confirmed in the low band (the rest were unclear without further tooling), and 0 confirmed-non-scam anywhere. The "unclear" rate in the middle bands is itself a finding: a human reviewer cannot easily evaluate unverified-bytecode-only contracts without decompilation or interaction simulation. This is the gap the analyzer fills.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Band&lt;/th&gt;
&lt;th&gt;yes&lt;/th&gt;
&lt;th&gt;no&lt;/th&gt;
&lt;th&gt;unclear&lt;/th&gt;
&lt;th&gt;Precision (where decided)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;very-high (&amp;gt;= 0.8)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;high (0.6 - 0.8)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium-high (0.4 - 0.6)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium (0.2 - 0.4)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;low (0.0 - 0.2)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;ScamSniffer cross-reference.&lt;/strong&gt; I downloaded ScamSniffer's public blacklist (2,530 addresses) and checked overlap with the 192 flagged contracts and their 59 unique deployers. Zero overlap. This is a real finding, not a bug. Public scam lists are weeks-to-months lagging indicators: they require a victim to report, a maintainer to confirm, and an entry to land. Heuristics that run at deploy time produce a lead-time signal that public lists structurally cannot match.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The dataset and the analyzer both have honest gaps. Calling them out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Factory deployments are missing.&lt;/strong&gt; Only top-level &lt;code&gt;to == null&lt;/code&gt; deploys are ingested. Factory routers using internal &lt;code&gt;CREATE&lt;/code&gt; / &lt;code&gt;CREATE2&lt;/code&gt; are skipped. Many real scam token launches go through factories. The 8,257 number is a lower bound on actual fresh-contract activity for the week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source-only heuristics on a 32% verified rate.&lt;/strong&gt; Two-thirds of contracts have no Solidity source on Etherscan. The source-text checks (blacklist, pause, owner-mint, asymmetric fees) are blind to those. Bytecode-only heuristics catch some of them (empty-runtime, tiny-runtime), but the long tail of unverified bytecode is the natural next analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One scam family explains the entire BLOCKER tier.&lt;/strong&gt; The FlashUSDT class is so common in this window that it crowds out other classes from the top scores. A different week with a different active campaign would look different. Generalizing the precision number requires multiple windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-curated name patterns.&lt;/strong&gt; The high-confidence name dictionary (FlashUSDT, FlashUSDTLiquidityBot) was seeded from a quick look at the data. It will not catch novel scam classes whose names I haven't seen yet. A pre-registered name list from public scam taxonomies would harden this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No on-chain interaction analysis.&lt;/strong&gt; The first 24 hours of interactions with a deployed contract is a strong signal (real holders vs. only the deployer; transfers in patterns consistent with bots). Pulling that requires either an indexer or expensive log scans. Out of scope for v0.1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No bytecode decompilation.&lt;/strong&gt; Runtime bytecode is hashed but not decompiled. A bytecode-level pattern matcher against well-known drainer kits (Pink Drainer, Inferno Drainer, etc.) is the right next addition. The seed hash list ships empty.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Reproducibility
&lt;/h2&gt;

&lt;p&gt;The whole pipeline is one Python project. Everything ships open source under MIT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sanjeevkkansal/evm-deploy-watch
&lt;span class="nb"&gt;cd &lt;/span&gt;evm-deploy-watch
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev,narrate]"&lt;/span&gt;

&lt;span class="c"&gt;# Set keys in .env:&lt;/span&gt;
&lt;span class="c"&gt;#   QUICKNODE=https://your-quicknode-mainnet-url&lt;/span&gt;
&lt;span class="c"&gt;#   ETHERSCAN_API_KEY=...&lt;/span&gt;
&lt;span class="c"&gt;#   ANTHROPIC_API_KEY=...   (only for the narrate phase)&lt;/span&gt;

&lt;span class="c"&gt;# Reproduce this week's run:&lt;/span&gt;
evm-deploy-watch find-blocks &lt;span class="nt"&gt;--start&lt;/span&gt; 2026-04-03T00:00:00Z &lt;span class="nt"&gt;--end&lt;/span&gt; 2026-04-10T00:00:00Z
evm-deploy-watch ingest &lt;span class="nt"&gt;--start-block&lt;/span&gt; 24795371 &lt;span class="nt"&gt;--end-block&lt;/span&gt; 24845605 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 10
evm-deploy-watch enrich &lt;span class="nt"&gt;--input&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers
evm-deploy-watch score &lt;span class="nt"&gt;--window&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; data/scored_2026_04_w1.json
evm-deploy-watch narrate &lt;span class="nt"&gt;--scored&lt;/span&gt; data/scored_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--window&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-dir&lt;/span&gt; data/narratives &lt;span class="nt"&gt;--n&lt;/span&gt; 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total runtime: ~2 hours for ingest + enrich. Total cost: ~$3 for narration on Claude Opus 4.7 if you run it.&lt;/p&gt;

&lt;p&gt;The raw dataset (window + enriched contracts + deployers + scored output + narratives) is roughly 320 MB. Source code for verified contracts is included verbatim, so antivirus tools may flag the directory once it lands. I exclude it from AV scanning locally and recommend the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd add next
&lt;/h2&gt;

&lt;p&gt;In rough order of payoff:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Factory-deployment ingest.&lt;/strong&gt; Use &lt;code&gt;trace_block&lt;/code&gt; or a derivable equivalent to catch contracts spawned via internal &lt;code&gt;CREATE&lt;/code&gt; / &lt;code&gt;CREATE2&lt;/code&gt;. Probably 4-10x the deployment count, mostly weighted toward scam-factory output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bytecode-hash matching against a real drainer-kit seed.&lt;/strong&gt; Pull known runtime hashes from drainer-archive and ScamSniffer reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-24h interaction analysis.&lt;/strong&gt; "How many distinct callers in the first day" is a much stronger legitimacy signal than name or source matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run on multiple weeks.&lt;/strong&gt; Lets the precision number generalize beyond the single FlashUSDT-heavy window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare against Base mainnet.&lt;/strong&gt; Same pipeline, different chain, different scam economy.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;Tools used: Python 3.13, httpx, pydantic, the Anthropic Python SDK, QuickNode for RPC, Etherscan v2 for source and account lookups, Claude Opus 4.7 for narration. No paid security tools, no SaaS pipelines.&lt;/p&gt;

&lt;p&gt;Everything is in &lt;a href="https://github.com/sanjeevkkansal/evm-deploy-watch" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;. Issues, PRs, and disagreement welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>security</category>
    </item>
    <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>
