Desplegar un servidor MCP en AWS Lambda, Azure y Cloud Run, lado a lado

Un servidor MCP en TypeScript sin estado, tres hosts. Mira la configuración exacta de Lambda Web Adapter, el host.json de Azure Functions para servidores autoalojados y el comando de despliegue de Cloud Run, además de las opciones de autenticación, los tiempos de espera y las contrapartidas del arranque en frío que deciden qué plataforma encaja con tu proyecto.

Desplegar un servidor MCP en AWS Lambda, Azure y Cloud Run, lado a lado
Cristian Da Conceicao
Fundador de Picasso IA

Tu servidor MCP funciona bien en tu equipo portátil por stdio. Después, un compañero te pide una URL que pueda pegar en un cliente, y empieza el trabajo de verdad. Un servidor remoto necesita HTTPS, autenticación, un transporte que sobreviva a los balanceadores de carga y un host que no te cobre mientras nadie lo llama. Este artículo toma un servidor pequeño en TypeScript y lo pone en tres plataformas: AWS Lambda, Azure Functions y Google Cloud Run. Obtienes la configuración que importa en cada una, el control de acceso que mantiene fuera a los desconocidos y una comparación clara para elegir host en diez minutos en lugar de en una semana.

💡 Alcance: todos los fragmentos de abajo asumen el transporte Streamable HTTP. Stdio sirve para procesos hijos locales, así que un servidor que solo habla stdio necesita un frontend HTTP antes de que cualquiera de estos hosts pueda ejecutarlo.

Elige el transporte antes que la nube

Por qué lo sin estado gana en serverless

Las plataformas serverless arrancan y detienen instancias cuando quieren. La petición uno llega a la instancia A, la dos a la B y la tres provoca un arranque en frío en la C. Si tu servidor guarda una sesión en memoria, esa secuencia la rompe.

La solución es un servidor Streamable HTTP sin estado: un único endpoint /mcp que acepta un POST, responde y olvida. Las tres plataformas están construidas en torno a esa forma. La vista previa autoalojada de Azure solo acepta servidores sin estado sobre el transporte streamable-http. Cloud Run documenta SSE y Streamable HTTP como sus dos opciones remotas, con streaming de respuestas HTTP integrado. Lambda se comporta igual en cuanto pones un adaptador web delante.

Qué cambió la especificación de julio de 2026

La revisión 2026-07-28 de la especificación de MCP apuntó en la misma dirección:

  • Sin sesiones a nivel de protocolo. La cabecera Mcp-Session-Id desaparece de Streamable HTTP.
  • Sin handshake. Se eliminó el intercambio initialize, y cada petición lleva ahora su versión del protocolo y las capacidades del cliente en _meta.
  • Estado mediante handles. Un servidor que necesita memoria entre llamadas crea un handle explícito y lo pasa como un argumento normal de herramienta.
  • Sin reanudación de streams. Si un stream de respuesta se rompe, la petición en curso se pierde y el cliente debe enviarla de nuevo con un nuevo ID de petición.
  • HTTP+SSE queda obsoleto. El trabajo nuevo debe usar Streamable HTTP.

En la práctica puedes escribir el servidor como código plano de petición y respuesta y dejar que la plataforma ejecute tantas copias como quiera. Una advertencia: las versiones del SDK van por detrás de las revisiones de la especificación, así que fija la versión del SDK y prueba con los clientes que te importan antes de confiar en un despliegue.

Manos de un desarrollador dibujando tres cajas conectadas en una pizarra de cristal

Un servidor, tres destinos

El handler que comparten todos los hosts

El servidor de demostración expone dos herramientas que dan acceso a la API para desarrolladores de PicassoIA: una inicia un trabajo de imagen y la otra lo consulta. La API es de estilo Replicate y asíncrona, con una URL base de https://api.picassoia.com/v1, autenticación Bearer, POST /models/{owner}/{name}/predictions para iniciar un trabajo y GET /predictions/{id} para leerlo. Separar el trabajo en iniciar y consultar mantiene cada petición corta, lo que encaja con plataformas que cobran por milisegundo. El trabajo de abajo apunta a PicassoIA Image mediante su 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");

Crear un servidor y un transporte nuevos por petición es el patrón sin estado de los ejemplos del SDK, y apenas cuesta nada, porque registrar dos herramientas es barato. Los nombres de los métodos cambian entre versiones del SDK, así que ajusta el fragmento a la versión que instales y revisa los campos de petición y respuesta en la documentación de la API de PicassoIA.

Los secretos quedan fuera de la imagen

No incluyas nada sensible en el contenedor. Lee PICASSOIA_API_TOKEN desde el almacén de secretos de la plataforma: AWS Secrets Manager o SSM Parameter Store en Lambda, un ajuste de la aplicación que apunte a una bóveda gestionada en Azure y Google Secret Manager en Cloud Run.

💡 Las cuentas de PicassoIA permiten 5 predicciones simultáneas, compartidas entre tokens y conexiones MCP. Limita el paralelismo de tu host con la concurrencia reservada de Lambda, --max-instances y --concurrency en Cloud Run, o con el número máximo de instancias de Azure, en lugar de descubrir el límite en producción.

Vista cenital de un escritorio de roble con un equipo portátil, una libreta y té

Desplegar en AWS Lambda

Configuración de Lambda Web Adapter

La ruta menos invasiva ejecuta tu aplicación Express sin cambios a través de Lambda Web Adapter. Para una imagen de contenedor, basta con una línea adicional:

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"]

¿Prefieres paquetes zip? Adjunta la capa del adaptador, fija AWS_LAMBDA_EXEC_WRAPPER en /opt/bootstrap y apunta el handler a un script de arranque. El adaptador lee el puerto de AWS_LWA_PORT (si no existe usa PORT, por defecto 8080) y comprueba la ruta de disponibilidad antes de reenviar tráfico.

URL de función y streaming de respuestas

Coloca delante una Function URL y fija su modo de invocación en RESPONSE_STREAM, igual que la variable del adaptador de arriba. El modo almacenado por defecto retiene toda la respuesta hasta que la herramienta termina, lo que anula el streaming. Lambda te da hasta 15 minutos por invocación y hasta 10 GB de memoria, mucho más de lo que necesita una herramienta de iniciar y consultar.

Existen dos vías de acceso:

  • AWS_IAM Function URL. Quien llama firma las peticiones con SigV4. Va bien para tráfico entre servicios y resulta incómodo para clientes MCP de escritorio.
  • NONE más tu propia comprobación. Ejecuta OAuth dentro del servidor o pon un autorizador delante. Un autorizador de Cognito o de Lambda a través de API Gateway es la opción habitual, pero su tiempo de espera de integración por defecto ronda los 30 segundos, así que las llamadas largas favorecen la Function URL.

El Serverless Framework v4 puede configurar todo esto con unas pocas líneas de YAML:

mcp:
  servers:
    images:
      server: index.ts

💡 Ese artículo señala dos trampas: el inicio de sesión interactivo con OAuth necesita un dominio personalizado en la raíz, en lugar de la URL execute-api por defecto, y Cognito no tiene registro dinámico de clientes.

Largo pasillo de racks de servidores en un centro de datos con un técnico alejándose

Desplegar en Azure Functions

El host.json que importa

Azure ejecuta los servidores creados con el SDK como handlers personalizados: el host de Functions recibe la petición y la envía a tu proceso. La documentación de Microsoft sobre MCP autoalojado ofrece este archivo mínimo para un servidor en TypeScript, y el inicio rápido de Node lo muestra en un proyecto que funciona:

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

El perfil mcp-custom-handler activa el proxy HTTP, dirige todas las rutas ({*route}) a tu servidor y elimina el prefijo de ruta, de modo que /mcp llega sin tocar. Haz que el valor port coincida con el puerto en el que escucha tu servidor. Prueba en local con func start, ya que el depurador con F5 aún no está soportado, y luego publica con func azure functionapp publish <APP_NAME>.

Límites de la vista previa e inicio de sesión con Entra

Lee la letra pequeña antes de comprometerte: esta función está en vista previa pública. Solo admite servidores streamable-http sin estado, escritos con los SDK de Python, TypeScript, C# o Java, y la aplicación debe ejecutarse en el plan Flex Consumption. Si necesitas estado, Microsoft te remite a la extensión de MCP de Functions. Flex Consumption puede mantener instancias siempre listas para reducir los arranques en frío, a cambio de pagar por capacidad inactiva.

La autenticación es el punto fuerte de Azure. La autenticación de servidor integrada de la plataforma implementa por ti los requisitos de autorización de MCP: emite el desafío 401, publica el documento de metadatos del recurso protegido y envía a los clientes a Microsoft Entra ID para iniciar sesión. La versión ampliada host.json de la documentación fija defaultAuthorizationLevel en anonymous y deja el inicio de sesión en esa capa de la plataforma, así que actívalo antes de hacer pública la URL.

Mano presionando un cable de fibra óptica amarillo sobre un switch de red

Desplegar en Cloud Run

Un solo comando desde el código fuente

Cloud Run es el que menos ceremonia exige. Con un Dockerfile o un proyecto de Node en la carpeta:

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

¿Ya tienes una imagen? gcloud run deploy --image IMAGE_URL --port PORT hace el trabajo. Cloud Run inyecta PORT, y el servidor debe escuchar en 0.0.0.0, algo que ya hace el handler compartido. La línea del adaptador en el Dockerfile de la sección de Lambda es solo un archivo inerte aquí, así que una misma imagen puede servir a ambas plataformas.

Privado por defecto

Una URL nueva de Cloud Run exige el rol de IAM Cloud Run Invoker (roles/run.invoker) en cada petición. Para un cliente local, la documentación de Google recomienda un proxy que inyecte tu identidad:

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

Después apunta el cliente a http://localhost:3000/mcp. Quienes hacen llamadas automatizadas pueden enviar un token OIDC de ID como Authorization: Bearer <token>, con la audiencia (audience) fijada a la URL run.app del servicio. Los llamantes que se ejecutan en Cloud Run tienen más opciones, como un sidecar, la autenticación estándar entre servicios o Cloud Service Mesh. Un servidor público orientado al consumidor necesita --allow-unauthenticated más OAuth dentro de tu aplicación, y esa es una decisión que tomar a propósito, no por defecto.

Instancias calientes y tiempos de espera

Cloud Run escala a cero por defecto. Añade --min-instances 1 si los arranques en frío te afectan, y ten en cuenta el costo de la instancia inactiva. Las peticiones pueden durar hasta 60 minutos con --timeout (el valor por defecto son 5 minutos), el límite más alto de los tres, y el streaming de respuestas HTTP no necesita ningún interruptor extra.

Vista desde abajo de nubes blancas sobre una ladera verde con un aerogenerador

Comparación lado a lado

PreguntaAWS LambdaAzure FunctionsCloud Run
EmpaquetadoImagen de contenedor con el Web Adapter, o zip más capaHandler personalizado más host.jsonImagen de contenedor o despliegue desde el código fuente
Servidores con estadoEvitarNo en la vista previa autoalojadaEvitar
Petición más larga15 minutosSegún el plan Flex Consumption60 minutos
Opciones de inicio de sesiónFunction URL con IAM, autorizador de Cognito o de LambdaAutenticación integrada con Entra IDRol Invoker o token OIDC de ID
Instancias calientesConcurrencia aprovisionadaInstancias siempre listas--min-instances
Estado para MCP autoalojadoFunciona a través del adaptadorVista previa públicaRuta de alojamiento documentada

¿Qué host encaja con cada equipo?

  • Ya en AWS con tráfico irregular: Lambda. Pagas por petición y nada mientras está inactivo.
  • Equipo que trabaja con Microsoft y Entra ID: Azure Functions. La autenticación integrada te ahorra escribir una capa de OAuth, siempre que una función en vista previa sea aceptable.
  • Equipo pequeño con llamadas largas: Cloud Run. La menor ceremonia y el tiempo de espera más largo.

Si no sabes decidirte, crea primero una imagen de contenedor. Funciona en Cloud Run tal cual, funciona en Lambda a través del adaptador y el mismo código se ejecuta detrás del handler personalizado de Azure.

Dos ingenieros comparando hojas impresas en una mesa alta de madera

Prueba el endpoint antes que los clientes

Ejecuta MCP Inspector con npx @modelcontextprotocol/inspector, elige Streamable HTTP, pega tu URL /mcp y lista las herramientas. Luego haz la prueba que la gente se salta: llama a la URL sin credenciales.

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

Un 401 o un 403 significa que la puerta de entrada resiste. Cualquier otra respuesta quiere decir que la petición ha superado tu autenticación, y la cuenta de PicassoIA que hay detrás paga por lo que haga después quien llama.

3 errores comunes

  1. Enlazar a localhost. 127.0.0.1 funciona en el equipo portátil y falla detrás de cada una de estas plataformas. Enlaza a 0.0.0.0.
  2. Guardar el estado en memoria. Un contador o una caché que vive en el proceso desaparece en el siguiente arranque en frío. Usa handles explícitos o un almacén externo.
  3. Almacenar en búfer el stream. El modo de invocación por defecto de Lambda es almacenado, y un proxy intermedio puede hacer lo mismo. Si los mensajes de progreso llegan todos de golpe, busca un búfer.

Primer plano extremo de unos dedos escribiendo durante una prueba del endpoint

Redacta e ilustra con PicassoIA

La misma plataforma que le da a tu servidor algo que llamar también puede escribir el código que lo rodea y crear las imágenes para su documentación.

Usa Claude Sonnet 5 en PicassoIA

Un modelo de programación te lleva de los fragmentos de arriba a un servidor que encaja con tus propias herramientas. Claude Sonnet 5 gestiona tareas de programación de varios pasos y uso de herramientas, y lee imágenes, así que una captura de un despliegue fallido puede ir directamente en la petición.

  1. Abre Claude Sonnet 5 en PicassoIA.
  2. Pega un prompt que nombre el transporte, las herramientas y el host, por ejemplo: "Escribe un servidor MCP en TypeScript con Streamable HTTP, sin estado, con dos herramientas, start_job y get_job, listo para Cloud Run."
  3. Rellena una vez el prompt de sistema para que cada respuesta siga tus reglas: sin estado, enlazar a 0.0.0.0, leer PORT, sin sesiones en memoria.
  4. Elige un nivel de esfuerzo que encaje con la tarea, según la tabla de abajo.
  5. Deja max_tokens en el valor por defecto de 8192 para respuestas de un solo archivo, y pide un archivo cada vez si una respuesta se corta.
  6. Adjunta una imagen cuando tengas una captura del log. El ajuste max_image_resolution viene por defecto en 0,5 megapíxeles y la reduce antes de enviarla.
ParámetroAjuste sugeridoÚsalo para
effortlow (por defecto)Retoques de configuración y arreglos de una línea
efforthigh o maxFlujos de autenticación y errores que afectan a varios archivos
max_tokens8192 (por defecto)Un archivo por respuesta
system_promptLas reglas de tu hostingResultados coherentes en todo un proyecto
imageCaptura del errorDepurar los logs de despliegue

Para una segunda opinión sobre un error de autenticación complicado, ejecuta el mismo prompt con GPT 5.6 Sol y compara las dos respuestas.

Diseñador en un escritorio amplio con un monitor que muestra una fotografía de montaña

Genera tus propias imágenes

Una vez que el servidor esté en marcha, necesitará una cabecera para el README, un fondo para el diagrama y una tarjeta para redes sociales. PicassoIA Image convierte un prompt sencillo en una imagen terminada en segundos, con siete relaciones de aspecto desde 1:1 hasta 16:9, una semilla bloqueable para resultados reproducibles, salida en JPG, PNG o WebP y hasta dos variaciones por ejecución. Se describe como ilimitado, sin tope por imagen, así que puedes iterar con libertad. Cuando una imagen fija merece movimiento, PicassoIA Video la anima en un clip corto.

Prueba este prompt: un loft tranquilo de oficina al atardecer, un equipo portátil abierto sobre un escritorio de roble, luz suave de ventana, fotografía de 35 mm, grano de película. Cambia un detalle, bloquea la semilla, genera de nuevo y compara las dos. Abre Picasso IA, ejecuta tu primer prompt y mira cómo será tu próxima imagen. Todos los modelos están en picassoia.com/en/all-models, así que hay mucho con lo que experimentar.

Persona junto a una ventana en una mesa de un espacio de coworking tranquilo al atardecer

Compartir este artículo

Elige tu idioma