Um servidor MCP iniciado por stdio funciona bem quando o próprio cliente cria o processo. Ele deixa de ser suficiente quando vários clientes precisam chamar as mesmas ferramentas por uma URL, quando o servidor deve viver atrás de um proxy ou quando a equipe quer publicar uma integração sem distribuir um pacote.

Para esse caso, hospede o servidor por Streamable HTTP. O MCP TypeScript SDK atual separa o servidor da camada de transporte: stdio atende integrações locais, enquanto Streamable HTTP atende servidores remotos (MCP TypeScript SDK, "Serve over HTTP", consultado em 2026-08-10). A aplicação continua registrando as mesmas tools, mas o runtime passa a controlar HTTP, autenticação e encerramento.

Este tutorial mostra o desenho mínimo, os limites de segurança e uma rotina de verificação. O exemplo usa os pacotes atuais do SDK v2. Se o seu projeto ainda usa @modelcontextprotocol/sdk, compare os imports com o guia de migração do SDK antes de copiar o código.

Se você ainda precisa criar o servidor local, comece pelo servidor MCP em TypeScript para agentes de código. Aqui, o problema é outro: colocar a mesma superfície atrás de uma rede sem confundir transporte com autorização.

Diagrama mostra um servidor MCP em TypeScript passando por endpoint HTTP, autenticação e verificação de ferramentas.

Resumo prático

  • Use stdio quando o host inicia um processo local; use Streamable HTTP para um servidor remoto.
  • Comece sem estado quando cada requisição puder ser independente.
  • Valide Host, Origin e autenticação antes de deixar uma tool executar.
  • Teste tools/list e uma chamada real com um cliente separado do processo do servidor.

Quando um servidor MCP deve sair do processo local?

Um servidor remoto faz sentido quando o cliente não deve gerenciar o processo, quando vários consumidores compartilham a integração ou quando a ferramenta precisa acessar uma rede e credenciais que não devem ir para cada workspace. O servidor local continua sendo melhor para ferramentas privadas, rápidas e acopladas ao diretório do desenvolvedor.

O protocolo não transforma uma integração local em remota apenas por trocar a URL. O guia de transporte Streamable HTTP define um endpoint HTTP único que recebe mensagens JSON-RPC por POST. Cada requisição pode receber um objeto JSON ou uma resposta SSE. Isso cria uma fronteira de rede que precisa de autenticação, limites e observabilidade.

Use esta decisão simples:

Situação Transporte inicial Motivo
IDE ou CLI inicia o servidor no mesmo computador stdio O processo e as credenciais ficam no host local.
Vários clientes acessam ferramentas por URL Streamable HTTP O servidor tem um endpoint compartilhado.
Ferramentas são stateless e escalam atrás de proxy Streamable HTTP sem estado Cada requisição pode cair em qualquer instância.
O fluxo exige sessões, retomada ou notificações Streamable HTTP com estado O servidor precisa persistir ou rotear o estado.

Não escolha HTTP apenas porque ele parece mais moderno. Se o único cliente é um processo local e a ferramenta lê arquivos desse workspace, stdio reduz superfície operacional. O ganho do endpoint remoto precisa compensar TLS, identidade, limites de rede e monitoramento.

O que o Streamable HTTP muda no contrato do MCP?

Streamable HTTP coloca o servidor como processo independente e usa POST para cada mensagem JSON-RPC. A especificação atual exige que o cliente anuncie suporte a application/json e text/event-stream, e o servidor escolhe uma resposta JSON ou um stream SSE por requisição (MCP, "Streamable HTTP", consultado em 2026-08-10). O cliente não conversa com stdin e stdout.

Esse detalhe muda os testes. Você precisa verificar método HTTP, Content-Type, headers de versão, resposta JSON-RPC, cancelamento e comportamento quando um proxy encerra a conexão. Um teste que chama apenas o handler da tool não prova que o endpoint remoto obedece ao protocolo.

Também não trate a versão do protocolo como detalhe fixo para sempre. A revisão 2026-07-28 mudou o comportamento de sessões e de streams em relação a revisões anteriores. O SDK atual oferece createMcpHandler para construir uma instância por requisição no caminho moderno e mantém opções de compatibilidade para clientes legados (SDK, "Supporting protocol revision 2026-07-28", consultado em 2026-08-10).

Na prática, registre a versão dos pacotes, o protocolo que você aceita e os clientes testados. Não escreva um fallback para SSE antigo sem um cliente que realmente precise dele. Transportes legados aumentam estados e caminhos de falha que você também terá de manter.

Como montar um servidor MCP remoto em TypeScript?

O SDK atual reduz o caminho HTTP a três peças: uma fábrica que cria o McpServer, um handler que transforma cada requisição em uma execução e um adaptador para o runtime Node. O exemplo abaixo é ilustrativo, mas usa os imports e a forma de composição apresentados nos exemplos oficiais do SDK.

Instale os pacotes e fixe as versões no lockfile do seu projeto:

npm install @modelcontextprotocol/server @modelcontextprotocol/node zod
npm install --save-dev typescript tsx @types/node

Crie uma factory pequena. Ela deve registrar ferramentas estreitas e não deve ler credenciais diretamente do prompt. O contexto autenticado entra pela camada HTTP, não por uma string que o modelo pode alterar.

// src/mcp-server.ts
import { McpServer } from "@modelcontextprotocol/server";
import * as z from "zod/v4";

export function buildServer(): McpServer {
  const server = new McpServer({
    name: "remote-notes",
    version: "1.0.0",
  });

  server.registerTool(
    "find_note",
    {
      description: "Find a note by its public identifier",
      inputSchema: z.object({
        id: z.string().min(1).max(80),
      }),
    },
    async ({ id }) => ({
      content: [{ type: "text", text: `Requested note: ${id}` }],
    }),
  );

  return server;
}

A tool real deve consultar uma fonte autorizada e validar também o resultado. O retorno acima é deliberadamente pequeno. Ele deixa o contrato visível sem fingir que uma API de notas, banco ou sistema de tickets existe neste exemplo.

Agora monte uma entrada HTTP sem estado. createMcpHandler cria o servidor a partir da factory por requisição, e toNodeHandler adapta o handler para node:http. O código também aplica as proteções de Host e Origin oferecidas pelo pacote Node.

// src/http.ts
import { createServer } from "node:http";
import {
  localhostHostValidation,
  localhostOriginValidation,
  toNodeHandler,
} from "@modelcontextprotocol/node";
import { createMcpHandler } from "@modelcontextprotocol/server";
import { buildServer } from "./mcp-server.js";

const handler = toNodeHandler(createMcpHandler(buildServer));
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();

const httpServer = createServer((req, res) => {
  if (!validateHost(req, res) || !validateOrigin(req, res)) return;
  void handler(req, res);
});

httpServer.listen(3000, "127.0.0.1", () => {
  console.error("MCP endpoint listening on http://127.0.0.1:3000/mcp");
});

process.on("SIGTERM", () => {
  httpServer.close(() => process.exit(0));
});

Esse bind em loopback é apropriado para desenvolvimento local. Para produção, use um framework HTTP ou um adaptador de runtime que tenha uma allowlist de hosts, escute no endereço definido pelo ambiente e coloque autenticação antes do handler. Não copie 127.0.0.1 para um container que precisa receber tráfego externo.

O SDK mostra a mesma separação no exemplo de gateway: createMcpHandler cuida do protocolo e toNodeHandler cuida da adaptação para Node (SDK, "Gateway example", consultado em 2026-08-10). Essa divisão facilita testar o handler sem abrir uma porta e testar o processo completo com HTTP.

Como proteger o endpoint antes da primeira tool call?

Proteja o endpoint em camadas. O servidor MCP deve rejeitar uma origem ou host inválido, o proxy deve terminar TLS e a camada de identidade deve validar o token antes de entregar a requisição ao handler. A autorização tutorial do MCP recomenda OAuth para servidores HTTP hospedados remotamente, enquanto servidores stdio podem usar credenciais do ambiente local (MCP, "Understanding Authorization", consultado em 2026-08-10).

Em um serviço Node exposto publicamente, o formato é este. O verifier é um adaptador para o seu provedor de identidade. Não coloque um token real no repositório nem trate este recorte como uma implementação completa de OAuth.

import { requireBearerAuth } from "@modelcontextprotocol/server";
import { createMcpExpressApp } from "@modelcontextprotocol/express";
import { toNodeHandler } from "@modelcontextprotocol/node";
import { createMcpHandler } from "@modelcontextprotocol/server";
import { buildServer } from "./mcp-server.js";

const app = createMcpExpressApp({
  host: "0.0.0.0",
  allowedHosts: ["mcp.example.com"],
});

const verifier = createVerifierFromYourIdentityProvider();
const auth = requireBearerAuth({
  verifier,
  requiredScopes: ["mcp:tools"],
});

const handler = toNodeHandler(createMcpHandler(buildServer));

app.all("/mcp", auth, (req, res) => {
  void handler(req, res, req.body);
});

app.listen(8080, () => {
  console.error("MCP endpoint listening on port 8080");
});

A documentação do SDK descreve requireBearerAuth, requiredScopes e o desafio WWW-Authenticate como parte dessa fronteira (SDK, "Authorization", consultado em 2026-08-10). A tool ainda deve aplicar autorização de domínio: um token válido não significa que pode ler qualquer nota, banco ou repositório.

No exemplo público, troque mcp.example.com pelo host real e mantenha uma allowlist explícita. A especificação também exige validação de Origin para evitar DNS rebinding. Se você montar o servidor com node:http sem uma factory de framework, implemente os guards correspondentes antes do handler.

Não libere ferramentas por nome apenas porque o servidor passou na autenticação. Separe leitura de mutação, limite o escopo por usuário e registre qual identidade chamou qual ferramenta. O artigo sobre allowlist de MCP para agentes de código cobre a política de ferramentas que fica acima da autenticação.

Estado sem sessão ou sessão persistente?

Comece sem estado quando cada requisição puder reconstruir o servidor e acessar o armazenamento por conta própria. O guia de escalabilidade do SDK explica que createMcpHandler cria uma instância por requisição e, por isso, servidores stateless podem escalar atrás de um balanceador sem afinidade de sessão (SDK, "Sessions, state, and scaling", consultado em 2026-08-10).

Esse modelo funciona bem para tools que recebem todos os argumentos, consultam uma fonte externa e devolvem um resultado. O estado da aplicação fica em banco, cache ou fila, não no objeto McpServer criado para uma única requisição.

Use estado persistente quando o fluxo precisa retomar um stream, acompanhar notificações, manter uma assinatura ou executar uma operação longa entre chamadas. Não deixe um Map em memória ser a única cópia de uma sessão se o serviço pode ter duas réplicas ou reiniciar.

Há três opções práticas:

  1. Stateless: o handler cria o servidor por requisição e qualquer instância pode atender qualquer chamada.
  2. Estado externo: sessões, eventos ou resultados ficam num armazenamento compartilhado, e o balanceador pode enviar a requisição a qualquer nó.
  3. Afinidade de sessão: o estado fica na memória de um nó e o proxy mantém o cliente preso a ele. É simples, mas torna falha e escala mais difíceis.

O guia de escalabilidade do SDK também descreve um barramento compartilhado para notificações entre nós. Use essa opção somente quando a funcionalidade realmente exigir comunicação entre instâncias. Para uma tool de consulta simples, não compre esse custo antes de precisar dele.

O corte mais útil não é "HTTP com sessão" versus "HTTP sem sessão". É separar estado necessário para o domínio de estado acidental do transporte. Uma tarefa de longa duração precisa de persistência porque o trabalho existe. Uma sessão em memória só porque o exemplo copiou um mapa do SDK é uma dívida operacional.

Como verificar um servidor MCP remoto sem confiar no próprio processo?

Verifique o servidor em camadas: processo inicia, endpoint responde, cliente descobre tools, chamada válida produz resultado, chamada inválida é rejeitada e uma requisição sem autorização não executa efeito. O MCP Inspector é útil para depuração interativa, mas o teste que protege o CI deve iniciar ou chamar o endpoint como um cliente real.

Durante o desenvolvimento, rode o processo e abra o Inspector apontando para o endpoint:

npx @modelcontextprotocol/inspector http://127.0.0.1:3000/mcp

Confirme a sequência abaixo no painel e nos logs do servidor:

  1. O cliente completa initialize e negocia uma versão que o servidor aceita.
  2. tools/list mostra find_note com o schema publicado.
  3. Uma chamada com um identificador válido retorna o conteúdo esperado.
  4. Um identificador vazio falha na validação antes da consulta real.
  5. Uma origem, host ou credencial inválida falha antes do handler da tool.

Não use apenas uma resposta 200 como prova. A camada HTTP pode responder corretamente enquanto o contrato da tool está errado. O tutorial de testes de contrato para servidores MCP em TypeScript mostra como automatizar descoberta, chamada válida, entrada inválida e erro de transporte para um servidor local. Para este spoke, troque stdio pela URL HTTP e acrescente os casos de autenticação e proxy.

Um teste de fumaça simples pode chamar o endpoint por uma rede de teste, nunca com uma credencial de produção. Capture status, Content-Type, corpo JSON-RPC, tempo de resposta e logs redigidos. Se o endpoint usa SSE, confirme também que o proxy não está armazenando a resposta em buffer.

Não há um resultado de produção ou benchmark próprio neste artigo. A prova proposta é deliberadamente pequena: um cliente externo descobre a tool, chama um caso válido, força um caso inválido e confirma que a barreira de identidade interrompe a execução. Isso é mais honesto e mais útil que chamar o handler diretamente e declarar que o servidor remoto está pronto.

Quais falhas aparecem primeiro em produção?

Os primeiros erros costumam estar na fronteira, não no raciocínio do modelo. Um endpoint em caminho errado retorna 404; um token ausente retorna 401; um escopo insuficiente retorna 403; uma origem rejeitada pode retornar 403; um cliente incompatível falha durante a negociação; e um proxy mal configurado segura ou encerra um stream. Registre o erro sem gravar tokens, prompts ou dados de ferramentas.

Sintoma Causa provável Verificação
404 ao conectar URL não aponta para /mcp ou a rota não foi montada Confirme a rota final e o método POST.
401 antes de tools/list Token ausente, expirado ou issuer incorreto Inspecione o desafio WWW-Authenticate e use um token de teste.
403 em localhost Host ou Origin não passou no guard Teste a origem explícita e não escute em todas as interfaces sem allowlist.
Nenhuma tool aparece Falha de negociação ou factory não registrou a tool Leia a resposta de initialize e o log de construção.
Funciona numa réplica e falha em outra Estado de sessão apenas em memória Use stateless, armazenamento compartilhado ou afinidade consciente.
Tool demora e a conexão cai Timeout do proxy, servidor ou cliente Diferencie cancelamento de erro da ferramenta e meça cada camada.

O ponto mais perigoso é repetir uma mutação depois de uma desconexão. A queda da conexão não prova que o servidor não aplicou o efeito. Para escrita, use idempotency key, consulta de confirmação ou operação de compensação. Para leitura, uma repetição costuma ser mais simples, mas ainda precisa de limite.

Outro limite: Streamable HTTP não autoriza automaticamente uma ferramenta. O transporte entrega mensagens; a autenticação identifica o caller; a política da aplicação decide se aquele caller pode executar a ação. Mantenha essas decisões em camadas separadas para que um ajuste de transporte não amplie a agência do agente por acidente.

Checklist antes de publicar o endpoint

Antes de dar a URL a um cliente, percorra esta lista:

  • [ ] O servidor usa Streamable HTTP para o caso remoto e não expõe stdio na rede.
  • [ ] O caminho /mcp está documentado e testado por um cliente externo.
  • [ ] TLS termina num proxy ou no processo, conforme o ambiente.
  • [ ] Host e Origin têm validação explícita, sem * como configuração permanente.
  • [ ] A autenticação valida issuer, audience, expiração e escopo.
  • [ ] Cada tool aplica autorização de domínio, não apenas autenticação global.
  • [ ] O modo stateless ou stateful foi escolhido a partir do fluxo real.
  • [ ] Logs redigem tokens, prompts, dados pessoais e resultados sensíveis.
  • [ ] O CI testa descoberta, schema, sucesso, rejeição, transporte e encerramento.
  • [ ] Mutations têm idempotência ou uma confirmação de efeito antes do retry.

Se você precisar publicar no Cloud Run, trate o serviço como o processo HTTP que recebe o endpoint MCP. O artigo sobre serviço ou worker pool para agentes no Cloud Run ajuda a decidir o modelo de execução. Ele não substitui os testes de protocolo, autenticação e escala deste artigo.

Conclusão

Um MCP remoto confiável é um servidor HTTP comum apenas na aparência. O transporte define como as mensagens chegam, mas Host, Origin, identidade, permissão, estado e verificação definem se a integração pode operar com segurança. Comece sem estado quando o domínio permitir e adicione persistência somente para um requisito concreto.

Em fluxos longos de Claude Code e Codex, uso o RemoteCode como ferramenta do autor para continuar o trabalho entre sessões. Ele ajuda a manter continuidade, mas não substitui autenticação, testes de contrato ou a decisão sobre quais tools um caller pode executar.

Perguntas frequentes

Posso usar o mesmo servidor MCP em stdio e Streamable HTTP?

Sim. Registre tools numa factory compartilhada e escolha uma entrada de transporte por ambiente. stdio serve o processo local; createMcpHandler ou um transporte HTTP serve requisições remotas. Teste os dois caminhos porque eles têm ciclo de vida, autenticação e tratamento de encerramento diferentes.

Streamable HTTP precisa de uma sessão em memória?

Não. O SDK atual permite um handler sem estado por requisição. Use sessão ou estado externo quando o fluxo exigir retomada, notificações ou trabalho que atravesse chamadas. Um Map local é aceitável como exemplo, mas não como única fonte de verdade num serviço que escala ou reinicia.

Posso proteger o MCP apenas com uma API key?

Pode existir uma API key em integrações simples, mas ela precisa ser validada antes do handler e limitada ao menor conjunto de tools possível. Para dados por usuário, consentimento, auditoria ou escopos distintos, use uma solução de identidade adequada e valide issuer, audience, expiração e permissão no serviço.

O MCP Inspector substitui testes de contrato?

Não. O Inspector acelera a investigação manual de conexão, schemas e chamadas. O CI deve repetir esses casos com um cliente automatizado e falhar quando a superfície publicada mudar. O teste deve conversar pelo mesmo transporte usado em produção, não chamar apenas uma função interna.

Preciso aceitar clientes MCP antigos?

Somente se houver um cliente que você precise atender. O SDK atual documenta compatibilidade entre revisões, mas cada caminho extra aumenta a matriz de testes. Declare a versão aceita, registre clientes testados e remova o fallback legado quando ele deixar de ter uma função real.

Fontes consultadas