Déployer un serveur MCP sur AWS Lambda, Azure et Cloud Run côte à côte

Un serveur MCP TypeScript sans état, trois hôtes. Découvrez la configuration exacte de Lambda Web Adapter, le host.json d’Azure Functions pour les serveurs auto-hébergés et la commande de déploiement Cloud Run, ainsi que les options d’authentification, les délais d’expiration et les compromis liés aux démarrages à froid qui décident de la plateforme adaptée à votre projet.

Déployer un serveur MCP sur AWS Lambda, Azure et Cloud Run côte à côte
Cristian Da Conceicao
Fondateur de Picasso IA

Votre serveur MCP fonctionne très bien sur votre ordinateur en stdio. Puis un collègue vous demande une URL qu’il peut coller dans un client, et le vrai travail commence. Un serveur distant a besoin de HTTPS, d’une authentification, d’un transport qui survit aux équilibreurs de charge, et d’un hôte qui ne vous facture rien quand personne ne l’appelle. Cet article prend un petit serveur TypeScript et le déploie sur trois plateformes : AWS Lambda, Azure Functions et Google Cloud Run. Vous obtenez la configuration qui compte sur chacune, le contrôle d’accès qui tient les inconnus à l’écart, et une comparaison claire pour choisir un hôte en dix minutes plutôt qu’en une semaine.

💡 Périmètre : chaque extrait ci-dessous suppose le transport Streamable HTTP. Stdio sert aux processus enfants locaux ; un serveur qui ne parle que stdio a donc besoin d’un front HTTP avant que l’un de ces hôtes puisse le faire tourner.

Choisir le transport avant le cloud

Pourquoi le sans état l’emporte en serverless

Les plateformes serverless démarrent et arrêtent les instances à leur guise. La première requête atterrit sur l’instance A, la deuxième sur l’instance B, et la troisième déclenche un démarrage à froid sur l’instance C. Si votre serveur garde une session en mémoire, cette séquence la casse.

La solution est un serveur Streamable HTTP sans état : un seul point de terminaison /mcp qui accepte un POST, répond et oublie. Les trois plateformes sont conçues autour de cette forme. La préversion auto-hébergée d’Azure n’accepte que des serveurs sans état sur le transport streamable-http. Cloud Run documente SSE et Streamable HTTP comme ses deux options distantes, avec le streaming de réponse HTTP intégré. Lambda se comporte de la même façon dès qu’on place un adaptateur web devant.

Ce qu’a changé la spécification de juillet 2026

La révision 2026-07-28 de la spécification MCP allait dans le même sens :

  • Plus de sessions au niveau du protocole. L’en-tête Mcp-Session-Id disparaît de Streamable HTTP.
  • Plus de poignée de main. L’échange initialize a été supprimé, et chaque requête porte désormais sa version de protocole et les capacités du client dans _meta.
  • État via des handles. Un serveur qui a besoin de mémoire entre les appels crée un handle explicite et le transmet comme un argument d’outil ordinaire.
  • Pas de reprise de flux. Un flux de réponse rompu fait perdre la requête en cours, et le client doit la renvoyer avec un nouvel identifiant de requête.
  • HTTP+SSE est déprécié. Les nouveaux développements doivent utiliser Streamable HTTP.

Concrètement, vous pouvez écrire le serveur comme du simple code de requête et de réponse, et laisser la plateforme lancer autant de copies qu’elle veut. Une mise en garde : les versions du SDK arrivent après les révisions de la spécification. Épinglez donc votre version du SDK et testez avec les clients qui comptent pour vous avant de faire confiance à un déploiement.

Mains d’un développeur esquissant trois boîtes connectées sur un tableau blanc en verre

Un serveur, trois cibles

Le handler que partagent tous les hôtes

La démo expose deux outils qui font office de façade pour l’API développeur de PicassoIA : l’un lance une tâche de génération d’image, l’autre en vérifie l’état. L’API est de style Replicate et asynchrone, avec une URL de base https://api.picassoia.com/v1, une authentification Bearer, POST /models/{owner}/{name}/predictions pour lancer une tâche et GET /predictions/{id} pour la lire. Séparer le travail en lancement et vérification garde chaque requête courte, ce qui convient aux plateformes qui facturent à la milliseconde. La tâche ci-dessous cible PicassoIA Image via son slug picassoia/picassoia-image.

import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

const API = "https://api.picassoia.com/v1";
const auth = { Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}` };

function buildServer() {
  const server = new McpServer({ name: "image-tools", version: "1.0.0" });

  server.registerTool(
    "start_image",
    { description: "Start an image generation", inputSchema: { prompt: z.string().max(4000) } },
    async ({ prompt }) => {
      const res = await fetch(`${API}/models/picassoia/picassoia-image/predictions`, {
        method: "POST",
        headers: { ...auth, "Content-Type": "application/json" },
        body: JSON.stringify({ input: { prompt, aspect_ratio: "16:9" } }),
      });
      const job = await res.json();
      return { content: [{ type: "text", text: JSON.stringify({ id: job.id, status: job.status }) }] };
    }
  );

  server.registerTool(
    "get_image",
    { description: "Check a generation by id", inputSchema: { id: z.string() } },
    async ({ id }) => {
      const res = await fetch(`${API}/predictions/${id}`, { headers: auth });
      return { content: [{ type: "text", text: await res.text() }] };
    }
  );
  return server;
}

const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
  const server = buildServer();
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  res.on("close", () => { transport.close(); server.close(); });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.get("/health", (_req, res) => res.send("ok"));
app.listen(Number(process.env.PORT ?? 8080), "0.0.0.0");

Un serveur et un transport neufs par requête constituent le schéma sans état des exemples du SDK, et ce schéma ne coûte presque rien, car l’enregistrement de deux outils est peu coûteux. Les noms de méthodes changent d’une version du SDK à l’autre : adaptez donc l’extrait à la version que vous installez, et vérifiez les champs de requête et de réponse dans la documentation de l’API PicassoIA.

Les secrets restent hors de l’image

N’intégrez rien de sensible dans le conteneur. Lisez PICASSOIA_API_TOKEN dans le coffre-fort de secrets de la plateforme : AWS Secrets Manager ou SSM Parameter Store sur Lambda, un paramètre d’application qui pointe vers un coffre géré sur Azure, et Google Secret Manager sur Cloud Run.

💡 Les comptes PicassoIA autorisent 5 prédictions simultanées, partagées entre les tokens et les connexions MCP. Plafonnez la diffusion de votre hôte avec la concurrence réservée de Lambda, --max-instances et --concurrency sur Cloud Run, ou le nombre maximal d’instances d’Azure, plutôt que de découvrir la limite en production.

Vue de dessus d’un bureau en chêne avec un ordinateur portable, un carnet et du thé

Déployer sur AWS Lambda

Configuration de Lambda Web Adapter

L’approche la moins intrusive fait tourner votre application Express sans modification via Lambda Web Adapter. Pour une image de conteneur, cela tient en une ligne supplémentaire :

FROM public.ecr.aws/docker/library/node:22-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:1.1.0 /lambda-adapter /opt/extensions/lambda-adapter
ENV PORT=8080 AWS_LWA_INVOKE_MODE=response_stream AWS_LWA_READINESS_CHECK_PATH=/health
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
CMD ["node", "dist/server.js"]

Vous préférez les paquets zip ? Ajoutez la couche de l’adaptateur, réglez AWS_LAMBDA_EXEC_WRAPPER sur /opt/bootstrap, et pointez le handler vers un script de démarrage. L’adaptateur lit le port dans AWS_LWA_PORT (il se rabat sur PORT, par défaut 8080) et interroge le chemin de disponibilité avant de transmettre le trafic.

Function URL et streaming de réponse

Placez une Function URL devant et réglez son mode d’invocation sur RESPONSE_STREAM, en cohérence avec la variable de l’adaptateur ci-dessus. Le mode tamponné par défaut conserve la réponse entière jusqu’à la fin de l’outil, ce qui annule le streaming. Lambda offre jusqu’à 15 minutes par invocation et jusqu’à 10 Go de mémoire, bien plus que ce qu’exige un outil de type démarrage-et-vérification.

Deux chemins d’accès existent :

  • AWS_IAM Function URL. Les appelants signent leurs requêtes avec SigV4. Bien adapté au trafic de service à service, peu pratique pour les clients MCP de bureau.
  • NONE plus votre propre contrôle. Exécutez OAuth dans le serveur ou placez un autorisateur devant. Un autorisateur Cognito ou Lambda via API Gateway est le choix habituel, mais son délai d’intégration par défaut tourne autour de 30 secondes. Les longs appels d’outils favorisent donc la Function URL.

Le Serverless Framework v4 peut câbler tout cela en quelques lignes de YAML :

mcp:
  servers:
    images:
      server: index.ts

💡 Ce billet signale deux pièges : la connexion OAuth interactive exige un domaine personnalisé à la racine plutôt que l’URL par défaut execute-api, et Cognito ne prend pas en charge l’enregistrement dynamique de clients.

Long couloir de baies de serveurs dans un centre de données, avec un technicien qui s’éloigne

Déployer sur Azure Functions

Le host.json qui compte

Azure exécute les serveurs construits avec le SDK comme des gestionnaires personnalisés : l’hôte Functions reçoit la requête et la transmet à votre processus. La documentation Microsoft sur MCP auto-hébergé donne ce fichier minimal pour un serveur TypeScript, et le démarrage rapide Node le montre dans un projet fonctionnel :

{
  "version": "2.0",
  "configurationProfile": "mcp-custom-handler",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "npm",
      "arguments": ["run", "start"]
    },
    "port": "8080"
  }
}

Le profil mcp-custom-handler active le proxy HTTP, route tous les chemins ({*route}) vers votre serveur et supprime le préfixe de route, de sorte que /mcp arrive intact. Faites correspondre la valeur port au port sur lequel écoute votre serveur. Testez en local avec func start, car le débogueur F5 n’est pas encore pris en charge, puis publiez avec func azure functionapp publish <APP_NAME>.

Limites de la préversion et connexion Entra

Lisez les petites lignes avant de vous engager : cette fonctionnalité est en préversion publique. Elle ne prend en charge que les serveurs streamable-http sans état, écrits avec les SDK Python, TypeScript, C# ou Java, et l’application doit tourner sur le plan Flex Consumption. Si vous avez besoin d’un état, Microsoft vous oriente vers l’extension MCP de Functions. Flex Consumption peut garder des instances toujours prêtes pour réduire les démarrages à froid, au prix de la facturation de la capacité inactive.

L’authentification est le point fort d’Azure. L’authentification serveur intégrée à la plateforme implémente pour vous les exigences d’autorisation MCP : elle émet le défi 401, publie le document Protected Resource Metadata et envoie les clients vers Microsoft Entra ID pour se connecter. L’exemple développé host.json de la documentation définit defaultAuthorizationLevel sur anonymous et laisse la connexion à cette couche de la plateforme. Activez-la donc avant que l’URL ne soit rendue publique.

Main enfonçant un câble de raccordement en fibre jaune dans un commutateur réseau

Déployer sur Cloud Run

Une seule commande depuis les sources

Cloud Run est le plus simple à mettre en place. Avec un Dockerfile ou un projet Node dans le dossier :

gcloud run deploy mcp-images --source . --region us-central1 \
  --set-secrets PICASSOIA_API_TOKEN=picassoia-token:latest \
  --max-instances 3

Vous avez déjà une image ? gcloud run deploy --image IMAGE_URL --port PORT suffit. Cloud Run injecte PORT, et le serveur doit se lier à 0.0.0.0, ce que fait déjà le handler partagé. La ligne d’adaptateur du Dockerfile de la section Lambda n’est ici qu’un fichier inerte, de sorte qu’une seule image peut servir aux deux plateformes.

Privé par défaut

Une nouvelle URL Cloud Run exige le rôle IAM Cloud Run Invoker (roles/run.invoker) sur chaque requête. Pour un client local, la documentation de Google recommande un proxy qui injecte votre identité :

gcloud run services proxy mcp-images --region us-central1 --port=3000

Pointez ensuite le client vers http://localhost:3000/mcp. Les appelants automatisés peuvent envoyer un jeton d’identité OIDC sous la forme Authorization: Bearer <token>, avec l’audience définie sur l’URL run.app du service. Les appelants qui tournent sur Cloud Run ont davantage d’options, dont un sidecar, l’authentification standard de service à service ou Cloud Service Mesh. Un serveur public destiné aux consommateurs a besoin de --allow-unauthenticated plus OAuth dans votre application, et c’est une décision à prendre délibérément, pas par défaut.

Instances préchauffées et délais d’attente

Cloud Run passe à zéro instance par défaut. Ajoutez --min-instances 1 si les démarrages à froid vous gênent, et prévoyez le coût de l’instance inactive. Les requêtes peuvent durer jusqu’à 60 minutes avec --timeout (la valeur par défaut est de 5 minutes), le plafond le plus élevé des trois, et le streaming de réponse HTTP ne demande aucune option supplémentaire.

Vue en contre-plongée de nuages blancs au-dessus d’une colline verte avec une éolienne

Comparaison côte à côte

QuestionAWS LambdaAzure FunctionsCloud Run
Format de déploiementImage de conteneur avec le Web Adapter, ou zip plus coucheGestionnaire personnalisé plus host.jsonImage de conteneur ou déploiement depuis les sources
Serveurs avec étatÀ éviterPas dans la préversion auto-hébergéeÀ éviter
Requête la plus longue15 minutesDéfini par le plan Flex Consumption60 minutes
Options de connexionFunction URL avec IAM, Cognito ou autorisateur LambdaAuthentification intégrée avec Entra IDRôle Invoker ou jeton d’identité OIDC
Instances préchaufféesConcurrence provisionnéeInstances toujours prêtes--min-instances
Statut pour le MCP auto-hébergéFonctionne via l’adaptateurPréversion publiqueChemin d’hébergement documenté

Quel hôte pour quelle équipe ?

  • Déjà sur AWS avec un trafic irrégulier : Lambda. Vous payez à la requête et rien pendant les périodes d’inactivité.
  • Environnement Microsoft avec Entra ID : Azure Functions. L’authentification intégrée vous évite d’écrire une couche OAuth, à condition qu’une fonctionnalité en préversion soit acceptable.
  • Petite équipe avec de longs appels d’outils : Cloud Run. La mise en place la plus simple et le délai le plus long.

Si vous ne parvenez pas à trancher, construisez d’abord une image de conteneur. Elle tourne telle quelle sur Cloud Run, sur Lambda via l’adaptateur, et le même code fonctionne derrière le gestionnaire personnalisé Azure.

Deux ingénieurs comparant des feuilles imprimées à une grande table en bois haute

Tester le point de terminaison avant les clients

Lancez le MCP Inspector avec npx @modelcontextprotocol/inspector, choisissez Streamable HTTP, collez votre URL /mcp et listez les outils. Puis faites le test que les gens sautent : appelez l’URL sans identifiants.

curl -i -X POST "$URL/mcp" -H "Content-Type: application/json" -d '{}'

Un 401 ou un 403 signifie que la porte d’entrée tient. Toute autre réponse signifie que la requête a franchi votre authentification, et votre compte en amont paie pour ce que cet appelant fait ensuite.

3 erreurs courantes

  1. Se lier à localhost. 127.0.0.1 fonctionne sur un ordinateur portable mais échoue derrière chacune de ces plateformes. Liez-vous à 0.0.0.0.
  2. Garder l’état en mémoire. Un compteur ou un cache qui vit dans le processus disparaît au démarrage à froid suivant. Utilisez des handles explicites ou un stockage externe.
  3. Mettre le flux en tampon. Le mode d’invocation par défaut de Lambda est tamponné, et un proxy intermédiaire peut faire de même. Si les messages de progression arrivent en bloc, cherchez un tampon.

Gros plan extrême de doigts en train de taper pendant un test de point de terminaison

Rédiger et illustrer avec PicassoIA

La même plateforme qui fournit à votre serveur quelque chose à appeler peut aussi écrire le code qui l’entoure et créer les images de sa documentation.

Utiliser Claude Sonnet 5 sur PicassoIA

Un modèle de code vous mène des extraits ci-dessus à un serveur qui correspond à vos propres outils. Claude Sonnet 5 gère le codage en plusieurs étapes et les tâches d’usage d’outils, et lit les images : une capture d’écran d’un déploiement raté peut donc entrer directement dans la requête.

  1. Ouvrez Claude Sonnet 5 sur PicassoIA.
  2. Collez un prompt qui nomme le transport, les outils et l’hôte, par exemple : « Write a stateless Streamable HTTP MCP server in TypeScript with two tools, start_job and get_job, ready for Cloud Run. »
  3. Remplissez une fois le system prompt pour que chaque réponse suive vos règles : sans état, liaison à 0.0.0.0, lecture de PORT, pas de sessions en mémoire.
  4. Choisissez un niveau d’effort adapté à la tâche, en vous aidant du tableau ci-dessous.
  5. Laissez max_tokens à la valeur par défaut de 8 192 pour les réponses en un seul fichier, et demandez un fichier à la fois si une réponse est coupée.
  6. Joignez une image lorsque vous avez une capture de journal. Le réglage max_image_resolution est par défaut à 0,5 mégapixel et la réduit avant l’envoi.
ParamètreRéglage suggéréÀ utiliser pour
effortlow (par défaut)Ajustements de configuration et corrections d’une ligne
efforthigh ou maxFlux d’authentification et bogues touchant plusieurs fichiers
max_tokens8 192 (par défaut)Un fichier par réponse
system_promptLes règles de votre hébergementSortie cohérente sur l’ensemble d’un projet
imageCapture d’écran d’erreurDébogage des journaux de déploiement

Pour un second avis sur un bogue d’authentification délicat, passez le même prompt par GPT 5.6 Sol et comparez les deux réponses.

Designer à un large bureau avec un écran affichant une photo de montagne

Générer vos propres images

Une fois le serveur en ligne, il lui faut un en-tête de README, un fond de schéma et une carte pour les réseaux sociaux. PicassoIA Image transforme un prompt simple en image finie en quelques secondes, avec sept formats, de 1:1 à 16:9, une seed verrouillable pour des résultats reproductibles, une sortie en JPG, PNG ou WebP et jusqu’à deux variations par exécution. Il est présenté comme illimité, sans plafond par image, ce qui vous permet d’itérer librement. Quand une image fixe mérite du mouvement, PicassoIA Video l’anime en un court clip.

Essayez ce prompt : a quiet loft office at dusk, laptop open on an oak desk, soft window light, 35mm photograph, film grain. Modifiez un seul détail, verrouillez la seed, générez à nouveau et comparez les deux. Ouvrez Picasso IA, lancez votre premier prompt et découvrez à quoi ressemblera votre prochaine image. Chaque modèle se trouve sur picassoia.com/en/all-models, il y a donc de quoi expérimenter.

Personne à une table près d’une fenêtre dans un espace de coworking calme au crépuscule

Partager cet article

Choisissez votre langue