Partager via


Tutoriel : Déployer un serveur MCP Python sur Azure Container Apps

Dans ce tutoriel, vous créez un serveur MCP (Model Context Protocol) qui expose des outils de gestion des tâches à l’aide de FastAPI et du SDK Python MCP. Vous déployez le serveur sur Azure Container Apps et vous y connectez à partir de GitHub Copilot Chat dans VS Code.

Dans ce tutoriel, vous allez :

  • Créer une application FastAPI qui expose les outils MCP
  • Tester le serveur MCP localement avec GitHub Copilot
  • Conteneuriser et déployer l’application sur Azure Container Apps
  • Connecter GitHub Copilot au serveur MCP déployé

Prerequisites

Créer la structure de l’application

Dans cette section, vous allez créer un projet Python avec FastAPI et le Kit de développement logiciel (SDK) Python MCP.

  1. Créez le répertoire du projet et configurez un environnement virtuel :

    mkdir tasks-mcp-server && cd tasks-mcp-server
    python -m venv .venv
    source .venv/bin/activate
    
  2. Créer requirements.txt:

    fastapi>=0.115.0
    uvicorn>=0.30.0
    mcp[cli]>=1.2.0
    
  3. Installez des dépendances :

    pip install -r requirements.txt
    
  4. Créez task_store.py pour le magasin de données en mémoire :

    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()
    

    La TaskItem classe de données définit le modèle de données avec une to_dict() méthode de sérialisation. La classe TaskStore gère une liste en mémoire préremplie avec des données d'exemple et fournit des méthodes CRUD. Le singleton au niveau store du module est partagé avec l'application entière pour simplifier.

Définir les outils MCP

Dans cette section, vous définissez les outils MCP que le modèle IA peut appeler et monter le serveur MCP dans votre application FastAPI.

  1. Créer 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."
    

    Points clés :

    • FastMCP("TasksMCP", stateless_http=True) crée un serveur MCP à l’aide du modèle HTTP sans état dans le Kit de développement logiciel (SDK) Python. Le point de terminaison HTTP streamable est défini par défaut sur le /mcp sous-chemin.
    • Chaque @mcp.tool() fonction devient un outil invocable. Les annotations function docstring et de paramètres aident le modèle d'IA à comprendre comment utiliser chaque outil.
  2. Créer app.py. Ce fichier définit l’application FastAPI qui monte le serveur MCP :

    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"})
    

    L’application serveur MCP monte à la racine (/). Le point de terminaison HTTP streamable du Kit de développement logiciel (SDK) a la valeur par défaut /mcp: le chemin d’accès complet du point de terminaison est /mcpdonc .

    Un point de terminaison distinct /health est utilisé pour les sondes de santé des Container Apps. Les points de terminaison MCP attendent les demandes POST JSON-RPC et ne conviennent pas comme contrôles d'intégrité.

Tester le serveur MCP localement

Avant de déployer sur Azure, vérifiez que le serveur MCP fonctionne en l’exécutant localement et en vous connectant à partir de GitHub Copilot.

  1. Démarrez l’application :

    uvicorn app:app --reload --port 8080
    
  2. Ouvrez VS Code, puis ouvrez Copilot Chat et sélectionnez le mode Agent .

  3. Sélectionnez le bouton Outils , puis sélectionnez Ajouter d’autres outils...>Ajoutez le serveur MCP.

  4. Sélectionnez HTTP (HTTP ou événements envoyés par le serveur).

  5. Entrez l’URL du serveur : http://localhost:8080/mcp

  6. Entrez un ID de serveur : tasks-mcp

  7. Sélectionnez Paramètres de l’espace de travail.

  8. Dans une nouvelle invite de conversation Copilot, tapez : « Afficher toutes les tâches »

  9. Sélectionnez Continuer lorsque Copilot demande la confirmation de l’outil MCP.

Vous devez voir la liste des tâches retournée par votre mémoire vive.

Conseil / Astuce

Essayez d'autres invites comme « Créer une tâche pour examiner la demande de tirage », « Marquer la tâche 1 comme terminée » ou « Supprimer la tâche 2 ».

Conteneuriser l’application

Empaqueter l’application en tant que conteneur Docker afin de pouvoir la tester localement avant de le déployer sur Azure.

  1. Créez un 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 utilise une image de base Python 3.12 slim, installe les dépendances à partir de requirements.txt, puis copie le code de l’application. Uvicorn sert l’application FastAPI sur le port 8080.

  2. Vérifiez que les constructions et les exécutions du conteneur se font localement.

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

    Confirmez que le point de terminaison d'intégrité répond : curl http://localhost:8080/health

Déployer sur Azure Container Apps

Après avoir conteneurisé l’application, déployez-la sur Azure Container Apps à l’aide d’Azure CLI. La az containerapp up commande génère l’image conteneur dans le cloud. Vous n’avez donc pas besoin de Docker sur votre machine pour cette étape.

  1. Définissez des variables d’environnement :

    RESOURCE_GROUP="mcp-tutorial-rg"
    LOCATION="eastus"
    ENVIRONMENT_NAME="mcp-env"
    APP_NAME="tasks-mcp-server-py"
    
  2. Créez un groupe de ressources et un environnement Container Apps :

    az group create --name $RESOURCE_GROUP --location $LOCATION
    
    az containerapp env create \
        --name $ENVIRONMENT_NAME \
        --resource-group $RESOURCE_GROUP \
        --location $LOCATION
    
  3. Déployez l’application conteneur :

    az containerapp up \
        --name $APP_NAME \
        --resource-group $RESOURCE_GROUP \
        --environment $ENVIRONMENT_NAME \
        --source . \
        --ingress external \
        --target-port 8080
    
  4. Configurer CORS :

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

    Note

    Pour la production, remplacez les origines génériques par des origines approuvées spécifiques. Consultez les serveurs MCP sécurisés sur Container Apps.

  5. Vérifiez le déploiement :

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

Connecter GitHub Copilot au serveur déployé

Maintenant que le serveur MCP s’exécute dans Azure, configurez VS Code pour connecter GitHub Copilot au point de terminaison déployé.

  1. Créer ou mettre à jour .vscode/mcp.json:

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

    Remplacez <your-app-fqdn> par le FQDN de la sortie du déploiement.

  2. Dans VS Code, ouvrez Copilot Chat en mode Agent.

  3. Vérifiez que tasks-mcp-server apparaît dans la liste des outils. Sélectionnez Démarrer si nécessaire.

  4. Testez avec une invite comme « Créer une tâche pour déployer l’environnement intermédiaire ».

Configurer la mise à l’échelle pour une utilisation interactive

Par défaut, Azure Container Apps peut effectuer une mise à l’échelle vers zéro réplica. Pour les serveurs MCP qui servent des clients interactifs comme Copilot, les démarrages à froid provoquent des retards notables. Définissez un nombre minimal de réplicas pour conserver au moins une instance en cours d’exécution :

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

Considérations relatives à la sécurité

Ce tutoriel utilise un serveur MCP non authentifié pour plus de simplicité. Avant d’exécuter un serveur MCP en production, passez en revue les recommandations suivantes. Lorsqu’un agent alimenté par de grands modèles de langage (LLMs) appelle votre serveur MCP, tenez compte des attaques d’injection de commande.

  • Authentification et autorisation : Sécurisez votre serveur MCP avec l’ID Microsoft Entra. Consultez les serveurs MCP sécurisés sur Container Apps.
  • Validation d’entrée : validez toujours les paramètres de l’outil. Utilisez Pydantic pour appliquer la validation des données sur les entrées d’outil.
  • HTTPS : Azure Container Apps applique HTTPS par défaut avec des certificats TLS automatiques.
  • Privilège minimum : exposez uniquement les outils dont votre cas d’usage a besoin. Évitez les outils qui effectuent des opérations destructrices sans confirmation.
  • CORS : Restreindre les origines autorisées aux domaines de confiance en production.
  • Journalisation et surveillance : journaliser les appels de l'outil MCP pour l'audit. Utilisez Azure Monitor et Log Analytics.

Nettoyer les ressources

Si vous ne prévoyez pas de continuer à utiliser cette application, supprimez le groupe de ressources pour supprimer toutes les ressources que vous avez créées dans ce tutoriel :

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

Étape suivante