Seu servidor MCP não deveria obrigar as pessoas a colar um token

oauth

No mês passado, passei o servidor MCP do Kookee para tokens pessoais. Cada pessoa cria o seu, o assistente age como essa pessoa, e cada mudança que ele faz diz quem a fez. No Claude Code e no Cursor isso funciona bem: você cola o token num comando ou num arquivo de configuração, e pronto.

Agora tente explicar isso para alguém que escreve notas de versão no Claude, no notebook, ou que revisa o feedback pelo celular. Os apps de chat do Claude e o ChatGPT não têm campo para um cabeçalho Authorization. Eles se conectam a um servidor MCP remoto de um único jeito: você informa uma URL, eles mandam você entrar na sua conta e guardam a sessão. Isso é OAuth, e até esta semana o Kookee não falava OAuth.

Agora fala. Você adiciona https://api.kookee.dev/api/mcp no Claude, no ChatGPT ou no VS Code, entra no Kookee, escolhe Leitura e escrita ou Somente leitura, e a configuração acaba aí. A seguir, o que lancei e as decisões que levaram mais tempo que o código.

Como fica a conexão agora

A página do servidor MCP de cada projeto tem um card novo com a URL e os passos para cada app. Para o Claude há um botão Adicionar ao Claude. Ele abre a janela Add custom connector do Claude com o nome e a URL já preenchidos, e só falta clicar em Add.

O card Conecte um aplicativo de IA na página do servidor MCP: a URL do conector com um botão de copiar, abas para Claude, ChatGPT, VS Code e Claude Code, três passos e um botão Adicionar ao Claude

Em seguida, o Claude abre uma página do Kookee. Você entra na sua conta, ou cria uma, e decide o que o app pode fazer:

Página de consentimento do Kookee: Permitir que Claude use sua conta Kookee?, com a escolha entre Leitura e escrita, selecionada, e Somente leitura, e os botões Permitir e Cancelar

Nenhum token para criar, copiar, guardar ou trocar. O conector passa a aparecer no Claude na web, no desktop e no celular.

Sete decisões por trás do OAuth no MCP do Kookee

1. Uma conexão por pessoa, não por projeto

Meu primeiro plano copiava o modelo dos tokens: um token pertence a um projeto, então uma conexão também deveria pertencer. O Claude derrubou esse plano. Ele não deixa adicionar um segundo conector personalizado com a mesma URL, então uma conexão presa a um projeto limitaria todo usuário do Claude a um projeto só. Uma URL separada para cada projeto contorna isso, mas deixa quem está em cinco projetos com cinco conectores para configurar e acompanhar.

Por isso a conexão pertence à sua conta e alcança todos os projetos de que você é membro, com o seu papel em cada um. Toda ferramenta, exceto list_projects, aceita um argumento opcional project. Se você está em um projeto só, o assistente nunca precisa dele. Se está em vários, você diz “no projeto Lumen” e ele passa o slug. list_projects cobre o caso em que ele precisa perguntar.

Quando o projeto falta ou está errado, o servidor não responde com um erro HTTP. Ele devolve um resultado de ferramenta comum, que diz o que deu errado e lista os seus projetos:

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": "lumen", "name": "Lumen", "role": "OWNER" },
    { "slug": "lumen-docs", "name": "Lumen Docs", "role": "MEMBER" }
  ]
}

Um modelo lê isso e tenta de novo com o slug certo. Já um 400 vindo do transporte parece, para ele, um servidor quebrado.

2. Somente leitura é uma escolha na tela de consentimento

Com tokens, você escolhe o acesso ao criar o token. Com OAuth não existe página de tokens, então a escolha passou para o momento em que você aprova o app.

Por baixo, as duas opções são escopos OAuth, read e write, e escolher Somente leitura remove write antes de o app receber o código. Como nos tokens, uma conexão somente leitura não se limita a recusar escritas: ela nunca lista as ferramentas de escrita. Um modelo que vê move_feedback planeja uma triagem inteira em cima dela e falha no último passo. Um modelo que não a vê avisa que a conexão é somente leitura.

3. Desconectar tem que significar desconectado

Esta foi a parte que mais levou tempo. Um app OAuth guarda dois tokens: um token de acesso que dura uma hora e um refresh token que ele troca por novos tokens de acesso. Do jeito que vinha, o refresh grant do servidor OAuth em que me baseei não verificava se você ainda autorizava o app. Bastava remover o consentimento do app, e ele poderia continuar recebendo tokens de acesso novos.

Por isso agora acontecem duas coisas. Toda requisição MCP busca o token de acesso, o consentimento por trás dele e a conta numa única consulta, e recusa a requisição se algum deles não existir mais ou se a conta estiver desativada. E desconectar apaga o consentimento, os refresh tokens e os tokens de acesso juntos, numa única transação. Desconecte um app no Kookee, e a próxima requisição dele já é recusada.

Trocar ou redefinir a senha também desconecta todos os apps. Se você está redefinindo a senha porque algo deu errado, um assistente em algum lugar com uma sessão ativa é a última coisa que você quer. Contas de convidado não podem se conectar.

4. Tokens que dá para revogar, não tokens que é preciso esperar expirar

Uma escolha comum para tokens de acesso OAuth são os JWTs: assinados, autocontidos, verificados sem consultar o banco de dados. Eles também não podem ser revogados antes de expirar. Para um assistente que pode publicar em seu nome, “para de funcionar em até uma hora” não basta.

Os do Kookee são strings aleatórias, guardadas só como hashes SHA-256, com prefixos próprios: mcpa_ para tokens de acesso e mcpr_ para refresh tokens. Um scanner de segredos, ou uma pessoa lendo um log, consegue diferenciá-los de um token pessoal (kkm_) ou de uma chave de API (kk_). O preço é uma consulta por requisição, e o Kookee já faz essa consulta.

5. Só apps conhecidos

Os clientes MCP se identificam com um client ID metadata document: o client ID é uma URL, e o servidor a acessa para saber o nome do app e para onde pode mandar você de volta. É um design enxuto. Também significa que um servidor que aceita qualquer URL vai buscar o que apontarem para ele, e registrar um cliente para isso, antes que alguém tenha entrado na conta.

Em vez disso, aceito uma lista curta: Claude, Claude Code, ChatGPT e VS Code, cada um reconhecido pelo endereço onde fica o seu documento de metadados. Assim, quando a tela de consentimento pergunta “Permitir que Claude use sua conta Kookee?”, é mesmo o Claude, e ninguém consegue hospedar em outro lugar um documento que se diz Claude.

Isso tem um custo. O Cursor se registra por dynamic client registration, que deixei desligado, então quem usa o Cursor continua com tokens pessoais. Eles funcionam exatamente como antes.

6. A página de consentimento não pode ser carregada num frame

Uma página com um botão Permitir é o alvo clássico de clickjacking: carregue-a num frame invisível e ponha algo tentador embaixo do botão. A do Kookee envia Content-Security-Policy: frame-ancestors 'none' e X-Frame-Options: DENY, então nenhum outro site consegue colocá-la num frame. São dois cabeçalhos, fáceis de esquecer até alguém escrever para avisar.

7. A mesma atribuição de antes

Entrar na conta muda a forma de se conectar, não o que fica registrado. Uma mudança feita pelo Claude é salva no seu nome, como uma mudança feita com token, e o log do servidor anota qual app a fez. No quadro de feedback, as mudanças de coluna, as reatribuições e as respostas feitas pelo app aparecem marcadas via MCP, então os seus colegas veem quem moveu o post e que foi um assistente.

O que você vê no Kookee

Tudo o que você conectou aparece nas configurações da sua conta, em Segurança → Aplicativos conectados, com o acesso de cada app e o dia em que você o conectou. Desconectar leva um clique e uma confirmação.

Aplicativos conectados em Configurações → Segurança: Claude Code e Claude com Leitura e escrita, VS Code com Somente leitura, cada um com um botão Desconectar

Os tokens pessoais continuam aí. Eles ainda são a ferramenta certa para o Cursor, para scripts e CI, e para um .mcp.json que a equipe inteira versiona, e agora ficam num card na página do servidor MCP, abaixo do novo.

Se você mantém um servidor MCP

  • Dê suporte a OAuth. Quem usa o Claude, o ChatGPT e outros apps de chat não consegue colar um cabeçalho.

  • Faça a conexão por pessoa e receba o workspace como argumento da ferramenta. O Claude não aceita a mesma URL duas vezes.

  • Coloque a escolha de somente leitura na tela de consentimento e não liste ferramentas que a autorização não permite usar.

  • Verifique o consentimento a cada requisição e apague os refresh tokens quando alguém desconectar. Não conte com o refresh grant para fazer isso por você.

  • Prefira tokens opacos que você pode revogar a JWTs que é preciso esperar expirar.

  • Aceite uma lista de URLs de metadados de clientes conhecidos em vez de buscar o que quer que o cliente mande.

  • Mantenha a página de consentimento fora de frames.

  • Responda a “qual projeto?” com um resultado de ferramenta que o modelo consiga usar, não com um erro HTTP.


O servidor MCP do Kookee agora funciona no Claude, no ChatGPT e no VS Code com login. Veja Conectar o Claude, o ChatGPT ou o VS Code ao Kookee para os passos, ou Gerenciar seu conteúdo a partir de um assistente de IA (MCP) para o que um assistente consegue fazer depois de conectado.