Terraform MCP ServerをAmazon ECSにデプロイしてみた

Terraform MCP ServerをAmazon ECSにデプロイしてみた

2025.08.13

2025年7月にいくつかのMCPサーバーがAWS Marketplaceでサブスクライブできるようになりました。

以前Terraform MCP Serverをサブスクライブして、Amazon Bedrock AgentCoreでデプロイしてみました。

Terraform MCP ServerをAWS Marketplaceでサブスクライブして、Amazon Bedrock AgentCoreを使ってセットアップしてみた | DevelopersIO

デプロイガイドではデプロイ先として、Amazon Bedrock AgentCoreのほかにECSとECS Anywhereを選択可能でした。

Oo2inve3FwRJ.png

今回は、ECS上にデプロイしてみます。

構成

Terraform MCP ECS.png

今回は検証のための最小限の構成で構築しました。

クライアントからECSタスクのパブリックIPに直接アクセスするようにします。

本番運用であれば、前段にALBを用意してECSタスクはプライベートサブネットに配置し、直接パブリックIPは付与しないことをおすすめします。

AWS MarketplaceでTerraform MCP Serverをサブスクライブ

手順は上記のブログをご確認ください。

セットアップのガイドで、コンテナのイメージURIが表示されます。

次の手順で使うため、控えておきます。

AWS_Marketplace__Terraform_MCP_Server.png

Remote MCPサーバホスト用のリソース作成(ECS等)

AWSリソースはTerraformで作成します。

以下のファイルを用意します。terraform.tfvarsのdocker imageは前の手順で出力されたものを使います。

terraform.tfvars
docker_image = "1234567890.dkr.ecr.us-east-1.amazonaws.com/hashicorp/terraform-mcp-server:0.2.0" # mcp serverコンテナイメージURI
variables.tf
variable "aws_region" {
  description = "AWS region where resources will be created"
  type        = string
  default     = "ap-northeast-1"
}

variable "docker_image" {
  description = "Docker image for the container"
  type        = string
}
provider.tf
terraform {
  required_version = ">= 1.0.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}
main.tf
# Local values
locals {
  prefix = "tf-mcp-remote-ecs"
}

# Data sources
data "aws_availability_zones" "available" {
  state = "available"
}
data "aws_caller_identity" "current" {}

# VPC Module
module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = local.prefix
  cidr = "10.0.0.0/16"

  azs            = data.aws_availability_zones.available.names
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  enable_nat_gateway   = false
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Terraform = "true"
    Name      = "${local.prefix}"
  }
}

# Security Group
resource "aws_security_group" "ecs_tasks" {
  name_prefix = "${local.prefix}-ecs-tasks"
  vpc_id      = module.vpc.vpc_id

  ingress {
    description = "MCP Server Port"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "All outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${local.prefix}-ecs-tasks"
  }
}

# IAM Roles
resource "aws_iam_role" "ecs_execution_role" {
  name = "${local.prefix}-ecs-execution"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      }
    ]
  })

  tags = {
    Name = "${local.prefix}-ecs-execution"
  }
}

resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" {
  role       = aws_iam_role.ecs_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role" "ecs_task_role" {
  name = "${local.prefix}-ecs-task"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ecs-tasks.amazonaws.com"
        }
      }
    ]
  })

  tags = {
    Name = "${local.prefix}-ecs-task"
  }
}

# ECS Resources
resource "aws_ecs_cluster" "main" {
  name = local.prefix

  setting {
    name  = "containerInsights"
    value = "enabled"
  }

  tags = {
    Name = "${local.prefix}"
  }
}

resource "aws_ecs_task_definition" "main" {
  family                   = local.prefix
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = 512
  memory                   = 1024
  execution_role_arn       = aws_iam_role.ecs_execution_role.arn
  task_role_arn            = aws_iam_role.ecs_task_role.arn

  container_definitions = jsonencode([
    {
      name      = "terraform-mcp-server"
      image     = var.docker_image
      cpu       = 512
      memory    = 1024
      essential = true
      portMappings = [
        {
          containerPort = 80
          protocol      = "tcp"
        }
      ]
      environment = [
        {
          name  = "MCP_SESSION_MODE"
          value = "stateless"
        },
        {
          name  = "MCP_CORS_MODE"
          value = "strict"
        },
        {
          name  = "MCP_ALLOWED_ORIGINS"
          value = "http://127.0.0.1"
        },
        {
          name  = "TRANSPORT_MODE"
          value = "streamable-http"
        },
        {
          name  = "TRANSPORT_HOST"
          value = "0.0.0.0"
        },
        {
          name  = "TRANSPORT_PORT"
          value = "80"
        }
      ]
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = "/ecs/${local.prefix}"
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "ecs"
        }
      }
    }
  ])

  tags = {
    Name = "${local.prefix}"
  }
}

resource "aws_ecs_service" "main" {
  name            = local.prefix
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.main.arn
  desired_count   = 1
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = module.vpc.public_subnets
    security_groups  = [aws_security_group.ecs_tasks.id]
    assign_public_ip = true
  }

  depends_on = [aws_iam_role_policy_attachment.ecs_execution_role_policy]

  tags = {
    Name = "${local.prefix}"
  }
}

resource "aws_cloudwatch_log_group" "ecs" {
  name              = "/ecs/${local.prefix}"
  retention_in_days = 7

  tags = {
    Name = "${local.prefix}"
  }
}

ECSに設定している環境変数等は以下を参考にしました。

Deploy the Terraform model context protocol (MCP) server | Terraform | HashiCorp Developer

GitHub - hashicorp/terraform-mcp-server: The Terraform MCP Server provides seamless integration with Terraform ecosystem, enabling advanced automation and interaction capabilities for Infrastructure as Code (IaC) development.

Terraformを実行してリソースを作成します。

terraform init
terraform plan
terraform apply

ECSでタスクが起動したらOKです。

クラスター___Elastic_Container_Service___ap-northeast-1.png

動作確認: Claude CodeからTerraform MCP Serverを呼び出す

今回はECSタスクのパブリックIPに直接アクセスする形で呼び出します。

ECSタスクのパブリックIPを確認します。

マネジメントコンソールでECS -> クラスター -> [作成したクラスター] -> [作成したECSサービス] -> タスクの順に選択します。

タスク設定___Elastic_Container_Service___ap-northeast-1.png

Healthエンドポイントにcurlしてみます。

curl http://<ECSタスクのパブリックIP>/health

以下のようにStatus OKが返ってくることを確認します。

出力例
{"status":"ok","service":"terraform-mcp-server","transport":"streamable-http"}%

次にClaude Codeから呼び出してみます。

.claude.jsonに以下を追加します。

~/.claude.json
      "mcpServers": {
        "terraform-mcp-server-ecs": {
          "type": "http",
          "url": "http://<ECSタスクのパブリックIP>/mcp"
        }

Claude Codeを起動して、/mcpと入力します。

claude
/mcp

connectedになっていることが確認できました。

Claude_Code_—_terraform-sample.png

公式ドキュメント内のサンプルプロンプトを入力して、正常に呼び出せるか確認します。

Prompt an AI model connected to the Terraform MCP server | Terraform | HashiCorp Developer

I need help understanding what resources are available in the Google provider that are for AI

以下のようにMCP Serverを使って、レスポンスを返してくることを確認できました。

Claude_Code_—_terraform-sample.png

おわりに

デプロイガイドにあったので、ECSでのデプロイを試してみました。

ローカルから接続することを目的に最小限の構成で作成しました。

本番環境で運用するには、色々追加が必要です。

  • ALBの導入
  • ECSのPrivate Subnetへの移動
  • 認証・認可の仕組みづくり
  • ECSタスクのAuto Scaling設定
  • etc...

Amazon Bedrock AgentCore Runtimeに比べると手間はかかりますが、より柔軟な構成を組むことが可能です。

Remote MCPサーバーのホスト先としては、Amazon Bedrock Agent Core(2025/8時点ではプレビュー)を検討して、要件が満たせなかったらECSを選択する形がよさそうですね。

この記事をシェアする

facebookのロゴhatenaのロゴtwitterのロゴ

© Classmethod, Inc. All rights reserved.