OAuth no servidor MCP: Claude e ChatGPT se conectam sem token

OAuth

Há algumas semanas escrevi sobre por que agentes de IA precisam de tokens pessoais. A configuração no fim daquele post funciona bem no Claude Code: crie um token, rode um comando, e o seu agente consegue ler relatórios de bug.

Aí as pessoas tentaram adicionar o BugDump ao Claude na web, e não funcionou.

Num conector personalizado do Claude não há onde colar um token. No ChatGPT também não. O VS Code aceita um cabeçalho se você editar o arquivo de configuração dele, mas o fluxo «Add Server» não pede nenhum. Os clientes MCP que a maioria das pessoas usa esperam uma URL e, quando o servidor responde 401, esperam fazer login.

Então adicionei login ao servidor MCP do BugDump. O código foi a parte pequena. Abaixo está como isso fica para quem se conecta e as quatro decisões que tomaram a maior parte do tempo.

Como um cliente MCP faz login

Você copia https://api.bugdump.com/api/mcp e adiciona ao Claude como um conector personalizado, ou clica em Adicionar ao Claude na página MCP do seu projeto, que copia por você. O Claude abre uma página do BugDump no seu navegador. Você entra, escolhe se o app pode alterar coisas ou só lê-las, e clica em Permitir.

Página de consentimento do BugDump perguntando «Permitir que Claude use sua conta BugDump?», com Leitura e escrita selecionado, Somente leitura como a outra opção e os botões Permitir e Cancelar

Por baixo, é o fluxo que a especificação do MCP descreve. A primeira requisição volta como 401, com um cabeçalho WWW-Authenticate apontando para um pequeno documento JSON. Esse documento informa qual servidor cuida do login. O Claude busca os metadados desse servidor, manda você para a página de login e troca o resultado por um token. Nada disso é específico do Claude. Um cliente que segue a especificação só precisa da URL para encontrar a página de login.

Uma conexão para todos os seus projetos

Os tokens pessoais no BugDump são vinculados a um único projeto. É um modelo organizado. Um token da Acme Store não enxerga a Acme Admin, e ninguém precisa se perguntar em que projeto o agente está trabalhando.

Minha ideia era manter isso também no login: uma conexão por projeto, talvez com uma URL por projeto. Aí tentei adicionar uma segunda. O Claude não adiciona a mesma URL de conector duas vezes, e uma URL diferente para cada projeto significa um conector separado para configurar e acompanhar em cada um. Quem tem três projetos acabaria com três conectores, e a maioria das pessoas pararia no primeiro.

Por isso, uma conexão por login pertence a você, não a um projeto. Ela alcança todos os projetos dos quais você é membro, e todas as outras ferramentas recebem um argumento a mais, project: o slug do projeto, que pode ser omitido se você pertence a um projeto só. Uma ferramenta nova, list_projects, informa ao assistente o que ele pode acessar, incluindo a chave de cada projeto, para que ele saiba que AS-1 fica na Acme Store.

A parte de que mais gosto é o que acontece quando o assistente esquece de dizer de qual projeto está falando. O servidor não escolhe um por ele. Se você pertence a mais de um projeto, a chamada volta com isto:

JSON
{
  "error": "MCP_PROJECT_REQUIRED",
  "message": "This account belongs to several projects. Pass `project` with the slug of the one the user means.",
  "projects": [
    { "slug": "acme-admin", "key": "AA", "name": "Acme Admin", "role": "OWNER" },
    { "slug": "acme-store", "key": "AS", "name": "Acme Store", "role": "OWNER" }
  ]
}

Isso volta como o resultado da própria ferramenta, e não como um erro de protocolo, então o modelo lê e tenta de novo com o slug certo. Não deve acontecer com frequência: as instruções do servidor dizem ao assistente para chamar list_projects antes de qualquer outra coisa.

A participação no projeto é verificada a cada chamada. Se alguém sai de um projeto, o assistente dessa pessoa perde o acesso a ele na requisição seguinte, e não há nada para limpar.

Só clientes conhecidos: Client ID Metadata Documents

No OAuth clássico, um app é registrado no servidor antes de poder pedir acesso a alguém, à mão ou via Dynamic Client Registration. A abordagem mais nova que a especificação do MCP recomenda, os Client ID Metadata Documents, deixa o app se identificar com uma URL que ele controla. A do Claude é um arquivo JSON em claude.ai que, entre outras coisas, lista os endereços em que o Claude pode receber o resultado do login.

É um bom design. Sozinho, porém, ele significa que qualquer pessoa capaz de hospedar um arquivo JSON pode aparecer na sua página de consentimento com o nome que quiser.

Por isso o BugDump aceita quatro apps: Claude, Claude Code e VS Code pela URL exata do documento de cada um, e o ChatGPT por um padrão fixo em chatgpt.com, porque ele publica um documento por conexão. A página de consentimento mostra o nome do app e o host onde o documento dele fica («Solicitado por claude.ai»), e isso o app não consegue falsificar. O Dynamic Client Registration está desligado. O Cursor usa esse mecanismo, então o Cursor continua usando um token pessoal, assim como scripts e jobs de CI. É para isso que os tokens servem bem, de qualquer forma.

Checando o token, o consentimento e a conta a cada requisição

Um access token dura uma hora. Depois disso, o app troca discretamente um refresh token por um novo, e é assim que uma conexão continua viva por semanas sem pedir que você faça login de novo. O problema é que a etapa de refresh não para para perguntar se você ainda quer o app conectado, nem se a sua conta continua ativa.

Por isso o endpoint MCP do BugDump pergunta, toda vez. A cada requisição, ele verifica se o access token existe e não expirou, se o seu consentimento para aquele app ainda existe e se a sua conta está ativa e não é de convidado. Desconectar um app apaga o consentimento e todos os tokens dele de uma vez, e a chamada seguinte falha com um 401 que o manda de volta para a página de login. Trocar ou redefinir a senha desconecta todos os apps ao mesmo tempo, que é o que você quer se o motivo da troca for um vazamento.

Aplicativos conectados na segurança da conta do BugDump: VS Code com Somente leitura, Claude Code e Claude com Leitura e escrita, cada um com o botão Desconectar

Os tokens em si são strings aleatórias guardadas como hash, iguais aos tokens pessoais. Não há chaves de assinatura para rotacionar, e revogar um token significa apagar uma linha.

O assistente continua agindo como você

Todo o resto ficou igual de propósito. Um app conectado por login age como você e vê o que você vê. Cada mudança que ele faz aparece no histórico e nos comentários da tarefa com o seu nome e o rótulo via MCP, e o log de auditoria registra qual app a fez. Escolha Somente leitura, e as ferramentas de escrita nem são registradas: o modelo nunca vê move_task, então não tem como tentar chamá-la.

Tarefa do BugDump «Checkout button does nothing on Safari» com um comentário que o Claude publicou em nome de Alex Rivera, marcado como via MCP

Esse é o Claude num bug de checkout no Safari. Ele leu a tarefa, deixou uma nota sobre onde olhar primeiro, moveu-a para Em andamento e a atribuiu. Para quem ler a tarefa depois, fica claro qual parte veio do assistente.

O que ainda falta resolver

  • O ChatGPT precisa do modo de desenvolvedor para adicionar um servidor MCP personalizado, e o modo de desenvolvedor exige um plano pago do ChatGPT.

  • Mudar o acesso significa reconectar. Para dar acesso de escrita a um app que só lê, você o desconecta e conecta de novo. Prefiro que você faça essa escolha de forma deliberada, e não com um botão que depois esquece, mas é um passo a mais.

  • O rótulo ainda não diz o nome do app. Um comentário diz «via MCP», não «via Claude». Cada comentário e cada entrada do histórico já guardam qual app os fez; a página da tarefa só ainda não mostra isso.

Conectando o BugDump ao Claude, ao ChatGPT ou ao VS Code

Abra a página Servidor MCP de qualquer projeto e clique em Adicionar ao Claude, ou copie a URL para o ChatGPT ou o VS Code. Os passos para cada app estão em Conectar o Claude, o ChatGPT ou o VS Code ao BugDump. Se você usa o Cursor ou está conectando um script, os tokens pessoais continuam funcionando exatamente como antes: veja Usando o BugDump com o Claude Code (MCP).

Leia também