次の方法で共有


チュートリアル: Python MCP サーバーを Azure Container Apps にデプロイする

このチュートリアルでは、FastAPI と MCP Python SDK を使用してタスク管理ツールを公開するモデル コンテキスト プロトコル ( MCP) サーバーを構築します。 サーバーを Azure Container Apps にデプロイし、VS Code の GitHub Copilot Chat からサーバーに接続します。

このチュートリアルでは、次の操作を行います。

  • MCP ツールを公開する FastAPI アプリを作成する
  • GitHub Copilot を使用して MCP サーバーをローカルでテストする
  • アプリをコンテナー化して Azure Container Apps にデプロイする
  • デプロイされた MCP サーバーに GitHub Copilot を接続する

[前提条件]

アプリの雛形を作成する

このセクションでは、FastAPI と MCP Python SDK を使用して新しい Python プロジェクトを作成します。

  1. プロジェクト ディレクトリを作成し、仮想環境を設定します。

    mkdir tasks-mcp-server && cd tasks-mcp-server
    python -m venv .venv
    source .venv/bin/activate
    
  2. requirements.txtを作成します。

    fastapi>=0.115.0
    uvicorn>=0.30.0
    mcp[cli]>=1.2.0
    
  3. 依存関係をインストールします。

    pip install -r requirements.txt
    
  4. インメモリ データ ストアの task_store.py を作成します。

    from dataclasses import dataclass, field
    from datetime import datetime, timezone
    
    
    @dataclass
    class TaskItem:
        id: int
        title: str
        description: str
        is_complete: bool = False
        created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    
        def to_dict(self) -> dict:
            return {
                "id": self.id,
                "title": self.title,
                "description": self.description,
                "is_complete": self.is_complete,
                "created_at": self.created_at.isoformat(),
            }
    
    
    class TaskStore:
        def __init__(self):
            self._tasks: list[TaskItem] = [
                TaskItem(1, "Buy groceries", "Milk, eggs, bread"),
                TaskItem(2, "Write docs", "Draft the MCP tutorial", True),
            ]
            self._next_id = 3
    
        def get_all(self) -> list[dict]:
            return [t.to_dict() for t in self._tasks]
    
        def get_by_id(self, task_id: int) -> dict | None:
            task = next((t for t in self._tasks if t.id == task_id), None)
            return task.to_dict() if task else None
    
        def create(self, title: str, description: str) -> dict:
            task = TaskItem(self._next_id, title, description)
            self._next_id += 1
            self._tasks.append(task)
            return task.to_dict()
    
        def toggle_complete(self, task_id: int) -> dict | None:
            task = next((t for t in self._tasks if t.id == task_id), None)
            if task is None:
                return None
            task.is_complete = not task.is_complete
            return task.to_dict()
    
        def delete(self, task_id: int) -> bool:
            task = next((t for t in self._tasks if t.id == task_id), None)
            if task is None:
                return False
            self._tasks.remove(task)
            return True
    
    
    # For demonstration only — not thread-safe.
    store = TaskStore()
    

    TaskItemデータクラスは、シリアル化のためのto_dict()メソッドを使用してデータ モデルを定義します。 TaskStore クラスは、サンプル データが事前に設定されたメモリ内リストを管理し、CRUD メソッドを提供します。 シンプルにするために、シングルトン store モジュール レベルがアプリケーション全体で共有されます。

MCP ツールを定義する

このセクションでは、AI モデルが MCP サーバーを呼び出して FastAPI アプリケーションにマウントできる MCP ツールを定義します。

  1. mcp_server.pyを作成します。

    from mcp.server.fastmcp import FastMCP
    from task_store import store
    
    mcp = FastMCP("TasksMCP", stateless_http=True)
    
    
    @mcp.tool()
    async def list_tasks() -> list[dict]:
        """List all tasks with their ID, title, description, and completion status."""
        return store.get_all()
    
    
    @mcp.tool()
    async def get_task(task_id: int) -> dict | None:
        """Get a single task by its numeric ID.
    
        Args:
            task_id: The numeric ID of the task to retrieve.
        """
        return store.get_by_id(task_id)
    
    
    @mcp.tool()
    async def create_task(title: str, description: str) -> dict:
        """Create a new task with the given title and description. Returns the created task.
    
        Args:
            title: A short title for the task.
            description: A detailed description of what the task involves.
        """
        return store.create(title, description)
    
    
    @mcp.tool()
    async def toggle_task_complete(task_id: int) -> str:
        """Toggle a task's completion status between complete and incomplete.
    
        Args:
            task_id: The numeric ID of the task to toggle.
        """
        task = store.toggle_complete(task_id)
        if task:
            status = "complete" if task["is_complete"] else "incomplete"
            return f"Task {task['id']} is now {status}."
        return f"Task with ID {task_id} not found."
    
    
    @mcp.tool()
    async def delete_task(task_id: int) -> str:
        """Delete a task by its numeric ID.
    
        Args:
            task_id: The numeric ID of the task to delete.
        """
        if store.delete(task_id):
            return f"Task {task_id} deleted."
        return f"Task with ID {task_id} not found."
    

    重要なポイント:

    • FastMCP("TasksMCP", stateless_http=True) は、Python SDK のステートレス HTTP パターンを使用して MCP サーバーを作成します。 ストリーミング可能な HTTP エンドポイントは、既定で /mcp サブパスに設定されます。
    • @mcp.tool() 関数は、呼び出し可能なツールになります。 関数 docstring とパラメーター注釈は、AI モデルが各ツールの使用方法を理解するのに役立ちます。
  2. app.pyを作成します。 このファイルは、MCP サーバーをマウントする FastAPI アプリケーションを定義します。

    from contextlib import AsyncExitStack, asynccontextmanager
    
    from fastapi import FastAPI
    from fastapi.responses import JSONResponse
    
    from mcp_server import mcp
    
    
    @asynccontextmanager
    async def lifespan(app: FastAPI):
        async with AsyncExitStack() as stack:
            await stack.enter_async_context(mcp.session_manager.run())
            yield
    
    
    app = FastAPI(lifespan=lifespan)
    app.mount("/", mcp.streamable_http_app())
    
    
    @app.get("/health")
    async def health():
        return JSONResponse({"status": "healthy"})
    

    MCP サーバー アプリはルート (/) にマウントされます。 SDK のストリーミング可能な HTTP エンドポイントは既定で /mcpされるため、完全なエンドポイント パスは /mcp

    Container Apps の正常性プローブには、個別の /health エンドポイントが使用されます。 MCP エンドポイントは、JSON-RPC POST 要求を想定しており、正常性チェックとしては適していません。

MCP サーバーをローカルでテストする

Azure にデプロイする前に、MCP サーバーがローカルで実行され、GitHub Copilot から接続されて動作することを確認します。

  1. アプリケーションを起動します。

    uvicorn app:app --reload --port 8080
    
  2. VS Code を開き、 Copilot チャット を開き、[ エージェント モード] を選択します。

  3. [ ツール ] ボタンを選択し、[ その他のツールの追加] を選択します。..>MCP サーバーを追加します

  4. HTTP (HTTP または Server-Sent イベント) を選択します。

  5. サーバーの URL を入力します。 http://localhost:8080/mcp

  6. サーバー ID を入力します。 tasks-mcp

  7. [ ワークスペースの設定] を選択します

  8. 新しい Copilot チャット プロンプトで、「すべてのタスクを表示する」と入力します。

  9. MCPツールの確認をCopilotが求めた場合は、[続行]を選択します。

メモリ内ストアから返されたタスク リストが表示されます。

ヒント

「PR を確認するタスクを作成する」、「タスク 1 を完了としてマークする」、「タスク 2 を削除する」などの他のプロンプトを試してください。

アプリケーションのコンテナー格納

Azure にデプロイする前にローカルでテストできるように、アプリケーションを Docker コンテナーとしてパッケージ化します。

  1. Dockerfile を作成します。

    FROM python:3.12-slim
    
    WORKDIR /app
    
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY . .
    
    EXPOSE 8080
    CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
    

    Dockerfile は Python 3.12 のスリムな基本イメージを使用し、 requirements.txtから依存関係をインストールしてから、アプリケーション コードをコピーします。 Uvicorn は、ポート 8080 で FastAPI アプリを提供します。

  2. コンテナーがビルドされ、ローカルで実行されていることを確認します。

    docker build -t tasks-mcp-server .
    docker run -p 8080:8080 tasks-mcp-server
    

    ヘルスエンドポイントが応答することを確認してください。 curl http://localhost:8080/health

Azure Container Apps へのデプロイ

アプリケーションをコンテナー化したら、Azure CLI を使用して Azure Container Apps にデプロイします。 az containerapp up コマンドはクラウドにコンテナー イメージをビルドするため、この手順ではマシン上に Docker は必要ありません。

  1. 環境変数を設定します。

    RESOURCE_GROUP="mcp-tutorial-rg"
    LOCATION="eastus"
    ENVIRONMENT_NAME="mcp-env"
    APP_NAME="tasks-mcp-server-py"
    
  2. リソース グループと Container Apps 環境を作成します。

    az group create --name $RESOURCE_GROUP --location $LOCATION
    
    az containerapp env create \
        --name $ENVIRONMENT_NAME \
        --resource-group $RESOURCE_GROUP \
        --location $LOCATION
    
  3. コンテナー アプリをデプロイします。

    az containerapp up \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --environment $ENVIRONMENT_NAME \
        --source . \
        --ingress external \
        --target-port 8080
    
  4. CORS を構成します。

    az containerapp ingress cors enable \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --allowed-origins "*" \
        --allowed-methods "GET,POST,DELETE,OPTIONS" \
        --allowed-headers "*"
    

    運用環境では、ワイルドカードの配信元を特定の信頼できるオリジンに置き換えます。 Container Apps での MCP サーバーのセキュリティ保護に関する情報を参照してください。

  5. デプロイを検証します。

    APP_URL=$(az containerapp show \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --query "properties.configuration.ingress.fqdn" -o tsv)
    
    curl https://$APP_URL/health
    

デプロイされたサーバーに GitHub Copilot を接続する

MCP サーバーが Azure で実行されたので、デプロイされたエンドポイントに GitHub Copilot を接続するように VS Code を構成します。

  1. .vscode/mcp.jsonを作成または更新します。

    {
        "servers": {
            "tasks-mcp-server": {
                "type": "http",
                "url": "https://<your-app-fqdn>/mcp"
            }
        }
    }
    

    <your-app-fqdn>をデプロイ出力の FQDN に置き換えます。

  2. VS Code で、エージェント モードで Copilot チャットを開きます。

  3. tasks-mcp-serverが [ツール] リストに表示されていることを確認します。 必要に応じて [開始] を選択します

  4. "ステージング環境をデプロイするタスクを作成する" などのプロンプトでテストします。

対話型使用のスケーリングを構成する

既定では、Azure Container Apps は 0 個のレプリカにスケーリングできます。 Copilot などの対話型クライアントにサービスを提供する MCP サーバーの場合、コールド スタートは顕著な遅延を引き起こします。 少なくとも 1 つのインスタンスを実行し続けるために、レプリカの最小数を設定します。

az containerapp update \
    --name $APP_NAME \
    --resource-group $RESOURCE_GROUP \
    --min-replicas 1

セキュリティに関する考慮事項

このチュートリアルでは、簡単にするために認証されていない MCP サーバーを使用します。 運用環境で MCP サーバーを実行する前に、次の推奨事項を確認してください。 大規模言語モデル (LLM) を利用するエージェントが MCP サーバーを呼び出す場合は、 迅速なインジェクション 攻撃に注意してください。

  • 認証と承認: Microsoft Entra ID を使用して MCP サーバーをセキュリティで保護します。 Container Apps での MCP サーバーのセキュリティ保護に関する情報を参照してください。
  • 入力検証: 常にツール パラメーターを検証します。 Pydantic を使用して、ツール入力にデータ検証を適用します。
  • HTTPS: Azure Container Apps では、自動 TLS 証明書を使用して既定で HTTPS が適用されます。
  • 最小権限: ユース ケースで必要なツールのみを公開します。 確認なしで破壊的な操作を実行するツールは避けてください。
  • CORS: 許可された配信元を運用環境の信頼されたドメインに制限します。
  • ログ記録と監視: MCP ツールの呼び出しを監査用にログに記録します。 Azure Monitor と Log Analytics を使用します。

リソースをクリーンアップする

このアプリケーションを引き続き使用する予定がない場合は、リソース グループを削除して、このチュートリアルで作成したすべてのリソースを削除します。

az group delete --resource-group $RESOURCE_GROUP --yes --no-wait

次のステップ