Self-Service Infrastructure, Driven by AI: An IaC MCP Server for Azure

Self-service infrastructure driven by AI — an IaC MCP server for Azure

Reading time: 6 minutes, 1 seconds

Every platform team eventually hits the same tension — and an IaC MCP server turned out to be the cleanest way I have found to resolve it.

Product teams want infrastructure now — a database, a storage account, a cluster — and every request lands as a ticket in the platform team’s queue, waiting for a cloud engineer to hand-write the Terraform. The platform team wants every one of those builds to be compliant — correctly named, fully tagged, in an approved region, built from vetted modules, secure by default — which is exactly why the queue exists. Centralizing infrastructure in DevOps, Cloud, and Architecture teams is not bureaucracy; it is how you get consistency, governance, and code someone can actually support at 3am. The cost is throughput: the standards live in engineers’ heads and wiki pages, and every request burns senior time on boilerplate that drifts out of date the moment someone copy-pastes it.

Now add AI coding agents to the picture. Claude Code and GitHub Copilot will cheerfully generate Terraform for you. They will also, left to their own devices, invent resource types, forget the mandatory tags, and pull a random module off the internet that nobody has ever reviewed. That is not a self-service platform — that is a compliance incident waiting to happen.

The instinct is to clamp down: ban the AI, or review every line it writes. I think that is the wrong move. The better move is to constrain the AI instead of fighting it — give it a tool that only ever exposes approved modules and encodes your standards, so the compliant path is the only path it can take. This is the story of building exactly that.

The fix: an IaC MCP server as a guardrail

The Model Context Protocol (MCP) is an open standard for giving an AI agent tools and context in a structured way. Instead of the model guessing, it calls real functions you define. That is the perfect shape for a guardrail.

So the platform has two halves:

  • An IaC MCP server — the guardrail. It exposes your approved Terraform module registry and your org standards as callable tools. The AI can only build from what the server offers.
  • An agent skill — the knowledge. A short instruction file that teaches the agent how to use those tools and in what order, so it behaves like a disciplined platform engineer rather than an improviser.

The flow looks like this:

Architecture: Claude Code and GitHub Copilot call the IaC MCP server over MCP; the server exposes seven tools and serves an approved module registry, the org standards, and the agent skill

The developer types a plain-English request. The agent consults the server, generates Terraform that can only use blessed modules, wires in the mandatory tags and naming, and validates the result — before a single resource is created.

Self-service does not mean ungoverned

Let me head off the objection every senior engineer is already forming: no, this does not mean application developers get terraform apply rights against production. Generating code and deploying it are different privileges, and this platform only changes the first one:

  • Generate — a developer or a cloud engineer, AI-assisted, constrained to approved modules and encoded standards.
  • Review — a pull request, approved by the platform team. Humans still hold the gate.
  • Deploy — the CI/CD pipeline only, behind a policy check (OPA/Conftest) and a plan review. Nobody applies from a laptop.

Governance does not disappear in this model — it moves from a manual gate (a ticket and a human writing HCL from memory) into an automated one that is enforced on every single request, identically, with no tired-engineer variance. The platform team stops being a typing service and becomes the team that owns the guardrails.

And if your org keeps infrastructure work entirely inside the DevOps and Cloud teams — many do, for good reasons — the platform is just as valuable. “Self-service” then means your own engineers self-serve from the approved registry instead of copy-pasting from the last project. Same guardrails, same speed-up, zero change to who holds the keys.

What the server hands the AI

The server exposes a small, sharp set of tools:

  • list_modules / search_modules — browse the approved registry.
  • get_module — full spec of one module: its source, version, required inputs, outputs, and a compliant example.
  • list_standards — the naming pattern, allowed regions, mandatory tags, and the security baseline the agent must honor.
  • generate_module_usage — scaffolds a compliant block with the naming convention, the correct resource-group wiring, and the required tags already filled in.
  • validate_config — a fast policy pre-flight that runs before terraform plan.

The agent’s standing instructions are blunt: only approved modules, standards first, generate rather than guess, always validate, and never write a secret as a literal.

The registry is hybrid — and free

Here is the part that surprises people: a “module registry” does not have to be a product you pay for. It is just where versioned module code lives. So the platform is deliberately hybrid:

  • Azure Verified Modules (AVM) as the baseline — Microsoft’s own curated, compliance-grade modules, published free on the public Terraform Registry. Real, maintained, and deployable today.
  • Your own modules referenced by a git:: GitHub source for anything AVM does not cover. Same experience to the agent; no registry server to run.

To the AI, both look identical — it queries them the same way. The “I don’t have a private registry” blocker simply evaporates.

One sentence in, compliant Terraform out

This is the part that still feels like magic. You open Claude Code in a project folder and type:

“Provision a storage account and a Postgres database for the payments app in prod.”

Flow from plain English to compliant Terraform: request, then list_standards, search/get_module, generate_module_usage, and validate_config, producing a compliant main.tf that deploys to Azure

The agent runs list_standards, finds the right modules with search_modules and get_module, scaffolds each block with generate_module_usage, stitches them together, and validates. What comes out is real, deployable Terraform built on Azure Verified Modules:

module "resource_group" {
source = "Azure/avm-res-resources-resourcegroup/azurerm"
version = "0.4.0"
name = "acme-prod-payments-rg01"
location = "westeurope"
tags = local.common_tags
}
module "storage_account" {
source = "Azure/avm-res-storage-storageaccount/azurerm"
version = "0.7.2"
name = "acmeprodpaymentsst01"
location = "westeurope"
parent_id = module.resource_group.resource_id
shared_access_key_enabled = false # Entra ID auth only
tags = local.common_tags
}

Notice the details the agent got right without being told each time: the org naming standard (acme-prod-payments-...), the storage name flattened to satisfy Azure’s lowercase-alphanumeric rule, the mandatory tags, Entra-ID-only auth, and — crucially — the correct resource-group wiring. AVM modules are inconsistent here: some take parent_id (the resource group’s ID), others take resource_group_name (a string). The server records that per module, so the generated code wires it correctly every time. That single detail is the difference between code that plans and code that errors.

The plot twist: the AI audited my platform

I expected the AI to consume the platform. I did not expect it to audit it.

On an early run, the agent was generating a Postgres server whose admin password should come from Key Vault. It paused and pointed out that my Key Vault module exposed no way to actually store a secret — no secrets input, no secret URI output — yet another module’s example referenced exactly that. It had found a genuine dead reference in my own registry. On the same run it flagged a false positive in my validator, which was treating a Terraform provider source as if it were a module source and rejecting it.

Both were real defects. Neither was something I would have caught by reading my own code, because I wrote the bug and the assumption in the same breath. The lesson stuck with me: an AI agent driving your platform is also a fuzzer for it. It exercises every assumption you encoded, and it complains — politely — when one does not hold. Fixing those two issues made the platform measurably better, and it only happened because I let the agent actually run.

Compliance the AI can’t skip

The guardrail is validate_config. It is a fast, heuristic pre-flight — not a replacement for terraform validate or an OPA/Conftest gate in CI, but a cheap check that catches the common violations before anything reaches a plan. Feed it a snippet that breaks the rules and it pushes back:

source = "github.com/random/module" -> REG-SRC: source not in the approved registry
location = "eastus" -> REGION: not an approved region
password = "hunter2" -> SEC-SECRET-001: hardcoded secret; use Key Vault

The encoded standards are exactly what you would expect a platform team to demand: a naming pattern with a regex, an allow-list of regions, five mandatory tags, and a security baseline (TLS 1.2, no public blob access, secrets from Key Vault or write-only arguments, remote state with versioning). The agent reads these before it writes anything, and the validator catches it if it slips.

From laptop to a team: hosting it on Azure

Locally, none of this needs babysitting — Claude Code auto-spawns the server over stdio per session. But a team wants one shared instance, not one server per laptop. So the same server runs in an HTTP mode and ships with a one-command deploy to Azure Container Apps:

az login
.\deploy\deploy-azure.ps1 # builds the image in Azure, deploys, prints the URL

Developers then point Claude Code or Copilot at that one URL, and the platform team updates the registry centrally — everyone sees new approved modules instantly.

And because I actually deployed it, the cloud caught a bug the laptop never could. The hosted endpoint kept returning 421 Misdirected Request. The cause: MCP’s streamable-HTTP transport has built-in DNS-rebinding protection that validates the Host header against an allow-list of localhost — perfectly sensible for a dev server, fatal behind a cloud ingress whose hostname it had never heard of. A one-setting fix, but a beautiful illustration of the point: you only find the network-shaped bugs by running on the network.

The end-to-end proof

The final test was the honest one: let the agent generate a project, then deploy it for real. A resource group and a storage account, built from AVM, with state in my own Azure storage account. terraform init, validate, plan, apply — two resources created in my subscription, state written to my container, then terraform destroy to clean up. The whole loop, start to finish: plain English to the agent, approved modules from the server, compliant Terraform, real Azure resources, clean teardown.

Try it yourself

The whole thing is open source: github.com/net9876/claude-iac-mcp-server.

Clone it, pip install -r requirements.txt, run the smoke test, wire the included .mcp.json into Claude Code, and ask it for infrastructure. The org name is a placeholder — rename it to yours, point the registry at your own AVM selection plus any git modules you own, and you have a self-service platform of your own. Hosting it for a team is the one-command deploy above.

A fair caveat: the heuristic validator is a pre-flight, not the enforcement gate — in a real org you would back it with OPA/Conftest in CI and put authentication in front of the hosted endpoint. The repository’s notes call out exactly those next steps.

And if AI-with-guardrails is your kind of lab, this pairs well with my earlier build: an agentic CloudOps review system on Microsoft Foundry and Azure Functions — the same philosophy, pointed at operations instead of provisioning.

The one-sentence version

Don’t ask the AI to be disciplined — give it a tool that makes the compliant path the only path, and let it generate; your pipeline still decides what deploys.

The agent stops being a liability and becomes the fastest, most consistent engineer on the platform team — one that queries your approved modules, honors your standards, validates its own output, and occasionally finds the bugs you left in the guardrail itself. The keys to production never move; the queue in front of them just disappears.

Facebook
Twitter
LinkedIn
Email

Leave a Reply

Get new articles by email

Practical Cloud, DevOps and AI walkthroughs

We don’t spam! Read our privacy policy for more info.

Discover more from HandsOnAzure

Subscribe now to keep reading and get access to the full archive.

Continue reading