Capa do artigo: Model Context Protocol, o padrao que conecta agentes a ferramentas e dados

Model Context Protocol (MCP): o padrão que conecta agentes de IA a ferramentas e dados

Todo time que coloca um agente em produção passa pelo mesmo funil de frustração. O modelo é excelente, o prompt está afiado, mas o agente só é útil quando toca o mundo real — o Jira, o data lake, o ERP, aquele serviço interno que ninguém documentou. E aí você descobre que 80% do esforço não está na IA: está em escrever, de novo, um conector diferente para cada ferramenta, em cada framework, com um formato de tool calling ligeiramente diferente. Troque de modelo e reescreva tudo. Troque de framework e reescreva tudo outra vez. O Model Context Protocol (MCP) existe para matar exatamente esse retrabalho.

Atualização 2026 Em 28 de julho de 2026 o MCP publicou a revisão 2026-07-28 — a maior mudança desde o lançamento: nove mudanças maiores, doze menores e seis recursos formalmente depreciados. O protocolo virou stateless, o handshake de initialize e o header Mcp-Session-Id foram removidos, o endpoint GET deu lugar a subscriptions/listen, a resumabilidade do SSE acabou, as extensões viraram cidadãs de primeira classe e a autorização se alinhou ao OAuth 2.x. Tem breaking change aqui. Este artigo cobre o antes e o depois — e traz um checklist de migração.

O que é o Model Context Protocol?

O MCP é um protocolo aberto que padroniza como um agente de IA descobre e usa capacidades externas — ferramentas, dados e fluxos prontos. A analogia que pegou é a do USB-C da IA: em vez de um cabo proprietário para cada aparelho, um conector só, e qualquer coisa que fale o mesmo protocolo se pluga.

Na prática, ele define três papéis:

  • Host — a aplicação onde o agente vive (seu app, um IDE, um copiloto).
  • Cliente MCP — o componente dentro do host que fala o protocolo, tipicamente um por servidor conectado.
  • Servidor MCP — o processo que expõe capacidades. Ele é quem sabe conversar com o Jira, o Databricks ou a sua API interna.

E o servidor expõe três primitivas:

Primitiva O que é Quem controla
Tools Funções que o modelo pode invocar (buscar, criar, calcular) O modelo decide chamar
Resources Dados legíveis endereçados por URI (arquivos, registros, config) A aplicação decide anexar
Prompts Fluxos e templates prontos, expostos como comandos O usuário decide acionar

A distinção importa mais do que parece: tool é ação com efeito colateral, resource é leitura. Confundir os dois é o erro de design número um em servidor MCP — e é o que transforma um agente prestativo num agente perigoso.

O problema que resolve

Sem um padrão, integrar M agentes a N ferramentas é um problema M × N: cada dupla exige um conector próprio, com autenticação, formato de schema e tratamento de erro próprios. Com MCP, vira M + N: cada agente fala o protocolo uma vez, cada ferramenta expõe o protocolo uma vez, e a combinação passa a ser gratuita.

É a mesma economia que o ODBC trouxe para bancos de dados nos anos 90 e que o Language Server Protocol trouxe para editores de código. Ninguém escreve um driver por par de (aplicação, banco) desde então — e a razão é exatamente essa.

Três dores concretas desaparecem:

  1. Acoplamento a framework — o servidor MCP que você escreve hoje serve o agente em LangGraph, no Agent Framework e no copiloto que ainda vai existir.
  2. Descoberta em tempo de execução — o agente pergunta ao servidor quais ferramentas existem e recebe o schema JSON de cada uma. Você adiciona uma capacidade sem reimplantar o agente.
  3. Superfície de segurança concentrada — autenticação, escopo e auditoria ficam em um lugar (o servidor), não espalhados em vinte conectores caseiros.
Diagrama do Model Context Protocol em duas bandas: à esquerda o servidor MCP com tools, resources, prompts e extensions; à direita o fluxo de uma chamada — host e agente, cliente MCP, POST /mcp autocontido e resultado — com um trilho de autorização OAuth, escopo mínimo, aprovação humana e rastreamento aplicado a cada etapa.
O que você expõe uma vez (esquerda) sustenta o que flui a cada chamada (direita) — e, desde a revisão 2026-07-28, sem sessão no protocolo.

Como funciona — passo a passo

  1. Exponha as capacidades — no servidor, declare cada tool com nome, descrição e um schema de entrada. A descrição não é enfeite: é ela que o modelo lê para decidir se e quando chamar.
  2. Conecte o cliente — o host abre uma conexão com o servidor por um dos dois transportes: stdio (processo local, ideal para desktop e IDE) ou Streamable HTTP (servidor remoto, um único endpoint POST /mcp).
  3. Descubra — o cliente chama tools/list e recebe o catálogo com os schemas. A partir de 2026-07-28, a resposta traz ttlMs e cacheScope, então o cliente sabe por quanto tempo pode cachear esse catálogo em vez de perguntar de novo a cada turno.
  4. Deixe o modelo escolher — o agente recebe as ferramentas disponíveis junto com a tarefa e decide qual invocar, com quais argumentos.
  5. Execute com aprovação — o cliente chama tools/call. Ações destrutivas ou irreversíveis passam por confirmação humana antes de sair.
  6. Devolva o resultado ao contexto — a saída volta como conteúdo (e, opcionalmente, structuredContent validado por schema) e o modelo segue o raciocínio.
        [ Agente ]  "qual o saldo do contrato 4471?"
              |
              v
      +----------------+       tools/list  (cacheável: ttlMs)
      |  Cliente MCP   |  <------------------------------+
      +----------------+                                 |
              |  POST /mcp   Mcp-Method: tools/call       |
              |              Mcp-Name: get_contract       |
              v                                           |
      +----------------+                          +--------------+
      | Servidor MCP   | -----------------------> |  ERP / Lake  |
      +----------------+       SQL / REST         +--------------+
              |
              v
      [ resultado ]  ou  [ input_required -> confirma? ]

O que mudou na revisão 2026-07-28

Se você já tem um servidor MCP rodando, esta é a seção que importa. A revisão anterior era a 2025-11-25, e o salto é grande: nove mudanças maiores, doze menores, seis recursos formalmente depreciados e — pela primeira vez — uma política pública de ciclo de vida para recursos do protocolo.

O fio condutor é um só: tirar estado do transporte. Quase tudo o mais decorre disso.

O protocolo virou stateless

Antes, toda conversa começava com um handshake initialize, seguido de notifications/initialized, e o servidor devolvia um Mcp-Session-Id que grudava o cliente naquela instância. Escalar horizontalmente exigia sticky sessions e um armazenamento de sessão compartilhado.

Agora initialize e Mcp-Session-Id não existem mais. Cada requisição carrega o que precisa em _meta:

Chave em _meta O que carrega
io.modelcontextprotocol/protocolVersion versão do protocolo daquela requisição
io.modelcontextprotocol/clientCapabilities capacidades do cliente
io.modelcontextprotocol/clientInfo identificação do cliente
io.modelcontextprotocol/serverInfo identificação do servidor (no _meta do resultado)

Incompatibilidade de versão virou um erro explícito — UnsupportedProtocolVersionError — em vez de um handshake que falha no escuro. E os endpoints de listagem (tools/list, resources/list, prompts/list) não variam mais por conexão, o que finalmente torna o cache do lado do cliente confiável.

Para escolher a versão antes de qualquer chamada existe o novo server/discover, que todo servidor é obrigado a implementar: ele anuncia versões suportadas, capacidades e identidade. No stdio, também serve de sonda de compatibilidade com servidores antigos.

O efeito prático é grande: qualquer requisição pode cair em qualquer instância. Um round-robin comum resolve, e servidor MCP passa a caber bem em plataforma serverless com escala a zero — Azure Functions no plano de consumo, por exemplo — que antes brigava com afinidade de sessão.

Protocolo sem estado não significa aplicação sem estado. Se o seu servidor precisa carregar contexto entre chamadas, a spec agora prescreve o padrão explícito: o servidor cunha um identificador (basket_id, job_id), devolve como resultado, e o modelo o repassa como argumento comum na chamada seguinte. O estado fica visível para o modelo em vez de escondido no transporte — o que é mais poderoso e muito mais fácil de depurar.

O endpoint GET saiu de cena: agora é subscriptions/listen

O endpoint HTTP GET e o par resources/subscribe / resources/unsubscribe foram substituídos por um único método: subscriptions/listen, um POST de resposta longa em que o cliente opta explicitamente pelos tipos de notificação que quer — toolsListChanged, promptsListChanged, resourcesListChanged e resourceSubscriptions. O servidor confirma a inscrição e carimba cada notificação com io.modelcontextprotocol/subscriptionId.

Notificações de escopo de requisição (notifications/progress, notifications/message) continuam viajando no stream de resposta da própria requisição — não nesse canal. É uma separação limpa entre “me avise quando o catálogo mudar” e “me conte como vai esta chamada”.

Stream quebrado virou requisição perdida

A resumabilidade do SSE saiu: não existe mais Last-Event-ID nem reentrega de mensagens. Se o stream de resposta cair, a requisição em voo se perde, e o cliente precisa reemiti-la com um novo id.

Isso muda o desenho de ferramentas demoradas, e de duas formas bem concretas:

  • Idempotência deixou de ser boa prática e virou requisito. Reemissão vai acontecer; sua tool de escrita precisa tolerar a segunda chamada sem duplicar efeito.
  • Trabalho longo pertence à extensão Tasks, não a uma chamada síncrona segurando um stream aberto por minutos.

Perguntar ao usuário no meio da chamada mudou de forma

Elicitação, roots/list e sampling/createMessage eram requisições iniciadas pelo servidor. Esse mecanismo saiu inteiro e deu lugar ao padrão Multi Round-Trip Requests (MRTR).

Todo resultado passou a ter um campo obrigatório, o resultType:

  • "complete" — resultado normal;
  • "input_required" — o servidor devolve inputRequests com o que precisa saber, mais um requestState opaco.

O cliente coleta as respostas e reemite a chamada original com inputResponses e o mesmo requestState. Como tudo o que o servidor precisa está no payload, a retentativa pode cair em qualquer instância. Para compatibilidade, resultado de servidor antigo sem resultType deve ser tratado como "complete".

Sumiram junto a notificação notifications/elicitation/complete e o campo elicitationId: quem precisa correlacionar uma interação fora de banda entre tentativas codifica o próprio identificador no requestState.

E uma regra de etiqueta virou obrigatória: o servidor só pode iniciar requisições enquanto processa uma chamada do cliente. O usuário nunca é interrompido do nada.

O tráfego ficou operável

  • Headers Mcp-Method e Mcp-Name passaram a ser obrigatórios no POST do Streamable HTTP. Seu gateway roteia, limita e mede por operação sem abrir o corpo da requisição — em API Management, é a diferença entre uma política de uma linha e um script.
  • x-mcp-header permite promover parâmetros de tool a cabeçalhos HTTP customizados.
  • ttlMs e cacheScope passaram a ser obrigatórios nos resultados de tools/list, prompts/list, resources/list, resources/read e resources/templates/list, pela nova interface CacheableResult. ttlMs é dica de frescor em milissegundos; cacheScope ("public" ou "private") diz se um intermediário compartilhado pode guardar aquele resultado.
  • Ordem determinística em tools/list virou recomendação — e não é firula: lista estável melhora o hit rate do cache de prompt do modelo, e isso aparece na fatura.
  • W3C Trace Context (traceparent, tracestate, baggage) padronizado em _meta — o trace atravessa host, SDK, servidor MCP e o que vier depois, e aparece como uma árvore só no seu backend OpenTelemetry.

Métodos que simplesmente sumiram

ping, logging/setLevel e notifications/roots/list_changed foram removidos. O nível de log agora é por requisição, via io.modelcontextprotocol/logLevel em _meta — e o servidor não pode emitir notifications/message para requisições que não pediram log. Menos ruído no stream, menos byte de saída, menos custo.

Extensões viraram cidadãs de primeira classe

ClientCapabilities e ServerCapabilities ganharam o campo extensions. Extensões são identificadas por reverse-DNS, negociadas por capacidade e versionadas fora da spec — é assim que o MCP pretende evoluir sem inchar o núcleo. Duas já são oficiais:

  • MCP Apps — o servidor entrega interfaces HTML renderizadas pelo host em iframe isolado.
  • Tasks (io.modelcontextprotocol/tasks) — saiu do núcleo experimental e virou extensão redesenhada: o bloqueante tasks/result deu lugar a polling com tasks/get; entrou tasks/update, para o cliente mandar informação durante a execução; tasks/list saiu; e o servidor pode devolver um handle de tarefa sem opt-in por requisição.

Autorização: mais perto do OAuth do mundo real

  • O servidor de autorização deve incluir o parâmetro iss na resposta (RFC 9207), e o cliente MCP deve validá-lo contra o emissor registrado antes de trocar o código — fechando a porta para ataques de mix-up.
  • O cliente precisa declarar application_type no registro dinâmico, evitando conflito de redirect URI com OpenID Connect.
  • Credenciais são atadas ao emissor que as criou: guarde-as com chave por issuer, nunca as reutilize com outro servidor de autorização e registre de novo quando ele mudar.
  • E a mudança de rumo: o Dynamic Client Registration (RFC 7591) foi depreciado como mecanismo de registro, em favor dos Client ID Metadata Documents. DCR continua funcionando por compatibilidade, mas deixou de ser o caminho recomendado.

Depreciação agora tem regra escrita

O MCP adotou uma política de ciclo de vida: todo recurso está em Active, Deprecated ou Removed, com janela mínima de doze meses entre depreciar e remover, e um registro público do que está depreciado. É a diferença entre “vai quebrar algum dia” e “você tem um ano e uma data”.

Entraram na lista: Roots (use parâmetros de tool, URIs de resource ou configuração do servidor), Sampling (chame a API do provedor de LLM direto), Logging (stderr no stdio, ou OpenTelemetry), o antigo transporte HTTP+SSE (migre para Streamable HTTP), os valores "thisServer" / "allServers" de includeContext e o DCR, já citado acima.

Detalhes que quebram cliente em silêncio

  • inputSchema e outputSchema agora aceitam JSON Schema 2020-12 completo (oneOf, $ref, $defs), e structuredContent aceita qualquer valor JSON. Ganhou-se expressividade — e a obrigação de resolver $ref.
  • Recurso inexistente mudou de -32002 para o padrão JSON-RPC -32602 (Invalid Params).
  • A faixa de erro foi particionada: -32000 a -32019 fica com as implementações, e -32020 a -32099 é reservada à spec. Os códigos novos foram renumerados: HeaderMismatch -32001-32020, MissingRequiredClientCapability -32003-32021, UnsupportedProtocolVersion -32004-32022.

Checklist de migração

O que você tem hoje O que fazer
initialize + Mcp-Session-Id Remover; enviar versão e capacidades em _meta a cada requisição
Sticky session no balanceador Desligar; qualquer instância atende qualquer requisição
Estado guardado por sessão Cunhar um handle explícito e devolvê-lo como argumento de tool
Endpoint GET, resources/subscribe Migrar para subscriptions/listen, com opt-in por tipo
Reconexão via Last-Event-ID Reemitir a requisição com novo id; tornar as tools idempotentes
roots/list, sampling/createMessage, elicitação Adotar MRTR: resultType, inputRequests, requestState
logging/setLevel e ping Log por requisição em _meta; health check no seu próprio endpoint HTTP
Resultados de listagem sem cache Preencher ttlMs e cacheScope; ordenar tools/list de forma estável
DCR (RFC 7591) Avaliar Client ID Metadata Documents
Tratamento de -32002 Passar a tratar -32602

Nada disso é opcional para quem opera servidor remoto. A boa notícia é que quase toda mudança troca complexidade de infraestrutura por explicitação no payload — e payload explícito é o que você consegue logar, cachear, versionar e auditar.

Construindo e hospedando um servidor MCP no Azure

Um servidor mínimo em Python cabe em vinte linhas. O que separa o protótipo da produção é onde ele roda e quem pode chamá-lo.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("contratos")

@mcp.tool()
def buscar_contrato(numero: str) -> dict:
    """Retorna dados cadastrais de um contrato pelo número.
    Use quando o usuário citar um número de contrato."""
    return repositorio.get(numero)          # leitura, sem efeito colateral

@mcp.tool()
def encerrar_contrato(numero: str, motivo: str) -> dict:
    """Encerra um contrato. AÇÃO IRREVERSÍVEL — exige confirmação."""
    return repositorio.encerrar(numero, motivo)

@mcp.resource("contrato://{numero}/clausulas")
def clausulas(numero: str) -> str:
    """Texto das cláusulas — dado de leitura, anexado pela aplicação."""
    return repositorio.clausulas(numero)

Repare no essencial: a docstring é o contrato com o modelo. Ela diz o que a ferramenta faz e quando usá-la. Descrição vaga é a causa raiz da maioria dos agentes que chamam a ferramenta errada.

Para colocar no ar, você tem quatro caminhos no Azure, do mais simples ao mais controlado:

  1. Azure Functions — servidor MCP remoto com scale-to-zero. É o melhor custo-benefício para servidores de uso intermitente e integra com managed identity para acessar recursos sem segredo em código.
  2. Azure Container Apps — quando você quer o contêiner do seu jeito. Com ingress interno e sub-rede dedicada, o servidor fica acessível só de dentro da VNet.
  3. Azure API Management — o gateway de IA na frente do servidor. É onde você aplica autenticação, rate limiting, cotas por consumidor, políticas de conteúdo e telemetria. Com os headers Mcp-Method e Mcp-Name, dá para escrever política por operação — bloquear tools/call de encerrar_contrato para um consumidor específico, por exemplo.
  4. Foundry Agent Service — do outro lado do balcão: aqui você conecta o agente a servidores MCP (seus ou de terceiros) sem escrever o encanamento do cliente.

Some a isso o Azure MCP Server, que expõe os próprios recursos do Azure como ferramentas — útil para agentes de operação e diagnóstico.

Boas práticas para produção

  • Trate a descrição da tool como código de produção. É o que o modelo lê para decidir. Seja específico sobre quando usar e quando não usar.
  • Separe leitura de escrita e exija confirmação humana em tudo que for irreversível. input_required existe para isso.
  • Escopo mínimo, por servidor. Um token por servidor MCP, com o menor escopo possível. Token largo é o combustível do ataque de confused deputy, em que o servidor age com mais privilégio do que o usuário tem.
  • Trate saída de ferramenta como entrada não confiável. Tool poisoning — instruções maliciosas escondidas na descrição ou no retorno de uma ferramenta — é hoje o vetor mais explorado. Descrição de tool de terceiro é conteúdo hostil até prova em contrário.
  • Allowlist e revisão de servidores de terceiros. Um servidor aprovado pode mudar de comportamento numa atualização (rug pull). Fixe versão e revise mudanças de schema.
  • Valide o header Origin e nunca exponha servidor local em 0.0.0.0. Servidor stdio local escuta em localhost — e só.
  • Instrumente com OpenTelemetry desde o dia um. Com Trace Context padronizado, você consegue responder “qual ferramenta o agente chamou, com quais argumentos e quanto custou” — que é exatamente a pergunta que a auditoria vai fazer.
  • Cacheie tools/list respeitando o ttlMs. Listar ferramentas a cada turno é desperdício de token e de latência.

Perguntas frequentes (FAQ)

MCP substitui o function calling do modelo?

Não. São camadas diferentes. Function calling é a habilidade do modelo de emitir uma chamada estruturada; MCP é o protocolo que padroniza como essa ferramenta é descoberta, descrita e executada por um processo externo. Na prática o MCP alimenta o function calling com um catálogo padronizado.

Preciso migrar meu servidor para a revisão 2026-07-28 agora?

Se ele é remoto e você quer escalar, sim — vale o esforço, porque some a necessidade de sticky session e de armazenamento de sessão compartilhado. Como há breaking changes (fim do initialize, do Mcp-Session-Id e da resumabilidade do SSE, novo subscriptions/listen e mudança no fluxo de elicitação), planeje uma janela, siga o checklist de migração e valide contra a suíte de conformidade. Servidores locais em stdio sentem bem menos impacto. E lembre: a nova política de ciclo de vida garante no mínimo doze meses para o que foi apenas depreciado — o que é urgente é o que foi removido.

Quando usar stdio e quando usar Streamable HTTP?

stdio para servidores que rodam na máquina do usuário junto com o host (IDE, desktop, CLI) — sem rede, sem porta exposta. Streamable HTTP para servidores compartilhados por vários usuários ou que precisam viver perto do dado, no seu ambiente Azure.

MCP é seguro?

O protocolo dá as ferramentas (OAuth 2.x, escopo, consentimento explícito, auditoria), mas a segurança é da sua arquitetura. Os incidentes reais não vêm do protocolo: vêm de token com escopo largo demais, servidor de terceiro não revisado e ausência de aprovação humana em ação destrutiva.

Vale escrever um servidor MCP para uma API interna que só um agente usa?

Vale se você espera mais de um consumidor ao longo do tempo — e normalmente você espera. O custo marginal de expor via MCP em vez de um conector caseiro é baixo, e o ganho aparece no segundo agente, no segundo framework ou no dia em que o time de outra área quiser a mesma capacidade.

Conclusão

MCP deixou de ser curiosidade e virou infraestrutura. Com a revisão 2026-07-28, ele parou de exigir truques de infraestrutura para escalar: roda em HTTP comum, atrás de um balanceador comum, com cache, roteamento e tracing comuns. Isso muda o cálculo de quem estava esperando o padrão amadurecer.

A decisão de arquitetura que sobra para você não é mais “adoto MCP?”. É o que você expõe, com qual escopo e sob qual aprovação — porque cada tool que você publica é uma porta que um modelo pode abrir sozinho.

👉 Se você está desenhando a camada de integração dos seus agentes — especialmente em ambiente regulado, onde escopo e auditoria não são opcionais —, MCP é a decisão que evita reescrever conector a cada troca de modelo. Quer trocar ideia sobre MCP e arquitetura de agentes? Me chama no LinkedIn.

Compartilhe:

Eron Cavalcante

Eron Cavalcante atua como Cloud Solution Architect na Microsoft, com foco em plataformas de dados, IA generativa e arquitetura de nuvem para o setor financeiro. Combina experiência técnica em Azure, Databricks e engenharia de dados com uma visão de negócio orientada a valor, apoiando clientes na adoção responsável de IA em escala.

Deixe um comentário