リモートMCPサーバーをECSでたててみよう 〜IaCを添えて〜

# はじめに

こんにちは！ **yu_Matsu**です

最近 **MCP（Model Context Protocol）** が話題になっていますね！ GitHubやSlack、Google Driveなど、さまざまなサービスがMCPサーバーを提供しており、生成AIアプリケーションの開発により柔軟性が増してきました。

今回の記事では、このMCPを振り返るとともに、自分で実装する場合はどんな感じなのかについて見ていきます。また、実装したMCPサーバーを広く利用してもらうための方法の一つとして、AWS ECS（Elastic Container Service）上でサーバーをたててみたいと思います。

本記事の内容に関するサンプルコードを[GitHub](https://github.com/yukimatsuyoshi/mcp_on_ecs_sample)で公開していますので参考にしていただければと思います。また、記事タイトルにある通り、おまけとしてTerraformでのコード化のサンプルと解説もありますので、合わせてご覧ください。

https://github.com/yukimatsuyoshi/mcp_on_ecs_sample


# MCP（Model Context Protocol）とは
**MCP**は、アプリケーションがLLM（大規模言語モデル）にコンテキスト情報を提供する方法を標準化するプロトコルであり、Claudeシリーズを開発したAnthropic社が提唱しました。（ここでいう「コンテキスト」とは、LLMが会話や情報を適切に理解し処理するために必要な背景情報のことです。）

https://modelcontextprotocol.io/introduction

現在の生成AIアプリケーションのメインストリームである**AIエージェント**は、ユーザーの入力に対して最適解を得るために、**思考と行動**を繰り返します。この行動のフェーズで必要な情報をコンテキストとして取得するのですが、この処理は「**ツール**」として実装されることが多いです。
従来の生成AIアプリ開発では、このツールを、同じような処理を行うものであっても（例えばよく利用されるGmailやSlackなどを扱うツール）、その都度実装/連携する必要があったり、開発に利用されるフレームワークやプログラミング言語によって実装方法が異なるため、他者に提供しづらい、といった問題がありました。

しかし、MCPによりこのような問題が解決され、さまざまな生成AIアプリケーションが、コンテキスト情報取得のためのツールを共用できるようになりました。また、単なるプロトコルの提唱だけでなく、簡単にアプリケーションに組み込めるように**SDK（ソフトウェア開発キット）** まで用意されているため、現在急速に普及しています。

https://github.com/modelcontextprotocol

MCPの基本構造としては、下図のような **クライアント・サーバー・アークテクチャ** に基づいていまして、以下の概念が存在します。

- **MCPホスト**：Claude DesktopやGitHubCopliot、Clineなど、後述するMCPクライアントを内包した生成AIアプリケーション
- **MCPクライアント**：MCPに則ってサーバーと接続、LLMの指示をサーバーに渡し、その処置結果を受け取る
- **MCPサーバー**：MCPに則って、特定の機能を提供するプログラム

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/cffb56fe-1e88-41b9-a8fa-3c37c8b914bc.png" width=600>
(https://modelcontextprotocol.io/introduction から引用)

MCPに関しては、以下の資料が非常に分かりやすくまとまっていますので、一度ご覧いただければと思います。

https://speakerdeck.com/minorun365/yasasiimcpru-men



# 1. ローカルでMCPサーバーをたててみる
### 簡単なMCPサーバーの実装
MCP公式ドキュメントの[**Quickstart**](https://modelcontextprotocol.io/quickstart/server)を実施して、簡単なMCPサーバーを構築してみましょう。

:::note warn
Quickstartをほぼ愚直に実施するだけですので、もう試されたことがある方は流し読みしていただいて大丈夫です！ 

また、GitHubのサンプルコードを実行しながら読み進められる場合は、mcp_serverディレクトリに移動して以下を実行してください
- 下記の手順を参考に**uv**のインストールと仮想環境のアクティベート
- ```uv sync```を実行
:::

Pythonのパッケージ管理ツールである**uv**を利用して、MCPサーバー用のプロジェクトを作成します。
```bash
# プロジェクトの作成
uv init mcp_server
cd mcp_server

# 仮想環境のアクティベート
uv venv
source .venv/bin/activate

# パッケージのインストール
uv add fastmcp httpx  # 「FastMCP」の v2 をインストールしている

# MCPサーバーを実装するファイルの作成
touch weather.py
```
Quickstartと異なる点は、MCPサーバー作成に利用する「**FastMCP**」に関して、**v2** をインストールしているところです。**FastMCP**はMCPサーバーやMCPクライアントを実装しやすくなるSDKであり、v2はMCP公式のPython用SDKであるv1を拡張したものになります。詳しくは以下のドキュメントをご覧ください。

https://gofastmcp.com/getting-started/welcome

それでは、MCPサーバーを実際に実装していきます。（といっても、Quickstart通りですが...）

```Python:weather.py
from typing import Any
import httpx
from fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("weather")

# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
```

まずは必要なライブラリをインポートし、**FastMCPクラス**を初期化しています。このクラスにより、MCPサーバーのツール実装が容易になります。また、天気情報を取得する外部APIのURLの定数の定義もここでしています。

次に、MCPサーバーで提供するツール群の実装になります。機能としては以下の２つになります
- get_alerts: 州名から、その州で発生している気象警報の情報を調べて返す
- get_forecast: 緯度と経度から、そのエリアの気象予報を調べて返す

```Python:weather.py
async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Make a request to the NWS API with proper error handling."""
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/geo+json"
    }
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

def format_alert(feature: dict) -> str:
    """Format an alert feature into a readable string."""
    props = feature["properties"]
    return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
```
まずは、ヘルパー関数の実装になります。
**make_nws_request** は、APIのURLを受け取ってAPIを実行、そのレスポンスから必要な情報のみ取り出して返します。get_forecast、get_alerts共通で利用されます
**format_alert**は、気象警報情報を整形して返します。get_alertsでのみ利用されます

```Python:weather.py
@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get weather alerts for a US state.

    Args:
        state: Two-letter US state code (e.g. CA, NY)
    """
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."

    if not data["features"]:
        return "No active alerts for this state."

    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    # First get the forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    # Get the forecast URL from the points response
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    # Format the periods into a readable forecast
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # Only show next 5 periods
        forecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)
```
次に、ツール群本体の実装です。
といっても、開発者が意識するのは関数の内容のみであり、普通に関数を実装すれば良いです。
- get_alerts: 外部APIの気象警報情報を取得するためのURLを作成し、先ほどのmake_nws_requestに渡して実行することで、結果を取得します。そして、format_alertで整形し、返しています
- get_forecast: 外部APIの気象予報を取得するためのURLを作成し、make_nws_requestに渡して実行します。レスポンスの中から直近5日分の情報を抽出して返しています

それぞれの関数に、コードの最初の方で生成したFastMCPインスタンスのデコレーターである、**@mcp.tool()** をつけることで、関数をツール化することができます。Pythonに慣れていれば、かなり直感的であることが分かるかと思います。

最後に、サーバーを起動する処理を記述することで、実装完了です！！
```Python:weather.py
if __name__ == "__main__":
    # Initialize and run the server
    mcp.run(transport='stdio')
```
mcp.runの引数として、**transport='stdio'** を指定していますが、こちらの詳細に関しては後述します。

### Claude Desktopでの動作確認
それでは、動作確認をしてみましょう。
まずは、Quickstartに合わせてClaude Desktopで確認してみます。VSCodeがお手元にある場合は、以下のコマンドを実行して設定ファイルを開きます。

```bash:Macの場合になります
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```

:::note info
**Tips:** Claude Desktopのアプリケーションからも設定ファイルを開くことができます。
1. Claude Desktopの「設定」を開きます
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/17d16f27-0f57-446b-86ba-cc2374c5f0ac.png" width=300>
2. 「開発者」タブを開き、「構成を編集」を押下するとファイルブラウザが開きます
   <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/2c707d0f-5e19-4f75-b5c1-f2ceb59d4d27.png" width=400>
:::

開いたclaude_desktop_config.jsonに、以下の内容を記述します。もしすでに他のMCPサーバーの設定がある場合は、**weather**以下の内容を追加してください。これで設定完了です。
```claude_desktop_config.json
{
    "mcpServers": {
        "weather": {
            "command": "uv（Mac/Linuxの場合はuvの絶対パス）",
            "args": [
                "--directory",
                "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp_server",
                "run",
                "weather.py"
            ]
        }
    }
}
```

:::note info
もしご自身で環境を作らず、GitHubのサンプルコードを実行する場合は、mcp_server/weather.py の L103 ```mcp.run(transport="stdio")``` をコメントインし、L104 ```mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, log_level="debug")```をコメントアウトしてください

![スクリーンショット 2025-05-11 12.15.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/59a48cc7-8400-4e75-89a7-2943f7cbaa93.png)
:::

では、Claude Desktopを開いてみましょう。
ユーザー入力欄の「+」ボタン隣のツールボタンを押下してみると、MCPサーバーが追加されていることが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/6aebb6fe-52d1-4464-b0f2-dea241ec89b8.png" width=300>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/92e42fac-915a-4f22-b69b-475b56113082.png" width=300>

ちゃんと get_alerts、get_forecast ともにツールが有効になっています！

Quickstartの入力例である、「**What’s the weather in Sacramento?**」を入力してみます。

:::note info
**Tips:** 今回作成したMCPサーバーをClaude Desktopで動作確認をする際は、ツールの「**ウェブ検索**」をオフにすることをお勧めします。
:::

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/635387a1-d376-4765-b4ed-1ad13949c6b4.png" width=500>

ツールの外部連携使用許可に関するメッセージが表示されますが、とりあえず「常に許可する」にします。

![スクリーンショット 2025-05-11 11.53.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/b50bee85-169e-4c41-9296-f8394fe448b5.png)

エージェントが思考を始めました。MCPサーバーで提供している get_forecast をちゃんと利用してくれていることが分かります。

![スクリーンショット 2025-05-11 11.53.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/6628e734-9ce7-4217-8681-5271b7ed229e.png)

最終的な回答が返ってきました。サクラメントの気象予報を答えてくれていることが分かります！

ついでに、気象警報についてもQuickstartの入力例「**What are the active weather alerts in Texas?**」を入力してみます。

![スクリーンショット 2025-05-11 11.54.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/f3ccfb63-e9d8-4ce2-a1f0-a47644ec5441.png)

こちらも問題なく get_alerts を使ってくれています。

![スクリーンショット 2025-05-11 11.55.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/0bb1455f-af54-4466-a7ae-6166d67b6df4.png)

取得した情報を、format_alert で整形して返してくれていることが分かります。


### GitHub Copilotでの動作確認
次はGitHub Copilotでも動作確認してみます。
VSCodeを開き、下記の記事を参考に、setting.jsonを開いてください。

https://qiita.com/y-w/items/614843b259c04bb91495

setting.jsonに以下の内容を追記してください。

```json:setting.json
{
    "mcp": {
        "servers": {
            "weather": {
                "command": "uv（Mac/Linuxの場合はuvの絶対パス）",
                "args": [
                    "--directory",
                    "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp_server（絶対パス）",
                    "run",
                    "weather.py"
                ]
            }
        }
    }
}
```
すると、「weather」の上の方に「起動」と出てきますので、起動します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/4e976c49-5a1e-450a-9817-6c72e298df62.png" width=300>
「実行中」になれば、起動成功です
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/becea2a5-d876-4849-b45f-4fedf6ed6260.png" width=300>

:::note info
**Tips:** ここまでの流れを見てもらえれば分かると思いますが、Claude Desktop、Copliot等のMPCホストからMCPサーバーに接続する際は、ローカルであっても手元で先に```uv run weather.py```などでサーバーを起動しておく必要がありません。

しかし、後述するMCP InspectorやMCPクライアントから接続する場合は先にサーバーを起動しておく必要があることに注意しましょう。
:::

Copilotのチャット画面を開くと、ユーザー入力部分にツールのマークが出ていますので、押下して確認してみましょう。MCPサーバーが追加されていることが分かります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/42451276-9272-42b3-a3bd-3b677c2fb21a.png" width=300>
![スクリーンショット 2025-05-14 15.57.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/149f0865-012b-4fd1-af34-432b7156204c.png)

それでは、Claude Desktop と同様に、「**What’s the weather in Sacramento?**」と入力してみます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/e6ead9ff-fd27-4dc2-96aa-1b573d942502.png" width=450>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/7a702ea5-5700-44f6-9c57-0a9bb87fb4d6.png" width=450>

Claude Desktopと同様に、MCPサーバーの get_forecast を利用して気象予報を取得して返していることが分かります。


### MCP Inspector での動作確認
先ほどまで、Claude Desktop や GitHub Copilot などのMCPホストを利用して動作確認を行っていましたが、いきなりMCPホストに繋ぎ込みに行く前に、**MCP Inspector**というツールを利用することで、簡単にローカルで動作確認できます。

:::note info
**Tips:** 実際にMCPサーバーを開発する際は、まずはMCP Inspectorで動作確認してからMCPホスト、MCPクライアントに繋ぎ込みに行く方が良いでしょう。
:::

まず、MCPサーバーの仮想環境に入っているコマンドプロンプト/ターミナルで以下のコマンドを実行し、MCPサーバーを起動します。

```
uv run weather.py
```

次に、別のコマンドプロンプト/ターミナルを開き、以下のコマンドを実行してInspectorを起動します。

```
npx -y @modelcontextprotocol/inspector
```

すると、InspectorのURLが実行ログに表示されますので、ブラウザでアクセスします。

![スクリーンショット 2025-05-14 16.13.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/a339252d-ae19-4276-b061-ebf093479783.png)

![スクリーンショット 2025-05-14 16.14.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/db3431ae-82ac-4bdd-8e4a-b342d275fe99.png)

左メニューでMCPサーバーの接続設定を行い、「Connect」を押下してサーバーに接続します。
- Transport Type: **STDIO**
- Command: **uv** (mac/linuxの場合でもフルパスでなくてOK)
- Arguments: **--directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp_server run weather.py** (それぞれスペースで間を空ける) 

接続に成功したら、以下のような画面になります。

![スクリーンショット 2025-05-14 16.18.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/65ddb5d7-9370-472b-ab6c-166b749a9c38.png)

「Tools」タブを開き、「List Tools」を実行して、get_alerts とget_forecast が表示されれば一先ず接続確認は完了ということになります。

![スクリーンショット 2025-05-14 16.20.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/1e555118-d5af-46f0-b4da-c0b9089db389.png)


# 2. Dockerでサーバーをたててみる
ローカルでサーバーを立てての動作確認は完了しましたが、ECSに載せるにはDockerイメージを作成する必要があります。ということで、次はDockerイメージの作成し、そこからサーバーをたててみましょう。

まずは、mcp_serverディレクトリの下に以下の**Dockerfile**を作成します。

```Dockerfile
# Pythonのバージョンはお手元のものに合わせる
FROM python:3.11 

# Set working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements first for better caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code
COPY . .

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Command to run the application
CMD ["python", "weather.py"]
```
イメージ作成時にインストールが必要なパッケージ一覧を期待した requirements.txt は以下のような内容で、同じくmcp_serverディレクトリ下に作成します。

```requirements.txt
httpx>=0.28.1
fastmcp>=2.3.1
```

次に、Dockerイメージをビルドします。先にDocker Desktopを起動しておいてください。
以下のコマンドでをmcp_severディレクトリで実行してください。
```
docker build -t mcp-server .
```
問題なくビルドが完了したら、準備完了です！

### MCP Inspectorでの動作確認
それでは、先に MCP Inspectorで動作確認してみましょう。ローカルでの起動確認時と同様に、MCP Inspectorを起動します。また、以下のコマンドで、Dockerイメージからサーバーを起動しておきます。
```
docker run -i --rm mcp-server
```

そして、MCPサーバーの接続設定として以下を入力して、サーバーに接続します。
- Transport Type: **STDIO**
- Command: **docker**
- Arguments: **run -i --rm mcp-server**

接続に成功し、Toolsからツール一覧を取得できれば確認完了です。

![スクリーンショット 2025-05-14 17.08.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/794aabd9-dc53-412f-b7fb-7be4e1db5e07.png)

確認が終わりましたら、起動しているサーバーを **docker stop** で停止しておきましょう。


### Claude Desktop での動作確認
Claude Desktopでも確認しておきます。
ローカルの場合と同様に、設定ファイルを開いて以下のように編集します。

```claude_desktop_config.json
"weather": {
    "command": "docker",
    "args": [
        "run",
        "-i",
        "--rm",
        "mcp-server"
    ]
}
```

設定の変更が完了したので、Claude Desktopを再起動してみます。ツールが利用できる状態であるので、MCPサーバーに接続できていることが分かります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/7a005bf8-b8cb-4a1a-8744-ee6b224a02c3.png" width=300>

ではいつもの質問をしてみましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/99d6021c-092d-4eb3-86ba-d11ce3f023a9.png" width=450>

ツールをちゃんと使ってくれていること、意図した回答が返ってきていることが分かります。
これで、DockerからのMPCサーバー起動の確認は完了です！


※ Copilotでの動作確認は省略します。設定ファイルに記載する内容はClaude Desktopと同様になりますので、ご自身で試してみてください。


# 3. ECSでリモートMCPサーバーを立ててみる
いよいよECSでのリモートMCPサーバーの構築に移ります！

まずは、現在のMCPサーバーのコードをリモート対応するために少し修正します。

```diff_python:weather.py
if __name__ == "__main__":
    # Initialize and run the server
-   mcp.run(transport="stdio")
+   mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, log_level="debug")
```

MCPサーバー実行処理を記述している部分の **transport** の指定を **streamable-http** に変更します。また、コンテナ外からのアクセスを受け付けるためにhostを0.0.0.0にバインド、ポートを8000に指定しています。

:::note info
GitHubのサンプルコードを実行しながら読み進めていただいている場合は、mcp_server/weather.py の L103 ```mcp.run(transport="stdio")``` を**コメントアウト**し、L104 ```mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, log_level="debug")```を**コメントイン**してください

:::

### MCPサーバーのトランスポートについて
ここで、トランスポートについて見ておきましょう。
トランスポートは、MCPサーバーとMCPホスト/クライアントで情報をやり取りするための方法で、**Stdio（標準入出力）**と**Streamable HTTP**の２種類あります。今まで、ローカルやDockerでMCPサーバーを起動する場合はトランスポートに**stdio**を、今回リモートMCPサーバーをたてる上で**streamable-http**を指定しました。

**Stdio**は「標準入出力」の名の通り、MCPサーバーをローカルホストで起動し、MCPホスト/クライアントも同じローカル環境から接続しにいくような場合に利用します。標準入力（stdin）と標準出力（stdout）を通じてデータがやり取りされます。

**Streamable HTTP**は、HTTPベースでMCPサーバーとホスト/クライアント間でデータのやり取りをする方式になります。リモートMCPサーバーのように、MCPホスト/クライアントがインターネット越しでMCPサーバーに接続する必要がる場合などに利用されます。
従来は HTTP + SSE(Server-Side Events) 方式が利用されていましたが、それを代替、補完する方式として普及しつつあります。現状のMCP界隈ではまだHTTP + SSE方式が主流ですが、今後Streamable HTTPに置き換わっていくとのことです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/07e3ecd2-a591-46cc-a2c6-0eb473d3b13d.png" width=550>

:::note info
**Tips:** Streamable HTTPについてはかなり奥が深いため、より詳しく知りたい方は以下の記事をご覧ください。

https://www.issoh.co.jp/tech/details/6580/#HTTPSSEStreamable_HTTP
:::

### AWSのリソースの作成
ECS自体がAWSのサービスの一つですが、その他にもいろいろ必要ですので、作成していきましょう。

まずは、ECSを配置するネットワーク周りです。記事スペースの都合上詳しい作成手順等は省略させていただきますが、以下のリソースを作成してください。（後述しますが、GitHubのTerraformのコードも参考にしていただければと思います。）

- VPC
- パブリックサブネット（2AZ分）
- インターネットゲートウェイ
- ルートテーブル
- S3へのVPCエンドポイント
- セキュリティグループ
    - インバウンド、アウトバウンドともにすべてのトラフィックを許可

:::note info
**Tips:** 上記に挙げたセキュリティグループ以外のリソースは、「VPCワークフローの作成」で一気に作成してくれるので、とても便利です。

![スクリーンショット 2025-05-09 15.31.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/8dd0ab28-a915-48c7-a1d7-ef07d364075e.png)

:::

:::note warn
本来はECSをプライベートサブネットに配置するべきですし、セキュリティグループもしっかり絞る必要がありますが、今回はお試しのため上記のようにしています。実際に広く提供するようなリモートMCPサーバーをたてる場合は、セキュリティ観点を踏まえた上でアーキテクチャを検討しましょう。
:::

次に、Dockerイメージを管理するECR（Elastic Container Registory）のリソース作成です。
ECRのコンソールに移動し、「リポジトリを作成」を押下します。

リポジトリ作成画面に遷移するので、リポジトリ名にDockerイメージと同じ名前を入力します（今回は「mcp-server」）。また、イメージタグのミュータビリティは「Mutable」にしておきます。

![スクリーンショット 2025-05-08 0.57.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/69f4868e-4e35-4df2-828c-ef77619e1c90.png)

イメージリポジトリの作成が完了したら、Dockerイメージをpushしていきます。「プッシュコマンドを表示」を押下すると、実行すべきコマンド一覧が表示されます。

![スクリーンショット 2025-05-08 0.58.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/d960d58d-2381-4605-8ccb-b93df561eeca.png)

今回は、Dockerイメージのビルドはもう出来ているので、実行するコマンドは 1、3 ~ 4になります。

![スクリーンショット 2025-05-08 0.58.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/82788341-a93b-4efa-af87-826083f78ec4.png)

一通り完了すると、リポジトリにイメージがpushされます。

![スクリーンショット 2025-05-08 1.11.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/89c0f8e7-6d4c-4d6e-b123-67544210751f.png)


次に、ECSでMCPサーバーをたてる工程です。
ECSのコンソール左メニューから「タスク定義」を開き、「新しいタスク定義の作成」を押下します。

タスク定義作成画面に遷移するので、タスク定義ファミリー名を指定します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/22c0ae9b-7c68-41e3-9123-314c82c3e561.png" width=400>

インフラストラクチャの要件を指定します。
起動タイプは「AWS Fargate」、オペレーティングシステム/アーキテクチャは、Dockerイメージをビルドした環境に合わせて選択します。（筆者の環境だと「Linux/ARM64」）

![スクリーンショット 2025-05-08 10.43.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/74f6fc40-c237-46f2-b561-5ec2fc2a8579.png)

:::note info
**Tips:** オペレーティングシステム/アーキテクチャに何を選べば良いかですが、お手元のコマンドプロンプト/ターミナルで、**docker inspect <イメージ名>** で調べることができます。このコマンドを実行すると、その結果の中に以下のようなOSとアーキテクチャの情報が含まれていますので、それらを参考に選択してください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/c98058d2-ecac-4bde-9fe5-3ccc8c79040d.png" width=300>
:::

タスクロールとタスク実行ロールですが、今回は同じIAMロールを選択します。このロールは、ポリシーとしてマネージドの**AmazonECSTaskExecutionRolePolicy**のみを持つロールとなっており、タスク定義と並行して作成しておいてください。
（もし、AWSの他のサービスにアクセスするようなMCPサーバーをたてる場合は、タスク実行ロールは適切な権限を持つものを作成してください。）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/1d9458cd-b72c-4b37-971f-e4bc87922690.png" width=450>

最後に、コンテナの設定です。
コンテナイメージとして、先ほどpushしたイメージのURIを入力します。
また、ポートマッピングは下画像のように設定してください。

![スクリーンショット 2025-05-08 10.45.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/0eae2a34-a195-497d-9774-6b9309c52e38.png)

あとの設定はそのままで、タスク定義の作成は完了です。

次に、ECSクラスター/サービスの作成です。
コンソール左メニューの「クラスター」を開いて、「クラスターの作成」を押下します。
クラスターの作成画面に遷移するので、クラスター名とインフラストラクチャを下画像のように指定し、作成します。

![スクリーンショット 2025-05-09 15.40.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/4d2c49cb-3199-4bb9-b498-cacf42667682.png)

クラスターの作成が完了したら、サービスの作成に移ります。クラスター画面の「サービス」タブの「作成」を押下します。

![スクリーンショット 2025-05-09 15.41.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/b5421371-fc26-497f-a381-cef4528462cb.png)

サービスの作成画面に遷移するので、下画像を参考に設定をしてください。ネットワーキングの項目は、作成したVPCやセキュリティグループを選択、パブリックIPは有効にします。

![スクリーンショット 2025-05-09 15.41.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/d60c1395-96ae-43ba-9bc4-71a184545be1.png)
![スクリーンショット 2025-05-09 15.41.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/2549bd4b-9fb5-4afc-9072-529ab1f158b7.png)
![スクリーンショット 2025-05-09 15.42.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/8c1489a9-8de0-41e4-84aa-a82b20b84880.png)

設定が完了したら、サービスを作成します。数分かかることがありますので、気長に待ちましょう。

サービスの作成が完了したら、タスクが一件起動開始しているかと思います。「タスク」タブから確認できます。（下画像は「タスク(2)」となっていますが、気にしないでください）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/461afeae-73cd-4328-856c-6252969786f0.png" width=450>

タスク名を押下すると、タスクの詳細画面が開きます。「前回のステータス」が「実行中」になっていれば正常起動完了です。「パブリックIP」はこの後MCPサーバーへの接続に利用しますので、控えておいてください。

![スクリーンショット 2025-05-15 11.21.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/b15c6d23-6c70-4b81-8a0d-80e666131526.png)

「ログ」タブを開くと、サーバーログを確認することができます。このログはCloudWatch Logsに出されているものになりますので、ログ監視等も可能です。

![スクリーンショット 2025-05-15 11.19.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/8e430f08-56a9-418c-bb6b-6e59775225ab.png)


### MCP Inspectorでの動作確認
さて、無事にリモートMCPサーバーが起動しましたので、例の如くまずはMCP Inspectorでの動作確認を実施します。今までと同様にローカルでMCP Inspectorを立ち上げてブラウザで開きます。
左メニューで以下の接続設定をして、「Connect」を押下します。
- Transport Type: **Streamable HTTP**
- URL: **http\://ECSタスクのパブリックIP:8000/mcp/**

問題なく接続できました！

![スクリーンショット 2025-05-15 12.27.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/c0976af4-84cf-4a0d-bd1f-01103026ac20.png)

「Tools」タブでツール一覧を取得してみます。

![スクリーンショット 2025-05-15 12.32.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/7a641cf0-771e-4d5e-a9c4-32d3b62827ff.png)

問題なく取得できていそうです！
ちなみにこの時点でECSタスクのログを確認すると、サーバーに ListToolsRequest が行われていることが分かります。

![スクリーンショット 2025-05-15 12.33.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/4ac248cf-305e-4b49-b17b-4557498a0eff.png)


### Claude Desktop / GitHub Copilotでの動作確認
次に、Claude Desktopでの動作確認をしてみましょう。

基本的には今までと同様に設定ファイルを変更すれば良いのですが、一点注意が必要で、Claude Desktopは**リモートMCPサーバーを直接サポートしていません。**（当記事執筆時点）ですので、ローカルプロキシを間に噛ませてあげる必要があります。
今回は**mcp-remote**というローカルプロキシを使いたいと思いますので、以下のコマンドでインストールしてください。
```
npm install -g mcp-remote
```
インストールが完了しましたら、設定ファイルを開いて以下のように編集します。

```claude_desktop_config.json
"weather": {
    "command": "mcp-remote（Mac/Linuxの場合はuvの絶対パス）",
    "args": [
        "http://ECSタスクのパブリックIP:8000/mcp/",
        "--allow-http"
    ]
}
```
では、Claude Desktopを再起動してみましょう。
以下のように、ツールアイコンを開いてツールが有効化されていれば、リモートMCPサーバーへの接続成功です！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/c00b369c-6956-46de-97c1-60086bf791c5.png" width=300> 

試しにお馴染みの質問をしてみましょう。
今回は日本語で「サクラメントの天気を教えて！」と質問してみました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/226fc0df-5e7f-449a-88a0-de3ea975b756.png" width=500>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/7b5a045e-eabd-41c1-8042-3aaf25c64e27.png" width=500>

ちゃんと動いていそうです！
タスクのログを確認しても、外部APIが呼び出されていることが確認できます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/fdfee774-7e05-49de-bb60-e14634008ecf.png" width=600>

**無事にECSでリモートMCPサーバーをたてることができました！🎉**

GitHub Copilotでも動作確認しておきます。CopilotはリモートMCPサーバーをサポートしているため、接続設定は以下のようにかなりシンプルになります。
```setting.json
"weather": {
    "type": "http",
    "url": "http://ECSタスクのパブリックIP:8000/mcp/"
}
```

Claude Desktopと同じ質問をしてみましたが、ツールを利用して回答してくれていそうです！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/89c1fbe1-69a4-4664-a3ca-3ae20c3c5348.png" width=500>


### LangChainで実装したMCPクライアントで動作確認

今までは、Claude DesktopやGitHub CopilotのようなMCPホストで動作確認をしていましたが、自前のアプリケーションに組み込んだエージェントから利用することを想定して、**LangChain**で実装したMCPクライアント（厳密にはMCPホストに近い気がしますが）でも動作確認してみたいと思います。LangChainは、LLMを活用したアプリケーション開発を効率化するためのフレームワークです。

https://www.langchain.com/

mcp_serverディレクトリと同階層に、```uv init```で新しくmcp_clientというディレクトリを作成し、mcp_serverディレクトリの時と同じように仮想環境を作成してください。
※ 今までとは別のコマンドプロンプト/ターミナルを開いて作業してください。

:::note info
GitHubのサンプルコードを実行しながら読み進めていただいている場合は、ここら辺の作業は必要ありませんが、別コマンドプロンプト/ターミナルでmcp_clientディレクトリを開き、仮想環境のアクティベートと```uv sync```を実行してください
:::

今回はLangChain関連のパッケージが必要になるので、以下のコマンドで追加してください
```
uv add langchain-mcp-adapters langchain-aws langgraph boto3 streamlit
```

mcp_clientディレクトリ下にmain.pyを作成し、以下のようなコードを記述します。

```Python:main.py
import os
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_aws import ChatBedrockConverse
from langgraph.prebuilt import create_react_agent
from dotenv import load_dotenv

load_dotenv()

async def call_agent(user_input):
    # Select the model
    model = ChatBedrockConverse(
        model="us.anthropic.claude-3-7-sonnet-20250219-v1:0"
    )

    async with MultiServerMCPClient(
        {
            "weather": {
                "url": os.environ['MCP_SERVER_ENDPOINT_HTTP'],
                "transport": "streamable_http",
            }
        }
    ) as client:
        # Create the agent with tools
        agent = create_react_agent(model, client.get_tools())
        
        # Simple invocation without callbacks
        response = await agent.ainvoke({"messages": user_input})

        return response
        
if __name__ == "__main__":
    user_input = input("Enter your input: ")
    response = asyncio.run(call_agent(user_input))

    for message in response["messages"]:
        print(message.content)
```

LangChain（厳密にはLangGraphですが）でエージェントを実装する一つの手段として、```create_react_agent```があります。モデルのインスタンスと、エージェントが利用できるツール群を渡してあげることでエージェントを作成してくれます。

今回は、エージェントに渡すツールをMCPサーバーから取得できるように、**MultiServerMCPClient**を利用しています。コンフィグとしてMCPホストで設定するものと似たような記述をすることで、MCPサーバーに接続することができます。（マルチなので複数サーバーを設定可能）

MCPサーバーのURLは環境変数に外出ししているため、以下のような内容の.envファイルを作成してください。
```.env
MCP_SERVER_ENDPOINT_HTTP=http://ECSタスクのパブリックIP:8000/mcp/
```

また、以下も併せて実行しておいてください。
- **バージニア北部**リージョンのAmazon Bedrockのコンソールで、**Anthropic**系のモデルを有効化
- お手元のAWS CLIのプロファイルに関してバージニア北部用のものを作成して切り替えるか、コマンドプロンプト/ターミナルで環境変数```AWS_REGION```の値を**us-east-1**に変更

それでは```uv run main.py```で実行してみます。
同じく日本語で「サクラメントの天気を教えて」と入力してみたところ、緯度経度に変換してget_forecastを実行してくれています。

![スクリーンショット 2025-05-15 15.32.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/65c9bbed-d7f7-435d-a109-7a7fe4cceb3a.png)

最終的な回答内容もバッチリでした！

![スクリーンショット 2025-05-15 15.34.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/3c0b611a-d7e2-476c-8f11-f6a0b7f6613b.png)

ちなみに、記事スペースの都合上コードの掲載はしませんが、Streamlitで簡単なWebアプリケーション（main_gui.py）も作成していますので、気になる方はサンプルコードをご覧ください。

以下のコマンドで起動できます。
```
streamlit run main_gui.py
```
起動後、以下のような画面がブラウザで開くので、サイドバーでリモートMCPサーバーのURLを入力、使用したいモデルを選択してください。

![スクリーンショット 2025-05-15 15.35.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/1dfae803-8f1a-418e-aeff-db666cc2f43b.png)

あとは、質問を入力し、実行すればエージェントから回答が返ってくるようになっています。（雑なプログラムなので動作不良等あるかもしれませんが、ご了承ください）

![スクリーンショット 2025-05-15 15.41.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/9610ff31-effd-4141-9005-d584824bc114.png)

このように、比較的簡単に、かつ直感的にMCPクライアントを作成し、システムに組み込むことが出来るということもお分かりいただけるかと思います！

今回はLangChainを利用しましたが、AWS公式からも **Inline Agents SDK** が提供されており、MCPサーバーに対応しています。Inline Agents SDKを利用した記事もいろいろ出ていますので、調べてみてください。

https://github.com/awslabs/amazon-bedrock-agent-samples/tree/main/src/InlineAgent

:::note info
**Tips:** 本セクション冒頭にも述べた通り、セキュリティ観点を考慮していないアーキテクチャとなっているため、一通り動作確認が完了して必要がなくなった場合は、以下の手順でECSサービスを止めておくことを推奨します。
1. サービス一覧から「更新」を押下
   ![スクリーンショット 2025-05-15 19.21.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/0bd8b2d6-a897-4023-a6fc-9c4cbe26bdf7.png)
2. 「必要なタスク」を0にして更新する
   ![スクリーンショット 2025-05-15 19.21.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/71c6fc66-b823-4d47-bf26-24661f94bdba.png)

※もし再起動したい場合は、逆に「必要なタスク」を1にして、「新しいデプロイの強制」にチェックを入れてサービスを更新してください
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/efb0a701-5d3e-4b7e-81ac-c96ff4f17f88.png" width=300>
:::

# Next Action

とりあえず、ECSでリモートMCPサーバーをたてることは出来ましたが、今後のアクションとしてはどのようなことが考えられるでしょうか。

#### セキュリティ面の強化
前セクションで再三お伝えしている通り、今回は作成したものはあくまでハンズオンレベルになりますので、セキュリティ面が考慮されていません。もし実運用を考えるのであれば、最低限以下のような対応が必要になるかと思います。
- ECSをプライベートサブネット内に配置する
- MCPサーバーにhttpsで安全に通信できるように、ECSの前段に**ALB（Application Load Balancer）** を設ける
    - セキュリティグループを適切に設定することが併せて必要
- MCPサーバーにOAuthなどの認証機構を導入し、ツールを保護する（[こちら](https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#authentication)を参照）

#### CloudflareにMCPサーバーをデプロイする
自分でリモートMCPサーバーを環境から構築するとなると上記のようなこと配慮する必要がありますが、実は**Cloudflare**にMCPサーバーをデプロイすることで簡単に実現できます。また、環境の維持コストやスケーラビリティなども担保されるので、Cloudflareを利用可能であれば非常に有益な手段であると言えます。

https://developers.cloudflare.com/agents/guides/remote-mcp-server/



# 最後に
今回はMCPとは何だったのかを軽く振り返りつつ、簡易なMCPサーバーのたて方や、それをリモートサーバー化する方法例についての記事でした。

MCPは現在の生成AIシーンでは非常に重要な概念となっており、生成AIアプリケーションの進化をさらに一段階進めたのではないかと考えています。しかし、一見難しそうに見えて考え方はシンプルであり、SDKを利用することで非常に簡単にMCPサーバーをたてられることがお分かりいただけたかと思います。

提供されているMCPサーバーを利用するだけでなく、実際に自分で構築してみることで見えてくることも多々あるかと思いますので、しっかりMCPを使いこなしていきましょう！

本記事はこれで以上になります。ご高覧いただきありがとうございました！


# Appendix： TerraformでIaCしてみる
もし本格的にリモートMCPサーバーを運用していくとなった際に、今回手動で作成したインフラリソース周りをコード化して管理しておく必要が出てくることもあるかと思います。

ですので、このセクションでは**Terraform**でのコード管理（**Infrastructure as Code = IaC**）について簡単に触れておきます。

Terraformは、HashiCorp社で開発されているIaCツールであり、AWSやGCPなどのクラウドサービス上のインフラリソースをコードで定義し、コードに基づいたインフラ環境を自動構築することができる優れものです。

当記事のサンプルコードのterraformディレクトリ下で、今回手動で作成したリソース群のコードを作成していますので、興味がある方はご覧ください。本記事ではデプロイの仕方だけ掲載します。

まずは、Terraformのコマンドを実行するためにインストールしてください。バージョン管理を簡単にするために、**tfenv**でのインストールをお勧めします。

https://github.com/tfutils/tfenv

次に、terraformディレクトリに移動、以下のコマンドを実行してterraformの初期化を行います。
```
terraform init
```
:::note warn
AWS CLIのプロファイルや環境変数AWS_REGIONをバージニア北部リージョンに切り替えている方は、東京リージョン（ap-northeast-1）に戻しておいてください。
:::

初期化が完了すると、以下のようなメッセージが出ます。

![スクリーンショット 2025-05-15 19.54.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/81d44440-1191-4ad1-a972-79399c175f83.png)

次に、以下のコマンドを実行し、コードからどのようなリソースが作成され得るか、を確認します。（dry-runのようなものになります）

```
teraform plan
```

成功すると、以下のように作成される予定のリソース一覧が表示されます。今回は新規作成のみなので緑色の「＋」ばかりですが、もしTerraformで管理している既存のリソースから変更がある場合は「〜」で表現されたり、削除されるリソースや設定があれば赤色の「-」で表現されます。
![スクリーンショット 2025-05-15 19.54.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/379ec861-008e-418d-9393-37f5125433c1.png)
![スクリーンショット 2025-05-15 19.55.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/eabe7c50-12f7-4f82-9cf7-e522c1ab3ee1.png)

:::note info
Terraformでどのリソースを管理しているかは、```terraform plan```の実行後に自動的に作成される、**terraform.tfstate**をいうファイルに記載されます。次回以降のplanで変更箇所がある場合は、tfstateファイルと見比べて差分が検出される、という仕組みです。
:::

作成予定の内容に問題がなければ、以下のコマンドでapply（デプロイ）していきます。

```
terraform apply
```

実行すると、一度planが実行され、本当にapplyしても良いかの確認が入りますので、「yes」と入力します。すると、applyが開始されます。

![スクリーンショット 2025-05-15 19.55.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/97c6c1e6-1d50-4d40-b99a-2d4b87b0f53b.png)
![スクリーンショット 2025-05-15 19.55.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/728af703-155a-4708-8187-f2e3348b38fd.png)

以下のようなメッセージが出たら、apply完了です！

![スクリーンショット 2025-05-15 19.56.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/e468e17c-75b4-44e2-8f47-2f0603148d06.png)

これで、コードからリソースが作成されたわけですが、実はまだ終わりではありません。

まず、ECRのリポジトリは作成されたものの、イメージがpushされていませんので、リポジトリのpushコマンド一覧を参考に、イメージをpushしてください。（イメージをビルドする際、イメージ名はECRリポジトリ名を合わせた方が良いかもしれません）

あとは、ECSのサービス自体は作成されているのですが、タスクが起動していない状態です。これは、TerraformでECSサービスの定義をする際に、必要とするタスク数（desire_count）を0で設定したからです。（apply完了後にイメージがpushされていないので、自動でタスクを起動しても失敗する）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/f1b6d976-fae2-49d9-81ad-c03ac5d96123.png" width=400>

ですので、ECSサービスの設定を、「新しいデプロイの強制」にチェックを入れ、「必要なタスク」を 「1」 で更新してください。すると、タスクが起動します。

![スクリーンショット 2025-05-15 20.12.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/8833a067-2cbf-4de6-beea-6e7264bc8c35.png)

手動で作成した際と同様に、タスクの実行に成功したら完了です！
![スクリーンショット 2025-05-15 20.27.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3026734/00f37486-605f-49e2-b87d-cd3a5c44a249.png)

:::note info
Terraformでのデプロイから動作確認まで完了しましたら、もし必要がなければ terraform ディレクトリ下で```terraform destroy```を実行すると、コード管理しているリソースが全て削除されます。実運用では禁じ手のコマンドですが、ハンズオン後の片付け作業などでは便利です。
:::