Fluxo OAuth 2.1 do MCP explicado: CIMD ou DCR com exemplos

O fluxo OAuth 2.1 do MCP, passo a passo, desde o desafio 401 e as consultas de metadados até o PKCE e a validação do token. Veja JSON real para Client ID Metadata Documents e Dynamic Client Registration, uma tabela lado a lado e as verificações de segurança que cada abordagem exige.

Fluxo OAuth 2.1 do MCP explicado: CIMD ou DCR com exemplos
Cristian Da Conceicao
Fundador do Picasso IA

Um cliente MCP que quer chamar um servidor protegido enfrenta um problema de confiança já na primeira requisição. O servidor nunca viu esse cliente, e o servidor de autorização por trás dele também nunca ouviu falar dele. A especificação de autorização do MCP resolve isso com OAuth 2.1, e a parte que mais mudou no último ano é a forma como um cliente obtém seu client_id. Client ID Metadata Documents (CIMD) entraram na revisão 2025-11-25 como um mecanismo de registro recomendado, e a revisão 2026-07-28 marca o Dynamic Client Registration (DCR) como obsoleto. Este artigo acompanha o fluxo OAuth 2.1 do MCP desde a primeira resposta 401 até a primeira chamada de ferramenta autorizada, mostra requisições e respostas reais para os dois caminhos de registro e termina com uma regra simples para escolher entre eles.

Por que o MCP precisa de OAuth 2.1

Recepcionista de hotel deslizando um cartão simples de quarto sobre um balcão de mármore para um hóspede

Imagine a recepção de um hotel. Você mostra seu documento uma vez, a recepção confirma quem você é e você sai com um cartão de quarto que abre o seu quarto e mais nada. O OAuth segue o mesmo padrão. O servidor de autorização é a recepção, o token de acesso é o cartão do quarto, e o servidor MCP é a porta que confere o cartão. A porta nunca vê o seu passaporte, e um cartão do quarto 412 não abre o quarto 518.

A autorização é opcional no MCP, mas as regras ficam rígidas assim que você a ativa. Servidores baseados em HTTP DEVERIAM seguir a especificação de autorização, enquanto servidores stdio NÃO DEVERIAM fazer isso e, em vez disso, leem as credenciais do ambiente. Veja o que a especificação torna obrigatório:

  • PKCE com o método S256. Os clientes também devem verificar se o servidor de autorização anuncia code_challenge_methods_supported e recusar continuar se o campo estiver ausente.
  • Metadados de recurso protegido (RFC 9728). O servidor MCP os publica, e o cliente os usa para encontrar o servidor de autorização correto.
  • Indicadores de recurso (RFC 8707). Os clientes enviam um parâmetro resource tanto na requisição de autorização quanto na requisição de token.
  • Bearer tokens em um cabeçalho. O cabeçalho Authorization: Bearer vai em toda requisição HTTP, e os tokens nunca aparecem na query string.
  • Validação de audiência. Um servidor MCP aceita apenas tokens emitidos para ele mesmo.

Os quatro atores

Todo fluxo deste artigo envolve as mesmas quatro partes. Mantenha-as claras e o restante fica fácil de acompanhar.

AtorPapel no OAuthExemplo típico
UsuárioDono do recursoUma pessoa aprovando o acesso em um navegador
Cliente MCPCliente OAuthUm app de desktop com IA, uma IDE, um agente de linha de comando
Servidor MCPServidor de recursoshttps://mcp.example.com/mcp
Servidor de autorizaçãoEmite tokensAuth0, Okta, Microsoft Entra ID ou um serviço próprio

💡 O servidor MCP e o servidor de autorização podem estar na mesma implantação ou pertencer a duas empresas diferentes. Um client ID só tem significado para o servidor de autorização que o emitiu ou aceitou, então um cliente nunca deve presumir que um ID funciona em todo lugar.

O fluxo do 401 ao token

Desenvolvedora organizando cinco fichas com setas desenhadas à mão sobre uma mesa de carvalho

O handshake é uma cadeia curta de requisições HTTP simples. Você pode acompanhar cada uma no terminal, o que torna a depuração bem menos misteriosa do que as siglas sugerem.

O desafio 401

O cliente envia uma requisição MCP sem token. O servidor recusa e informa ao cliente onde procurar:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

O parâmetro scope é a dica do servidor sobre o menor privilégio necessário para esta requisição. Se o parâmetro resource_metadata estiver ausente, o cliente recorre a URLs conhecidas, primeiro /.well-known/oauth-protected-resource/mcp (com o caminho inserido) e depois a versão na raiz.

Duas consultas de metadados

O cliente busca o documento de metadados de recurso protegido e lê qual servidor de autorização usar:

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read", "files:write"]
}

Em seguida, pede ao servidor de autorização que se descreva, tentando primeiro /.well-known/oauth-authorization-server e depois o /.well-known/openid-configuration do OpenID Connect. O issuer presente na resposta precisa coincidir com a URL que o cliente usou para montar a requisição, ou o documento é descartado. Uma resposta típica tem esta aparência:

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "registration_endpoint": "https://auth.example.com/register",
  "code_challenge_methods_supported": ["S256"],
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true
}

Dois campos dessa resposta decidem como o cliente se registra: client_id_metadata_document_supported (CIMD) e registration_endpoint (DCR). Voltaremos aos dois.

PKCE e o parâmetro de recurso

Com um client_id em mãos, o cliente gera um verificador PKCE de uso único, calcula o hash dele e abre o navegador. Ele também registra o issuer esperado para conferir a resposta depois.

GET https://auth.example.com/authorize?response_type=code
  &client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient-metadata.json
  &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fmcp.example.com%2Fmcp
  &scope=files%3Aread
  &state=xyz123

O valor resource é a URI canônica do servidor MCP. Os clientes DEVEM enviá-lo mesmo quando o servidor de autorização o ignora, porque é isso que permite vincular um token a um servidor específico.

Troca do código e uso do token

O usuário aprova, e o navegador volta para a URI de redirecionamento com um code, o state e, idealmente, um parâmetro iss. A revisão 2026-07-28 acrescenta uma verificação de emissor da RFC 9207: se iss estiver presente, o cliente o compara com o emissor registrado antes de enviar o código para qualquer lugar. Isso bloqueia ataques de troca de contexto (mix-up), em que um servidor de autorização hostil tenta capturar códigos destinados a um servidor legítimo.

Depois vem a requisição de token, que comprova a posse do verificador PKCE:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
&client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient-metadata.json
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&resource=https%3A%2F%2Fmcp.example.com%2Fmcp

A resposta traz o token de acesso, e daí em diante toda requisição ao servidor MCP inclui Authorization: Bearer <access-token>. Se mais tarde o token não tiver um escopo necessário, o servidor responde 403 com error="insufficient_scope", e o cliente autoriza novamente com a união dos escopos antigos e novos.

DCR: como funciona e onde quebra

Viajantes em fila diante de uma fileira de portões eletrônicos idênticos de aeroporto sob um teto de vidro

O Dynamic Client Registration vem da RFC 7591. A ideia é simples: antes do primeiro login, o cliente envia seus dados a um registration_endpoint e recebe de volta um client_id novo. Nenhuma pessoa preenche um formulário. Durante anos essa foi a principal forma de integrar automaticamente um cliente desconhecido, e por isso o MCP adotou o mecanismo no início.

Uma requisição de registro

Veja uma troca realista para um cliente MCP de desktop:

POST /register HTTP/1.1
Host: auth.example.com
Content-Type: application/json

{
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://localhost:3000/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "application_type": "native"
}
{
  "client_id": "s6BhdRkqt3",
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://localhost:3000/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none",
  "client_id_issued_at": 1791247700
}

Repare em application_type. Desde a revisão 2026-07-28, os clientes DEVEM definir esse campo. Servidores que falam OpenID Connect tratam um valor ausente como web, e esse padrão pode rejeitar URIs de redirecionamento localhost.

Por que os servidores têm dificuldade com ele

O DCR funciona, mas transfere uma carga grande para o servidor de autorização:

  • Um endpoint público de escrita. Qualquer pessoa na internet pode criar registros, então você precisa de limites de taxa, expiração e tarefas de limpeza.
  • Um registro por pareamento. Cada cliente se registra separadamente em cada servidor de autorização e precisa armazenar o resultado com segurança, indexado por issuer. Quando o servidor de autorização muda, o cliente precisa se registrar de novo.
  • Nomes que ninguém verificou. A tela de consentimento mostra o client_name que quem se registrou digitou, então um aplicativo hostil pode se chamar como quiser.
  • Crescimento do banco de dados. Milhares de instalações de um mesmo cliente popular viram milhares de registros que todos significam a mesma coisa.

Esses custos são o motivo pelo qual a especificação agora aponta novas implementações para outro caminho.

CIMD: a URL é o client ID

Agente de fronteira examinando a página de um passaporte sob um abajur com lupa

Pense em um passaporte. Ninguém pede ao agente de fronteira que memorize você antecipadamente. Você entrega um documento, e o agente o confere com a autoridade emissora. O CIMD inverte o registro do mesmo jeito. O cliente publica um documento JSON em uma URL HTTPS estável, e essa URL é o client_id. O servidor de autorização lê o documento quando vê a URL pela primeira vez, então não há nada para registrar antecipadamente.

O documento de metadados

As regras para o cliente são curtas. O client_id precisa usar https e incluir um caminho, o documento deve conter client_id, client_name e redirect_uris, e o client_id dentro do arquivo precisa coincidir com a URL de onde ele foi servido, caractere por caractere. O exemplo da própria especificação tem esta aparência:

{
  "client_id": "https://app.example.com/oauth/client-metadata.json",
  "client_name": "Example MCP Client",
  "client_uri": "https://app.example.com",
  "logo_uri": "https://app.example.com/logo.png",
  "redirect_uris": [
    "http://127.0.0.1:3000/callback",
    "http://localhost:3000/callback"
  ],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

O que o servidor verifica

Quando chega uma requisição de autorização com uma URL no formato client_id, o servidor de autorização segue uma rotina fixa:

  1. Busque o documento com um GET HTTPS simples.
  2. Confirme que é um JSON válido e que contém os campos obrigatórios.
  3. Confirme que o client_id no arquivo é exatamente igual à URL.
  4. Confirme que o redirect_uri na requisição corresponde a um dos listados no arquivo.
  5. Armazene o resultado em cache, respeitando os cabeçalhos de cache HTTP.
  6. Mostre ao usuário o client_name e o nome do host de redirecionamento na tela de consentimento.

Um esboço mínimo dos passos 1 a 3 em TypeScript, escrito para clareza e não para uso em produção:

async function loadClient(clientId: string) {
  const url = new URL(clientId);
  if (url.protocol !== "https:" || url.pathname === "/") throw new Error("invalid_client");
  await assertPublicHost(url.hostname); // reject private, loopback and link-local addresses

  const res = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
  const doc = await res.json();

  if (doc.client_id !== clientId) throw new Error("invalid_client");
  if (!doc.client_name || !Array.isArray(doc.redirect_uris)) throw new Error("invalid_client");
  return doc;
}

Anunciando suporte a CIMD

O servidor de autorização anuncia o recurso em seus metadados com "client_id_metadata_document_supported": true. Os clientes que o encontram usam a URL deles como client_id e pulam o registro por completo. Como o ID é uma URL pública, ele também é portátil: o mesmo cliente pode conversar com outro servidor de autorização amanhã sem se registrar de novo.

CIMD ou DCR lado a lado

Duas portas iguais em um corredor de tijolos, uma com fechadura numérica e outra com placa de identificação

PerguntaCIMDDCR
Status na especificação em 2026-07-28Recomendado (DEVE)Obsoleto, mantido por compatibilidade (PODE)
Quem armazena o registro do clienteO cliente o hospeda, o servidor o armazena em cacheO servidor de autorização o armazena
Formato do client_idUma URL, como https://app.example.com/oauth/client-metadata.jsonUma string opaca, como s6BhdRkqt3
Precisa de um endpoint de registroNãoSim, anunciado como registration_endpoint
Trabalho antes do primeiro loginNenhum para o clienteUm POST por servidor de autorização
Portável entre servidores de autorizaçãoSimNão, registrar de novo para cada emissor
Principal riscoSSRF durante a busca, falsificação de localhostAbuso de endpoint aberto, registros inúteis
Como um servidor anuncia o recursoclient_id_metadata_document_supportedregistration_endpoint

Um cliente que suporta todas as opções DEVE escolher nesta ordem:

  1. Usar os dados de cliente pré-registrados, se tiver algum para este servidor.
  2. Usar CIMD se o servidor de autorização anunciar suporte.
  3. Recorrer ao DCR se existir um registration_endpoint.
  4. Pedir ao usuário que digite os dados do cliente manualmente.

💡 O pré-registro continua sendo a melhor opção quando você o tem. Se você controla tanto o cliente quanto o servidor de autorização, um client_id fixo pula todas as consultas acima.

Verificações de segurança que você não pode pular

Cadeado de latão e corrente de aço em um portão de ferro antigo ao amanhecer

Passar do DCR para o CIMD não elimina o risco. Ele move o risco para outros lugares, e cada um precisa de um responsável.

SSRF na busca

Com o CIMD, um visitante anônimo decide qual URL o seu servidor vai requisitar. Aponte client_id para https://169.254.169.254/latest/meta-data/ ou para um painel administrativo interno, e um buscador descuidado vira um proxy para dentro da sua rede. Resolva primeiro o nome do host e rejeite faixas privadas, de loopback e de link-local. Defina um timeout curto, limite o tamanho da resposta e seja rigoroso com os redirecionamentos.

Redirecionamentos para localhost

Um documento de metadados não pode provar que um processo escutando em localhost:3000 pertence ao cliente nomeado no arquivo. Qualquer programa local pode reivindicar essa porta. Por isso, o servidor de autorização DEVE exibir o nome do host de redirecionamento na tela de consentimento, DEVERIA avisar quando todas as URIs de redirecionamento forem localhost e PODE exigir uma atestação extra para obter maior garantia. As próprias URIs de redirecionamento devem corresponder exatamente, nunca por prefixo ou padrão.

Audiência e repasse de token

Técnico inspecionando um rack de servidores em um corredor de data center com uma lanterna

O servidor MCP é o último portão, e precisa conferir o cartão, não apenas dar uma olhada nele. Valide a assinatura, a expiração, os escopos e, acima de tudo, a audiência. Um token emitido para outro serviço deve ser rejeitado com um 401. Se o seu servidor MCP chamar uma API upstream, ele precisa de um token separado para essa API, emitido pelo servidor de autorização dessa API. Repassar o token do cliente se chama repasse de token (token passthrough), e a especificação o proíbe expressamente.

Uma lista curta de verificação para fixar ao lado do seu monitor:

  • Sirva todo endpoint de autorização sobre HTTPS e permita apenas redirecionamentos HTTPS ou localhost.
  • Mantenha os tokens de acesso de curta duração e rotacione os refresh tokens para clientes públicos.
  • Use e verifique o parâmetro state.
  • Valide iss quando ele estiver presente, antes de trocar o código.
  • Inclua em um único desafio todos os escopos necessários para uma operação, para que o usuário não passe por telas de aprovação repetidas.

Escolhendo uma estratégia

Três engenheiros desenhando caixas e setas em um quadro branco de vidro

A decisão é menos dramática do que o debate sugere. Dê prioridade ao CIMD, mantenha o DCR como ponte e seja honesto sobre de que lado da mesa você está.

Se você administra um servidor MCP

Publique os metadados de recurso protegido de qualquer forma. Escolha um servidor de autorização que suporte CIMD e, se o seu ainda não suportar, mantenha o DCR ativado com limites de requisição e expiração, em vez de bloquear todos os clientes. Coloque um scope no seu desafio WWW-Authenticate, responda com 403 e insufficient_scope quando um token não tiver o escopo necessário, e verifique a audiência em toda requisição.

Se você desenvolve um cliente MCP

Hospede seu documento de metadados em uma URL que você vai manter por anos, porque a URL é a sua identidade. Leia os metadados do servidor de autorização e depois escolha um caminho de registro no código:

function pickRegistration(as: AuthServerMetadata, saved?: SavedClient) {
  if (saved && saved.issuer === as.issuer) return { mode: "saved", clientId: saved.clientId };
  if (as.client_id_metadata_document_supported) return { mode: "cimd", clientId: CLIENT_METADATA_URL };
  if (as.registration_endpoint) return { mode: "dcr", endpoint: as.registration_endpoint };
  return { mode: "manual" };
}

Quando você de fato recorrer ao DCR, armazene as credenciais vinculadas ao issuer, defina application_type: "native" para apps de desktop e de linha de comando, e nunca as reutilize com outro servidor de autorização.

Experimente no PicassoIA

Designer segurando uma fotografia impressa diante da janela de um estúdio em loft

Seja qual for o lado do handshake que você construir, vai escrever bastante JSON, fixtures de teste e documentação. Um modelo de linguagem capaz acelera isso, e o PicassoIA hospeda vários. Veja um jeito rápido de usar o Claude Sonnet 5 como revisor do seu documento de metadados:

  1. Abra a página do Claude Sonnet 5 no PicassoIA.
  2. Cole o seu client-metadata.json e os metadados do servidor de autorização do seu provedor.
  3. Peça uma verificação com checklist: "Confira este documento com as regras do CIMD: client_id igual à URL, https com caminho, campos obrigatórios presentes, redirect_uris exatas. Liste cada falha."
  4. Peça que a saída venha como tabela com regra, resultado e correção, para que a resposta seja fácil de colar em um pull request.
  5. Para uma segunda opinião, rode o mesmo prompt no GPT 5.6 Sol ou faça uma passada rápida com o Gemini 3.5 Flash, e compare as falhas que cada um encontra.

💡 Os modelos revisam documentos bem, mas não substituem um teste real. Execute seu fluxo contra um servidor de autorização de homologação antes de publicar.

Documentação precisa de imagens tanto quanto precisa de JSON. Uma imagem de destaque, um fundo de diagrama ou um card para redes sociais tornam um post sobre OAuth muito mais fácil de compartilhar, e o PicassoIA foi feito exatamente para isso. Resultados fotorrealistas vêm de prompts específicos: nomeie a lente, a direção da luz e as texturas da cena. Experimente algo como "uma recepcionista de hotel deslizando um cartão de quarto sobre um balcão de mármore, lente de 50mm, luz suave de janela vindo da direita, granulação de filme" e veja o que aparece. Desenvolvedores também podem acessar os modelos de imagem e vídeo do PicassoIA pela API do PicassoIA e por conexões MCP, então o mesmo prompt pode rodar a partir do seu próprio agente.

Pronto para criar os seus próprios visuais? Abra o PicassoIA, escolha um modelo e comece a experimentar seu primeiro prompt hoje mesmo.

Compartilhe este artigo

Escolha seu idioma