Série MCP : Héberger votre serveur MCP sur Azure

Cet article de blog est le deuxième d’une série sur la construction de votre serveur MCP, son hébergement sur Azure et finalement son intégration dans un agent M365 Copilot.

Dans la première partie, nous avons construit un serveur MCP entièrement fonctionnel en JavaScript pur, exposant trois outils de gestion de projets et prenant en charge à la fois STDIO (pour le développement local) et HTTP (pour les clients distants). Il est maintenant temps de le sortir de votre machine pour le déployer sur Azure.

Azure Container Apps est la solution idéale pour héberger un serveur MCP : serverless, mise à l’échelle jusqu’à zéro, HTTPS inclus et déploiement simplifié.


1. Conteneuriser le serveur

Note

Cette étape nécessite Docker Desktop. Téléchargez-le et installez-le depuis docker.com/products/docker-desktop. Après l’installation, démarrez Docker Desktop et attendez que l’icône de baleine dans la barre système affiche “Docker Desktop is running” avant d’exécuter des commandes docker.

Créez un Dockerfile à la racine du projet :

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY server.js .
EXPOSE 3000
CMD ["node", "server.js"]

Créez un .dockerignore :

node_modules
.env

Construire et tester localement :

docker build -t my-mcp-server .
docker run -p 3000:3000 my-mcp-server

2. Pousser vers Azure Container Registry

Note

Assurez-vous que Azure CLI est installé. Téléchargez-le depuis learn.microsoft.com/cli/azure/install-azure-cli et exécutez az login avant de continuer.

Les noms ACR sont globalement uniques dans tout Azure. Si az acr create retourne une erreur AlreadyInUse, ce nom est déjà pris. Choisissez quelque chose de distinctif : ajoutez votre nom, celui de votre entreprise, ou un suffixe aléatoire, par ex. acrmcpnico.

$RESOURCE_GROUP = "rg-mcp-demo"
$LOCATION = "westeurope"
$ACR_NAME = "acrmcpyourname"   # Doit être globalement unique dans Azure, choisissez quelque chose de distinctif
$APP_ENV = "mcp-env"
$APP_NAME = "mcp-project-server"

az group create --name $RESOURCE_GROUP --location $LOCATION

# Enregistrer le fournisseur Container Registry si ce n'est pas déjà fait
az provider register --namespace Microsoft.ContainerRegistry

# Attendre que l'enregistrement soit terminé avant de continuer
az provider show --namespace Microsoft.ContainerRegistry --query "registrationState"
# Relancez la commande ci-dessus jusqu'à voir "Registered"

az acr create `
  --resource-group $RESOURCE_GROUP `
  --name $ACR_NAME `
  --sku Basic `
  --admin-enabled true

az acr login --name $ACR_NAME
docker tag my-mcp-server "$ACR_NAME.azurecr.io/my-mcp-server:latest"
docker push "$ACR_NAME.azurecr.io/my-mcp-server:latest"

3. Déployer sur Azure Container Apps

az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

az containerapp env create `
  --name $APP_ENV `
  --resource-group $RESOURCE_GROUP `
  --location $LOCATION

$ACR_PASSWORD = az acr credential show `
  --name $ACR_NAME `
  --query "passwords[0].value" -o tsv

az containerapp create `
  --name $APP_NAME `
  --resource-group $RESOURCE_GROUP `
  --environment $APP_ENV `
  --image "$ACR_NAME.azurecr.io/my-mcp-server:latest" `
  --registry-server "$ACR_NAME.azurecr.io" `
  --registry-username $ACR_NAME `
  --registry-password $ACR_PASSWORD `
  --target-port 3000 `
  --ingress external `
  --min-replicas 0 `
  --max-replicas 3 `
  --cpu 0.25 `
  --memory 0.5Gi `
  --env-vars NODE_ENV=production

Récupérez votre URL publique :

az containerapp show `
  --name $APP_NAME `
  --resource-group $RESOURCE_GROUP `
  --query "properties.configuration.ingress.fqdn" -o tsv

Votre endpoint MCP est maintenant disponible sur https://<your-fqdn>/mcp.


Tester l’endpoint hébergé

Note

L’endpoint /mcp n’accepte que les requêtes POST. Ouvrir l’URL dans un navigateur déclenche un GET et retourne Cannot GET /mcp — c’est le comportement attendu. Utilisez l’endpoint /health pour vérifier que le conteneur fonctionne : https://<your-fqdn>/health devrait retourner {"status":"ok"}.

Pour tester l’endpoint MCP lui-même, utilisez un client API comme Bruno (open source) ou Postman.

Créez une nouvelle requête POST avec les paramètres suivants :

  • URL : https://<your-fqdn>/mcp
  • Headers :
    • Content-Type : application/json
    • Accept : application/json, text/event-stream
  • Body (JSON) :
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Une réponse réussie retourne vos trois outils au format SSE. Vous pouvez également appeler un outil spécifique :

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "listProjects",
    "arguments": {}
  }
}


Considérations de sécurité

Par défaut, votre endpoint MCP est accessible publiquement — n’importe qui possédant l’URL peut appeler vos outils. Pour tout déploiement réel, vous voudrez le protéger avec au minimum une clé API.

Étape 1 : Mettre à jour server.js

Ajoutez la vérification de la clé API à la route MCP. La clé est lue depuis une variable d’environnement afin de ne jamais se retrouver codée en dur dans votre source :

app.post("/mcp", async (req, res) => {
  const apiKey = req.headers["x-api-key"];

  if (!apiKey || apiKey !== process.env.MCP_API_KEY) {
    res.status(401).json({ error: "Unauthorized" });
    return;
  }

  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
  res.on("finish", () => transport.close());
});

Étape 2 : Tester localement

Créez un fichier .env à la racine de votre projet (ajoutez-le à .gitignore !) :

MCP_API_KEY=my-local-secret

Installez dotenv pour le charger automatiquement :

npm install dotenv

Ajoutez ceci en haut de server.js :

import "dotenv/config";

Redémarrez le serveur et vérifiez que l’authentification fonctionne :

Sans clé : doit retourner 401 :

curl -N -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Avec la bonne clé : doit retourner la liste des outils :

curl -N -X POST http://localhost:3000/mcp -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -H "x-api-key: my-local-secret" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Dans Bruno, ajoutez un header à votre requête :

HeaderValeur
x-api-keymy-local-secret

Étape 3 : Stocker le secret dans Azure Container Apps

Ne transmettez jamais les secrets sous forme de variables d’environnement en clair. Utilisez plutôt le magasin de secrets intégré de Container Apps :

az containerapp secret set `
  --name $APP_NAME `
  --resource-group $RESOURCE_GROUP `
  --secrets mcp-api-key=<your-production-secret>

az containerapp update `
  --name $APP_NAME `
  --resource-group $RESOURCE_GROUP `
  --set-env-vars MCP_API_KEY=secretref:mcp-api-key

Le secret est stocké chiffré dans Azure et injecté dans le conteneur au moment de l’exécution — il n’apparaît jamais dans les logs de déploiement ni dans la sortie de az containerapp show.

Note

Pour les scénarios d’entreprise, envisagez de placer Azure API Management devant votre Container App. APIM vous offre l’authentification OAuth2/Entra ID, la limitation de débit et une observabilité complète — le tout sans toucher au code de votre serveur.