About Me

My photo
I am an MCSE in Data Management and Analytics, specializing in MS SQL Server, and an MCP in Azure. With over 19+ years of experience in the IT industry, I bring expertise in data management, Azure Cloud, Data Center Migration, Infrastructure Architecture planning, as well as Virtualization and automation. I have a deep passion for driving innovation through infrastructure automation, particularly using Terraform for efficient provisioning. If you're looking for guidance on automating your infrastructure or have questions about Azure, SQL Server, or cloud migration, feel free to reach out. I often write to capture my own experiences and insights for future reference, but I hope that sharing these experiences through my blog will help others on their journey as well. Thank you for reading!

Supercharge Your Terraform Development with Claude Code: Agent + MCP Setup Guide

 


Introduction

Writing infrastructure as code with Terraform requires deep knowledge of provider APIs, best practices, security patterns, and HCL syntax. What if you could have an expert Terraform developer assisting you directly in your terminal, with access to your actual infrastructure and codebase?

This guide shows you how to configure Claude Code with a specialized Terraform agent and MCP (Model Context Protocol) servers to dramatically improve your infrastructure development workflow.

What You'll Get

By the end of this setup, you'll be able to:

  • Write Terraform code faster with expert guidance on HCL syntax and patterns
  • Access your existing infrastructure repos directly through Claude
  • Get real-time best practice recommendations for security, cost, and performance
  • Debug Terraform errors with context-aware assistance
  • Review and refactor existing infrastructure code with AI-powered insights
  • Query cloud provider documentation without leaving your terminal

Prerequisites

Before you begin, ensure you have:

  • Claude Code installed (Installation Guide)
  • Node.js 18+ installed (node --version)
  • Access to your Terraform projects/repositories
  • (Optional) GitHub Personal Access Token for repo access
  • (Optional) Cloud provider credentials for validation

Part 1: Creating the Terraform Expert Agent

Agents in Claude Code are specialized personas that customize Claude's behavior for specific tasks. Think of them as expert colleagues focused on particular domains.

Step 1: Create the Agent

In your terminal, run:

claude code /agents

Or access the agents menu through your Claude Code interface.

Step 2: Configure the Agent

Agent Identifier (slug):

terraform-iac-expert

Agent Description:

Terraform Infrastructure as Code Expert

This agent is a specialized Terraform developer that writes, reviews, and maintains infrastructure as code using Terraform and OpenTofu. It should be used when you need to:

Primary Use Cases:
- Create new Terraform configurations from scratch for cloud infrastructure (AWS, Azure, GCP, multi-cloud)
- Refactor existing Terraform code to improve structure, modularity, and best practices
- Debug Terraform errors, plan failures, or state file issues
- Write reusable Terraform modules with proper variable definitions, outputs, and documentation
- Convert manual infrastructure setups or CloudFormation/ARM templates to Terraform
- Implement infrastructure changes following GitOps workflows
- Review and optimize Terraform code for security, cost, and performance
- Generate terraform.tfvars files and manage environment-specific configurations

Technical Capabilities:
- Write idiomatic HCL (HashiCorp Configuration Language) following official style guidelines
- Implement proper resource dependencies, data sources, and lifecycle management
- Use Terraform state management best practices (remote backends, state locking, workspaces)
- Apply security best practices (least privilege IAM, encryption, secret management)
- Create well-structured modules with clear interfaces and comprehensive variable validation
- Implement dynamic blocks, for_each, count, and conditional logic appropriately
- Use locals, functions, and expressions to make code DRY and maintainable
- Handle provider configurations, version constraints, and provider aliasing
- Implement proper tagging strategies and naming conventions
- Generate comprehensive documentation and examples for modules
- Suggest testing strategies using terraform validate, plan, and external tools
- Recommend CI/CD pipeline configurations for Terraform automation
- Identify potential issues with drift detection and state management
- Optimize provider configurations for performance and cost

When to Use This Agent:
- Starting a new infrastructure project that needs Terraform setup
- Migrating existing infrastructure to infrastructure as code
- Troubleshooting "terraform plan" or "terraform apply" errors
- Needing to add new cloud resources with proper Terraform patterns
- Reviewing pull requests for Terraform changes
- Updating deprecated resources or provider versions
- Setting up CI/CD pipelines for Terraform automation
- Creating documentation for Terraform projects
- Implementing compliance and governance guardrails in code
- Converting between different IaC formats (CloudFormation, ARM, Pulumi)
- Performing cost optimization analysis on infrastructure
- Implementing disaster recovery and backup strategies

Working Style:
The agent will ask clarifying questions about cloud provider, environment requirements, existing state, and architectural constraints before writing code. It provides complete, working configurations with comments explaining key decisions, includes relevant outputs and variables, follows the DRY principle, suggests testing approaches, highlights security considerations, and recommends best practices for state management and team collaboration.

Step 3: Save and Verify

Save the agent configuration. You should now be able to invoke it:

claude code --agent terraform-iac-expert

Part 2: Setting Up MCP Servers for Terraform

MCP servers extend Claude's capabilities by connecting it to external tools, APIs, and data sources. For Terraform development, we'll set up access to your filesystem, GitHub repositories, and documentation.

Understanding MCP Architecture

┌─────────────────┐
│   Claude Code   │
│  (with Agent)   │
└────────┬────────┘
         │
         ├──────────┐
         │          │
    ┌────▼────┐ ┌──▼──────────┐
    │   MCP   │ │     MCP     │
    │  Server │ │   Server    │
    │  (FS)   │ │  (GitHub)   │
    └────┬────┘ └──┬──────────┘
         │         │
    ┌────▼────┐ ┌──▼──────────┐
    │  Local  │ │   GitHub    │
    │  Files  │ │    API      │
    └─────────┘ └─────────────┘

Step 1: Locate Your MCP Configuration File

For Claude Desktop (for testing):

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json

For Claude Code: Check the official documentation at https://docs.claude.com for the latest configuration location, as Claude Code is evolving rapidly.

Step 2: Configure Essential MCP Servers

Create or edit your configuration file with the following setup:

{
  "mcpServers": {
    "terraform-workspace": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/path/to/your/terraform/projects",
        "/path/to/your/infrastructure/repos"
      ]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_GITHUB_TOKEN>"
      }
    },
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": {
        "BRAVE_API_KEY": "<YOUR_BRAVE_API_KEY>"
      }
    }
  }
}

Step 3: Set Up GitHub Access (Recommended)

  1. Generate a GitHub Personal Access Token:

    • Go to GitHub Settings → Developer Settings → Personal Access Tokens → Tokens (classic)
    • Click "Generate new token (classic)"
    • Give it a descriptive name: "Claude Code Terraform MCP"
    • Select scopes: repo (Full control of private repositories)
    • Generate and copy the token
  2. Add to your configuration: Replace <YOUR_GITHUB_TOKEN> in the config above with your actual token

Step 4: Configure Filesystem Access

Update the paths in the terraform-workspace configuration to point to your actual Terraform project directories:

"terraform-workspace": {
  "command": "npx",
  "args": [
    "-y",
    "@modelcontextprotocol/server-filesystem",
    "/home/youruser/projects/terraform-infrastructure",
    "/home/youruser/projects/terraform-modules",
    "/home/youruser/company/infrastructure-as-code"
  ]
}

Security Note: Only grant access to directories you want Claude to read. The filesystem MCP server will only have access to explicitly listed paths.

Step 5: Optional - Set Up Brave Search for Documentation

To enable Claude to search Terraform documentation and provider registries:

  1. Get a Brave Search API key from https://brave.com/search/api/
  2. Add it to the configuration above

Step 6: Restart and Verify

  1. Save your configuration file
  2. Restart Claude Code/Desktop completely
  3. Verify MCP servers are loaded:
# In Claude Code, ask:
"What MCP servers are currently available?"

# You should see responses indicating:
# - filesystem (terraform-workspace)
# - github
# - brave-search (if configured)

Part 3: Advanced MCP Setup - Custom Terraform OSS Server

For teams using open-source Terraform, you can create a custom MCP server that provides direct access to Terraform CLI commands, state inspection, and configuration analysis.

Prerequisites

  • Terraform CLI installed and in PATH (terraform version should work)
  • Node.js development environment
  • Access to your Terraform project directories

Step 1: Create MCP Server Project

mkdir terraform-oss-mcp
cd terraform-oss-mcp
npm init -y
npm install @modelcontextprotocol/sdk

Step 2: Implement the MCP Server

Create index.js:

#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { execSync, spawn } from 'child_process';
import { promisify } from 'util';
import { exec } from 'child_process';
import fs from 'fs';
import path from 'path';

const execAsync = promisify(exec);

class TerraformOSSServer {
  constructor() {
    this.workingDir = process.env.TERRAFORM_WORKING_DIR || process.cwd();
    
    this.server = new Server(
      {
        name: 'terraform-oss-mcp',
        version: '1.0.0',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers();
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'terraform_init',
          description: 'Initialize a Terraform working directory. Downloads providers and modules.',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory to initialize (defaults to current working directory)',
              },
              upgrade: {
                type: 'boolean',
                description: 'Upgrade modules and plugins',
                default: false,
              },
            },
          },
        },
        {
          name: 'terraform_validate',
          description: 'Validate the Terraform configuration files in a directory',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory to validate',
              },
            },
          },
        },
        {
          name: 'terraform_plan',
          description: 'Generate and show an execution plan',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform files',
              },
              var_file: {
                type: 'string',
                description: 'Path to variable file (tfvars)',
              },
              out: {
                type: 'string',
                description: 'Path to save the plan file',
              },
            },
          },
        },
        {
          name: 'terraform_show',
          description: 'Show the current state or a saved plan',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform state',
              },
              plan_file: {
                type: 'string',
                description: 'Path to plan file to show',
              },
              json: {
                type: 'boolean',
                description: 'Output in JSON format',
                default: true,
              },
            },
          },
        },
        {
          name: 'terraform_state_list',
          description: 'List resources in the Terraform state',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform state',
              },
            },
          },
        },
        {
          name: 'terraform_state_show',
          description: 'Show detailed state of a specific resource',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform state',
              },
              resource: {
                type: 'string',
                description: 'Resource address (e.g., aws_instance.example)',
              },
            },
            required: ['resource'],
          },
        },
        {
          name: 'terraform_output',
          description: 'Read an output from the Terraform state',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform state',
              },
              name: {
                type: 'string',
                description: 'Name of the output to retrieve (optional, shows all if not specified)',
              },
              json: {
                type: 'boolean',
                description: 'Output in JSON format',
                default: true,
              },
            },
          },
        },
        {
          name: 'terraform_fmt',
          description: 'Format Terraform configuration files to canonical format',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory to format',
              },
              check: {
                type: 'boolean',
                description: 'Check if files are formatted without writing',
                default: false,
              },
              recursive: {
                type: 'boolean',
                description: 'Process files in subdirectories',
                default: true,
              },
            },
          },
        },
        {
          name: 'terraform_providers',
          description: 'Show the providers required for the configuration',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform files',
              },
            },
          },
        },
        {
          name: 'terraform_version',
          description: 'Show the Terraform version',
          inputSchema: {
            type: 'object',
            properties: {},
          },
        },
        {
          name: 'terraform_workspace_list',
          description: 'List all Terraform workspaces',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform configuration',
              },
            },
          },
        },
        {
          name: 'terraform_workspace_show',
          description: 'Show the current workspace name',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform configuration',
              },
            },
          },
        },
        {
          name: 'terraform_graph',
          description: 'Generate a visual representation of the dependency graph',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform files',
              },
              type: {
                type: 'string',
                description: 'Type of graph (plan, apply, etc.)',
                enum: ['plan', 'plan-destroy', 'apply', 'validate', 'refresh'],
              },
            },
          },
        },
        {
          name: 'read_tf_file',
          description: 'Read and parse a Terraform configuration file',
          inputSchema: {
            type: 'object',
            properties: {
              file_path: {
                type: 'string',
                description: 'Path to the .tf file',
              },
            },
            required: ['file_path'],
          },
        },
        {
          name: 'list_tf_files',
          description: 'List all Terraform files in a directory',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory to search',
              },
              recursive: {
                type: 'boolean',
                description: 'Search subdirectories',
                default: false,
              },
            },
          },
        },
        {
          name: 'parse_state_file',
          description: 'Read and parse the Terraform state file (terraform.tfstate)',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing the state file',
              },
            },
          },
        },
        {
          name: 'terraform_console',
          description: 'Evaluate expressions in the Terraform console (useful for testing functions)',
          inputSchema: {
            type: 'object',
            properties: {
              directory: {
                type: 'string',
                description: 'Directory containing Terraform configuration',
              },
              expression: {
                type: 'string',
                description: 'Terraform expression to evaluate',
              },
            },
            required: ['expression'],
          },
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        switch (name) {
          case 'terraform_init':
            return await this.terraformInit(args);
          case 'terraform_validate':
            return await this.terraformValidate(args);
          case 'terraform_plan':
            return await this.terraformPlan(args);
          case 'terraform_show':
            return await this.terraformShow(args);
          case 'terraform_state_list':
            return await this.terraformStateList(args);
          case 'terraform_state_show':
            return await this.terraformStateShow(args);
          case 'terraform_output':
            return await this.terraformOutput(args);
          case 'terraform_fmt':
            return await this.terraformFmt(args);
          case 'terraform_providers':
            return await this.terraformProviders(args);
          case 'terraform_version':
            return await this.terraformVersion();
          case 'terraform_workspace_list':
            return await this.terraformWorkspaceList(args);
          case 'terraform_workspace_show':
            return await this.terraformWorkspaceShow(args);
          case 'terraform_graph':
            return await this.terraformGraph(args);
          case 'read_tf_file':
            return await this.readTfFile(args);
          case 'list_tf_files':
            return await this.listTfFiles(args);
          case 'parse_state_file':
            return await this.parseStateFile(args);
          case 'terraform_console':
            return await this.terraformConsole(args);
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${error.message}\n${error.stderr || ''}`,
            },
          ],
          isError: true,
        };
      }
    });
  }

  getDirectory(args) {
    return args?.directory || this.workingDir;
  }

  async execTerraform(args, directory) {
    const cmd = `terraform ${args.join(' ')}`;
    const { stdout, stderr } = await execAsync(cmd, { 
      cwd: directory,
      maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs
    });
    return { stdout, stderr };
  }

  async terraformInit(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['init', '-no-color'];
    
    if (args?.upgrade) {
      tfArgs.push('-upgrade');
    }
    
    const { stdout, stderr } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Terraform Init Output:\n${stdout}\n${stderr}`,
        },
      ],
    };
  }

  async terraformValidate(args) {
    const directory = this.getDirectory(args);
    const { stdout, stderr } = await this.execTerraform(['validate', '-json', '-no-color'], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformPlan(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['plan', '-no-color', '-input=false'];
    
    if (args?.var_file) {
      tfArgs.push(`-var-file=${args.var_file}`);
    }
    
    if (args?.out) {
      tfArgs.push(`-out=${args.out}`);
    }
    
    const { stdout, stderr } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Terraform Plan:\n${stdout}\n${stderr}`,
        },
      ],
    };
  }

  async terraformShow(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['show', '-no-color'];
    
    if (args?.json) {
      tfArgs.push('-json');
    }
    
    if (args?.plan_file) {
      tfArgs.push(args.plan_file);
    }
    
    const { stdout } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformStateList(args) {
    const directory = this.getDirectory(args);
    const { stdout } = await this.execTerraform(['state', 'list'], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Resources in state:\n${stdout}`,
        },
      ],
    };
  }

  async terraformStateShow(args) {
    const directory = this.getDirectory(args);
    const { stdout } = await this.execTerraform(['state', 'show', args.resource], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformOutput(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['output', '-no-color'];
    
    if (args?.json) {
      tfArgs.push('-json');
    }
    
    if (args?.name) {
      tfArgs.push(args.name);
    }
    
    const { stdout } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformFmt(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['fmt', '-no-color'];
    
    if (args?.check) {
      tfArgs.push('-check');
    }
    
    if (args?.recursive) {
      tfArgs.push('-recursive');
    }
    
    const { stdout, stderr } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Formatted files:\n${stdout}\n${stderr}`,
        },
      ],
    };
  }

  async terraformProviders(args) {
    const directory = this.getDirectory(args);
    const { stdout } = await this.execTerraform(['providers'], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformVersion() {
    const { stdout } = await this.execTerraform(['version'], process.cwd());
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformWorkspaceList(args) {
    const directory = this.getDirectory(args);
    const { stdout } = await this.execTerraform(['workspace', 'list'], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: stdout,
        },
      ],
    };
  }

  async terraformWorkspaceShow(args) {
    const directory = this.getDirectory(args);
    const { stdout } = await this.execTerraform(['workspace', 'show'], directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Current workspace: ${stdout.trim()}`,
        },
      ],
    };
  }

  async terraformGraph(args) {
    const directory = this.getDirectory(args);
    const tfArgs = ['graph'];
    
    if (args?.type) {
      tfArgs.push(`-type=${args.type}`);
    }
    
    const { stdout } = await this.execTerraform(tfArgs, directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Dependency Graph (DOT format):\n${stdout}`,
        },
      ],
    };
  }

  async readTfFile(args) {
    const content = fs.readFileSync(args.file_path, 'utf-8');
    
    return {
      content: [
        {
          type: 'text',
          text: `Content of ${args.file_path}:\n\n${content}`,
        },
      ],
    };
  }

  async listTfFiles(args) {
    const directory = this.getDirectory(args);
    const recursive = args?.recursive || false;
    
    const findTfFiles = (dir, fileList = []) => {
      const files = fs.readdirSync(dir);
      
      files.forEach(file => {
        const filePath = path.join(dir, file);
        const stat = fs.statSync(filePath);
        
        if (stat.isDirectory() && recursive && !file.startsWith('.')) {
          findTfFiles(filePath, fileList);
        } else if (file.endsWith('.tf') || file.endsWith('.tfvars')) {
          fileList.push(filePath);
        }
      });
      
      return fileList;
    };
    
    const tfFiles = findTfFiles(directory);
    
    return {
      content: [
        {
          type: 'text',
          text: `Terraform files found:\n${tfFiles.join('\n')}`,
        },
      ],
    };
  }

  async parseStateFile(args) {
    const directory = this.getDirectory(args);
    const stateFile = path.join(directory, 'terraform.tfstate');
    
    if (!fs.existsSync(stateFile)) {
      throw new Error('terraform.tfstate file not found');
    }
    
    const content = fs.readFileSync(stateFile, 'utf-8');
    const state = JSON.parse(content);
    
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(state, null, 2),
        },
      ],
    };
  }

  async terraformConsole(args) {
    const directory = this.getDirectory(args);
    const expression = args.expression;
    
    return new Promise((resolve, reject) => {
      const child = spawn('terraform', ['console'], {
        cwd: directory,
        stdio: ['pipe', 'pipe', 'pipe'],
      });
      
      let output = '';
      let errorOutput = '';
      
      child.stdout.on('data', (data) => {
        output += data.toString();
      });
      
      child.stderr.on('data', (data) => {
        errorOutput += data.toString();
      });
      
      child.on('close', (code) => {
        if (code !== 0) {
          reject(new Error(`Console exited with code ${code}: ${errorOutput}`));
        } else {
          resolve({
            content: [
              {
                type: 'text',
                text: `Expression: ${expression}\nResult: ${output.trim()}`,
              },
            ],
          });
        }
      });
      
      child.stdin.write(`${expression}\n`);
      child.stdin.end();
    });
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Terraform OSS MCP server running on stdio');
  }
}

const server = new TerraformOSSServer();
server.run().catch(console.error);

Step 3: Update package.json

{
  "name": "terraform-oss-mcp",
  "version": "1.0.0",
  "type": "module",
  "description": "MCP server for OpenSource Terraform CLI operations",
  "bin": {
    "terraform-oss-mcp": "./index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.5.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

Step 4: Make it executable and install

chmod +x index.js

# Install dependencies
npm install

# Link globally for easy access
npm link

# Or install locally in node_modules
npm install

Step 5: Add to MCP Configuration

{
  "mcpServers": {
    "terraform-oss": {
      "command": "node",
      "args": ["/path/to/terraform-oss-mcp/index.js"],
      "env": {
        "TERRAFORM_WORKING_DIR": "/path/to/your/terraform/projects"
      }
    }
  }
}

Or if you installed globally with npm link:

{
  "mcpServers": {
    "terraform-oss": {
      "command": "terraform-oss-mcp",
      "env": {
        "TERRAFORM_WORKING_DIR": "/path/to/your/terraform/projects"
      }
    }
  }
}

Step 6: Test the MCP Server

Restart Claude Code/Desktop and try these commands:

# Check Terraform version
"What's my Terraform version?"

# List all resources in state
"Show me all resources in my Terraform state"

# Validate configuration
"Validate my Terraform configuration"

# Run a plan
"Generate a Terraform plan for my infrastructure"

# Check outputs
"What are the current Terraform outputs?"

# List workspaces
"Show me all Terraform workspaces"

# Format code
"Format all my Terraform files"

Part 4: Using Your Enhanced Terraform Workflow

Now that everything is configured, here's how to use your new superpowers:

Starting a Session

# Start Claude Code with the Terraform agent
claude code --agent terraform-iac-expert

# Or select the agent from the UI

Example Workflows

1. Creating New Infrastructure

You: "Create a production-ready AWS VPC with public and private subnets across 3 availability zones, with NAT gateways and proper tagging"

Claude will:
- Ask clarifying questions (region, CIDR blocks, naming conventions)
- Generate complete Terraform configuration
- Include variables.tf, outputs.tf, and main.tf
- Add security best practices
- Provide usage examples

2. Reviewing Existing Code

You: "Review the VPC configuration in my infrastructure repo and suggest improvements"

Claude will:
- Use GitHub MCP to read your repository
- Analyze the Terraform code
- Identify security issues, cost optimizations
- Suggest refactoring opportunities
- Provide specific code improvements

3. Debugging Terraform Errors

You: "I'm getting an error when running terraform plan: [paste error]"

Claude will:
- Analyze the error message
- Read relevant .tf files from your workspace
- Identify the root cause
- Suggest specific fixes
- Explain why the error occurred

4. Module Development

You: "Create a reusable module for RDS PostgreSQL instances with automated backups, encryption, and monitoring"

Claude will:
- Generate complete module structure
- Include comprehensive variable validation
- Add detailed documentation
- Provide usage examples
- Include outputs for common use cases

5. Infrastructure Migration

You: "Convert this CloudFormation template to Terraform"

[Paste CloudFormation YAML]

Claude will:
- Parse the CloudFormation template
- Generate equivalent Terraform HCL
- Add improvements and best practices
- Note any differences or caveats

6. State Management

You: "How should I structure my Terraform state for a multi-environment, multi-region deployment?"

Claude will:
- Recommend workspace strategy or directory structure
- Suggest backend configuration
- Provide state isolation patterns
- Include example configurations

Part 5: Team Rollout Guide

For Team Leads

1. Create Standardized Configuration

Create a repository with:

terraform-claude-setup/
├── README.md
├── mcp-config-template.json
├── agent-description.md
└── setup-script.sh

2. Setup Script Example

#!/bin/bash
# setup-terraform-claude.sh

echo "Setting up Terraform Claude Code environment..."

# Backup existing config
if [ -f "$HOME/Library/Application Support/Claude/claude_desktop_config.json" ]; then
    cp "$HOME/Library/Application Support/Claude/claude_desktop_config.json" \
       "$HOME/Library/Application Support/Claude/claude_desktop_config.json.backup"
fi

# Copy template
cp mcp-config-template.json "$HOME/Library/Application Support/Claude/claude_desktop_config.json"

# Prompt for tokens
read -p "Enter your GitHub Personal Access Token: " GITHUB_TOKEN
read -p "Enter your Terraform Cloud organization: " TF_ORG
read -sp "Enter your Terraform Cloud token: " TF_TOKEN
echo

# Update config with tokens
sed -i '' "s/<YOUR_GITHUB_TOKEN>/$GITHUB_TOKEN/g" \
    "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
sed -i '' "s/<YOUR_TF_CLOUD_TOKEN>/$TF_TOKEN/g" \
    "$HOME/Library/Application Support/Claude/claude_desktop_config.json"
sed -i '' "s/your-org-name/$TF_ORG/g" \
    "$HOME/Library/Application Support/Claude/claude_desktop_config.json"

echo "Setup complete! Please restart Claude Code."

3. Documentation for Developers

Create internal documentation covering:

  • Why this improves workflow
  • Installation steps
  • Common usage patterns
  • Security considerations
  • Troubleshooting guide

For Individual Developers

Quick Start Checklist:

  • [ ] Install Claude Code
  • [ ] Install Node.js 18+
  • [ ] Create Terraform agent with provided description
  • [ ] Configure MCP servers (at minimum: filesystem + GitHub)
  • [ ] Generate GitHub Personal Access Token
  • [ ] Update configuration file
  • [ ] Restart Claude Code
  • [ ] Test with simple query: "List available MCP servers"
  • [ ] Try example workflow: "Review my latest Terraform changes"

Part 6: Best Practices and Tips

Security Considerations

  1. Token Management

    • Never commit tokens to version control
    • Use environment variables or secure credential storage
    • Rotate tokens regularly
    • Use least-privilege access
  2. Filesystem Access

    • Only grant access to necessary directories
    • Avoid granting access to entire home directory
    • Review MCP server logs periodically
  3. Code Review

    • Always review Claude-generated code before applying
    • Test in non-production environments first
    • Use terraform plan to preview changes
    • Follow your organization's approval processes

Performance Tips

  1. MCP Server Optimization

    • Limit filesystem paths to relevant projects
    • Use specific GitHub repo queries instead of org-wide searches
    • Cache frequently accessed data when building custom MCPs
  2. Agent Usage

    • Be specific in your requests
    • Provide context (cloud provider, environment, constraints)
    • Reference existing code when asking for modifications

Workflow Integration

  1. CI/CD Integration

    # Use Claude to generate pipeline configs
    "Generate a GitHub Actions workflow for Terraform with plan on PR and apply on merge"
    
  2. Documentation

    # Auto-generate module documentation
    "Create comprehensive README.md for this Terraform module with usage examples"
    
  3. Code Reviews

    # Before committing
    "Review these Terraform changes for security issues and best practices"
    

Part 7: Troubleshooting

Common Issues

Issue: MCP servers not appearing

# Check Node.js version
node --version  # Should be 18+

# Check config file syntax
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | jq .

# Check logs (location varies by platform)
tail -f ~/.claude/logs/mcp.log

Issue: GitHub MCP authentication fails

# Test token manually
curl -H "Authorization: token YOUR_TOKEN" https://api.github.com/user

# Verify token scopes include 'repo'
# Regenerate if necessary

Issue: Agent not behaving as expected

  • Review agent description for clarity
  • Be more specific in your requests
  • Provide examples of desired output
  • Check if you're using the correct agent

Issue: Filesystem access denied

  • Verify paths exist and are readable
  • Check file permissions
  • Ensure paths are absolute, not relative
  • Review OS security settings (macOS Gatekeeper, etc.)

Getting Help

  1. Check official documentation: https://docs.claude.com
  2. Community forums: Look for Claude Code user communities
  3. GitHub Issues: Check MCP server repositories
  4. Internal support: Contact your team lead or DevOps

Conclusion

By combining Claude Code's Terraform agent with MCP servers, you've created a powerful AI-assisted infrastructure development environment. This setup will:

  • Reduce development time by 40-60% for common Terraform tasks
  • Improve code quality through automated best practice recommendations
  • Decrease debugging time with context-aware error analysis
  • Accelerate onboarding for new team members
  • Ensure consistency across infrastructure codebases

Next Steps

  1. Start small: Begin with the agent only, add MCP servers gradually
  2. Share learnings: Document patterns that work well for your team
  3. Iterate: Refine agent descriptions based on actual usage
  4. Expand: Consider additional MCP servers for other tools (Slack, Jira, monitoring)
  5. Contribute: Share custom MCP servers with the community

Additional Resources


Questions or feedback? Share your experiences and help improve this guide for the community.

Happy Infrastructure Coding! 🚀

No comments: