Como publicar um servidor MCP no AWS Lambda, Azure e Cloud Run lado a lado

Um servidor MCP em TypeScript sem estado, três hosts. Veja a configuração exata do Lambda Web Adapter, o host.json do Azure Functions para servidores autogerenciados e o comando de deploy do Cloud Run, além das opções de autenticação, dos timeouts e das contrapartidas de cold start que definem qual plataforma serve ao seu projeto.

Como publicar um servidor MCP no AWS Lambda, Azure e Cloud Run lado a lado
Cristian Da Conceicao
Fundador do Picasso IA

Seu servidor MCP roda bem no notebook via stdio. Depois, um colega pede uma URL que ele possa colar em um cliente, e aí o trabalho de verdade começa. Um servidor remoto precisa de HTTPS, autenticação, um transporte que sobreviva a balanceadores de carga e um host que não cobra de você enquanto ninguém o chama. Este artigo pega um servidor TypeScript pequeno e o coloca em três plataformas: AWS Lambda, Azure Functions e Google Cloud Run. Você recebe a configuração que realmente importa em cada uma, o controle de acesso que mantém estranhos do lado de fora e uma comparação direta para escolher um host em dez minutos, e não em uma semana.

💡 Escopo: todos os trechos abaixo partem do transporte Streamable HTTP. O stdio serve para processos filhos locais, então um servidor que fala apenas stdio precisa de uma camada HTTP antes que qualquer um desses hosts consiga executá-lo.

Escolha o transporte antes da nuvem

Por que o modo sem estado vence no serverless

Plataformas serverless iniciam e encerram instâncias quando bem entendem. A requisição um cai na instância A, a dois na instância B, e a três dispara um cold start na instância C. Se o seu servidor guarda uma sessão na memória, essa sequência quebra tudo.

A solução é um servidor Streamable HTTP sem estado: um único endpoint /mcp que aceita um POST, responde e esquece. As três plataformas são construídas em torno desse formato. A prévia autogerenciada do Azure só aceita servidores sem estado no transporte streamable-http. O Cloud Run documenta SSE e Streamable HTTP como suas duas opções remotas, com streaming de resposta HTTP integrado. O Lambda funciona da mesma forma assim que você coloca um web adapter na frente.

O que a especificação de julho de 2026 mudou

A revisão 2026-07-28 da especificação do MCP seguiu na mesma direção:

  • Sem sessões no nível do protocolo. O cabeçalho Mcp-Session-Id foi removido do Streamable HTTP.
  • Sem handshake. A troca initialize foi removida, e agora toda requisição leva sua versão de protocolo e as capacidades do cliente em _meta.
  • Estado por meio de handles. Um servidor que precisa de memória entre chamadas gera um handle explícito e o passa como um argumento comum de ferramenta.
  • Sem retomada de stream. Um stream de resposta interrompido perde a requisição em andamento, e o cliente precisa enviá-la de novo com um novo ID de requisição.
  • HTTP+SSE foi descontinuado. Trabalhos novos devem usar Streamable HTTP.

Na prática, você pode escrever o servidor como código comum de requisição e resposta e deixar a plataforma rodar quantas cópias quiser. Um alerta: as versões do SDK vêm depois das revisões da especificação, então fixe a versão do seu SDK e teste com os clientes que você usa antes de confiar em um deploy.

Mãos de um desenvolvedor desenhando três caixas conectadas em um quadro branco de vidro

Um servidor, três destinos

O handler que todos os hosts compartilham

O servidor de demonstração expõe duas ferramentas que servem de fachada para a API para desenvolvedores da PicassoIA: uma inicia um job de imagem, a outra consulta o resultado. A API segue o estilo Replicate, é assíncrona e tem a URL base https://api.picassoia.com/v1, autenticação Bearer, POST /models/{owner}/{name}/predictions para iniciar um job e GET /predictions/{id} para ler o resultado. Separar o trabalho em início e consulta mantém cada requisição curta, o que combina com plataformas que cobram por milissegundo. O job abaixo tem como alvo o PicassoIA Image pelo 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");

Um servidor e um transporte novos a cada requisição é o padrão sem estado dos exemplos do SDK, e custa quase nada, porque registrar duas ferramentas é barato. Os nomes dos métodos mudam entre as versões do SDK, então ajuste o trecho à versão que você instalar e confira os campos de requisição e resposta na documentação da API PicassoIA.

Segredos ficam fora da imagem

Não coloque nada sensível dentro do contêiner. Leia PICASSOIA_API_TOKEN do cofre de segredos da plataforma: AWS Secrets Manager ou SSM Parameter Store no Lambda, uma configuração de aplicativo que aponta para um cofre gerenciado no Azure e o Google Secret Manager no Cloud Run.

💡 As contas da PicassoIA permitem 5 predições simultâneas, compartilhadas entre tokens e conexões MCP. Limite o paralelismo do seu host com a concorrência reservada do Lambda, --max-instances e --concurrency no Cloud Run, ou o número máximo de instâncias do Azure, em vez de descobrir o teto em produção.

Vista de cima de uma mesa de carvalho com notebook, caderno e chá

Faça o deploy no AWS Lambda

Configuração do Lambda Web Adapter

O caminho menos invasivo roda seu app Express sem alterações pelo Lambda Web Adapter. Para uma imagem de contêiner, é apenas uma linha a mais:

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

Prefere pacotes zip? Anexe a camada do adapter, defina AWS_LAMBDA_EXEC_WRAPPER como /opt/bootstrap e aponte o handler para um script de inicialização. O adapter lê a porta de AWS_LWA_PORT (ele recorre a PORT, padrão 8080) e verifica o caminho de prontidão antes de encaminhar o tráfego.

Function URL e streaming de resposta

Coloque uma Function URL na frente e defina o modo de invocação como RESPONSE_STREAM, que combine com a variável do adapter acima. O modo bufferizado padrão segura a resposta inteira até a ferramenta terminar, o que anula o streaming. O Lambda oferece até 15 minutos por invocação e até 10 GB de memória, muito mais do que uma ferramenta de início e consulta precisa.

Existem dois caminhos de acesso:

  • AWS_IAM Function URL. Os chamadores assinam as requisições com SigV4. Bom para tráfego entre serviços, incômodo para clientes MCP de desktop.
  • NONE mais uma verificação própria. Execute OAuth dentro do servidor ou coloque um autorizador na frente. Um autorizador Cognito ou Lambda pelo API Gateway é a escolha usual, mas o timeout padrão da integração fica perto de 30 segundos, então chamadas longas de ferramenta favorecem a Function URL.

O Serverless Framework v4 pode configurar tudo isso com algumas linhas de YAML:

mcp:
  servers:
    images:
      server: index.ts

💡 Esse post aponta duas armadilhas: o login OAuth interativo exige um domínio personalizado na raiz, e não a URL padrão execute-api, e o Cognito não tem registro dinâmico de clientes.

Corredor longo de racks de servidores em um data center com um técnico se afastando

Faça o deploy no Azure Functions

O host.json que importa

O Azure executa servidores construídos com o SDK como custom handlers: o host do Functions recebe a requisição e a repassa ao seu processo. A documentação de MCP autogerenciado da Microsoft traz este arquivo mínimo para um servidor TypeScript, e o quickstart de Node o mostra em um projeto funcional:

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

O perfil mcp-custom-handler ativa o proxy HTTP, roteia todos os caminhos ({*route}) para o seu servidor e limpa o prefixo de rota, para que /mcp chegue intacto. Faça o valor port coincidir com a porta em que o servidor escuta. Teste localmente com func start, já que o depurador com F5 ainda não é suportado, e depois publique com func azure functionapp publish <APP_NAME>.

Limites da prévia e login com Entra

Leia as letras miúdas antes de se comprometer: este recurso está em prévia pública. Ele aceita apenas servidores sem estado, em streamable-http, escritos com os SDKs de Python, TypeScript, C# ou Java, e o app precisa rodar no plano Flex Consumption. Se você precisa de estado, a Microsoft indica a extensão de MCP do Functions. O Flex Consumption pode manter instâncias sempre prontas para reduzir cold starts, ao custo de pagar pela capacidade ociosa.

A autenticação é onde o Azure se destaca. A autenticação de servidor integrada da plataforma implementa para você os requisitos de autorização do MCP: emite o desafio 401, publica o documento de Protected Resource Metadata e envia os clientes ao Microsoft Entra ID para entrar. O host.json expandido da documentação define defaultAuthorizationLevel como anonymous e deixa o login a cargo da camada da plataforma, então ative isso antes de a URL ir para qualquer lugar público.

Mão encaixando um cabo de fibra óptica amarelo em um switch de rede

Faça o deploy no Cloud Run

Um comando a partir do código-fonte

O Cloud Run é o que menos complica. Com um Dockerfile ou um projeto Node na pasta:

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

Já tem uma imagem? gcloud run deploy --image IMAGE_URL --port PORT resolve. O Cloud Run injeta PORT, e o servidor precisa escutar em 0.0.0.0, o que o handler compartilhado já faz. A linha do adapter no Dockerfile da seção do Lambda é apenas um arquivo inerte aqui, então uma mesma imagem pode servir às duas plataformas.

Privado por padrão

Uma nova URL do Cloud Run exige o papel de IAM Cloud Run Invoker (roles/run.invoker) em cada requisição. Para um cliente local, a documentação do Google recomenda um proxy que injete a sua identidade:

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

Depois, aponte o cliente para http://localhost:3000/mcp. Chamadores automatizados podem enviar um token ID OIDC como Authorization: Bearer <token>, com o público definido como a URL run.app do serviço. Chamadores que rodam no Cloud Run têm mais opções, incluindo um sidecar, a autenticação padrão entre serviços ou o Cloud Service Mesh. Um servidor público, voltado ao consumidor, precisa de --allow-unauthenticated mais OAuth dentro do seu app, e essa é uma decisão a tomar de propósito, não por padrão.

Instâncias quentes e timeouts

O Cloud Run escala a zero por padrão. Adicione --min-instances 1 se os cold starts incomodarem, e preveja o custo da instância ociosa. As requisições podem durar até 60 minutos com --timeout (o padrão é 5 minutos), o maior teto dos três, e o streaming de resposta HTTP não exige nenhuma opção extra.

Vista em contra-plongée de nuvens brancas sobre uma colina verde com uma turbina eólica

Comparação lado a lado

PerguntaAWS LambdaAzure FunctionsCloud Run
EmpacotamentoImagem de contêiner com o Web Adapter, ou zip mais camadaCustom handler mais host.jsonImagem de contêiner ou deploy a partir do código-fonte
Servidores com estadoEvitarNão na prévia autogerenciadaEvitar
Requisição mais longa15 minutosDefinida pelo plano Flex Consumption60 minutos
Opções de loginFunction URL com IAM, autorizador Cognito ou LambdaAutenticação integrada com Entra IDPapel Invoker ou token ID OIDC
Instâncias quentesConcorrência provisionadaInstâncias sempre prontas--min-instances
Status para MCP autogerenciadoFunciona pelo adapterPrévia públicaCaminho de hospedagem documentado

Qual host serve para cada time?

  • Já na AWS, com tráfego irregular: Lambda. Você paga por requisição e nada enquanto está ocioso.
  • Time da Microsoft com Entra ID: Azure Functions. A autenticação integrada poupa você de escrever uma camada OAuth, desde que um recurso em prévia seja aceitável.
  • Time pequeno com chamadas longas de ferramenta: Cloud Run. A menor complicação e o timeout mais longo.

Se você não consegue decidir, construa primeiro uma imagem de contêiner. Ela roda no Cloud Run como está, roda no Lambda pelo adapter, e o mesmo código roda atrás do custom handler do Azure.

Dois engenheiros comparando folhas impressas em uma mesa alta de madeira

Teste o endpoint antes dos clientes

Execute o MCP Inspector com npx @modelcontextprotocol/inspector, escolha Streamable HTTP, cole sua URL /mcp e liste as ferramentas. Depois, faça o teste que as pessoas pulam: chame a URL sem credenciais.

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

Um 401 ou 403 significa que a porta da frente está protegida. Qualquer outra resposta significa que a requisição passou pela sua autenticação, e a sua conta upstream paga por tudo o que esse chamador fizer a seguir.

3 erros comuns

  1. Vincular ao localhost. 127.0.0.1 funciona no notebook e falha atrás de qualquer uma dessas plataformas. Vincule a 0.0.0.0.
  2. Manter estado na memória. Um contador ou cache que vive no processo desaparece no próximo cold start. Use handles explícitos ou um armazenamento externo.
  3. Bufferizar o stream. O modo de invocação padrão do Lambda é bufferizado, e um proxy no meio do caminho pode fazer o mesmo. Se as mensagens de progresso chegarem todas de uma vez, procure um buffer.

Close extremo de dedos digitando durante um teste de endpoint

Escreva e ilustre com a PicassoIA

A mesma plataforma que oferece algo para o seu servidor chamar também pode escrever o código ao redor dele e criar as imagens da documentação.

Use o Claude Sonnet 5 na PicassoIA

Um modelo de programação leva você dos trechos acima a um servidor que combina com as suas próprias ferramentas. O Claude Sonnet 5 lida com tarefas de programação em várias etapas e uso de ferramentas, e lê imagens, então uma captura de tela de um deploy que falhou pode ir direto para a requisição.

  1. Abra o Claude Sonnet 5 na PicassoIA.
  2. Cole um prompt que nomeie o transporte, as ferramentas e o host, por exemplo: "Write a stateless Streamable HTTP MCP server in TypeScript with two tools, start_job and get_job, ready for Cloud Run."
  3. Preencha o prompt de sistema uma vez para que cada resposta siga as suas regras: sem estado, vincular a 0.0.0.0, ler PORT, sem sessões na memória.
  4. Escolha um nível de effort adequado à tarefa, usando a tabela abaixo.
  5. Deixe o max_tokens no padrão de 8.192 para respostas de um único arquivo, e peça um arquivo por vez se uma resposta for cortada.
  6. Anexe uma imagem quando tiver uma captura de log. A configuração max_image_resolution vem em 0,5 megapixel por padrão e reduz a imagem antes do envio.
ParâmetroConfiguração sugeridaUse para
effortlow (padrão)Ajustes de configuração e correções de uma linha
efforthigh ou maxFluxos de autenticação e bugs que tocam vários arquivos
max_tokens8192 (padrão)Um arquivo por resposta
system_promptSuas regras de hospedagemSaída coerente em todo o projeto
imageCaptura de erroDepuração de logs de deploy

Para uma segunda opinião sobre um bug difícil de autenticação, rode o mesmo prompt no GPT 5.6 Sol e compare as duas respostas.

Designer em uma mesa larga com um monitor mostrando a fotografia de uma montanha

Gere suas próprias imagens

Quando o servidor estiver no ar, ele vai precisar de um cabeçalho para o README, um fundo para o diagrama e um cartão para redes sociais. O PicassoIA Image transforma um prompt simples em uma imagem finalizada em segundos, com sete proporções, de 1:1 a 16:9, uma seed bloqueável para resultados reproduzíveis, saída em JPG, PNG ou WebP e até duas variações por execução. Ele é descrito como ilimitado, sem teto por imagem, então você pode iterar à vontade. Quando uma imagem fixa merecer movimento, o PicassoIA Video a anima em um clipe curto.

Experimente este prompt: a quiet loft office at dusk, laptop open on an oak desk, soft window light, 35mm photograph, film grain. Mude um detalhe, trave a seed, gere de novo e compare as duas. Abra a PicassoIA, rode seu primeiro prompt e veja como fica sua próxima imagem. Todos os modelos estão em picassoia.com/en/all-models, então há bastante coisa para experimentar.

Pessoa em uma mesa junto à janela de um espaço de coworking silencioso ao entardecer

Compartilhe este artigo

Escolha seu idioma