O que aprendi criando um servidor MCP para uma ferramenta de desenvolvedores

MCP

O Model Context Protocol é uma forma padrão de entregar um conjunto de ferramentas a um agente de IA. Você descreve o que o seu produto faz, o cliente do agente se conecta e, a partir daí, o agente chama essas ferramentas durante a conversa, do mesmo jeito que lê um arquivo ou roda um comando.

A especificação é pequena, e a implementação não é a parte difícil. Um servidor funcionando sai em uma tarde. Fazer o agente usá-lo corretamente — sem chutar identificadores, sem buscar a coisa errada, sem concluir com toda a confiança que uma saída vazia significa dados vazios — foi a parte que exigiu trabalho de design de verdade.

Estas são as decisões que importaram quando lancei o servidor MCP do BugDump, e elas valem para qualquer ferramenta de desenvolvedores que esteja pensando em ter um.

Primeiro, o que o MCP muda de fato

Sem MCP, conectar um agente ao seu produto significa que o usuário é a integração. Ele abre o seu painel, encontra os dados relevantes, copia e cola na conversa. Cada pedaço de contexto é levado à mão, então a quantidade de contexto fica limitada ao quanto a pessoa está disposta a copiar.

Com MCP, o agente busca sozinho. Na prática, no BugDump a diferença é entre:

Aqui está um relatório de bug. [cola 200 linhas de saída do console]

e:

Veja a tarefa #42 no BugDump e corrija.

Na segunda versão, o agente puxa a tarefa, lista os relatórios dela, lê a telemetria de console e de rede, cruza o stack trace com os arquivos de código-fonte reais do repositório e propõe uma alteração. Ninguém abriu o painel. O agente teve acesso ao relatório inteiro, e não só à parte que alguém achou que valia a pena copiar — e decidir o que vale a pena copiar é justamente o julgamento que a depuração deveria produzir, não consumir.

É aí que está o ganho, e é por isso que o objetivo de design não é «expor a API». É «expor uma API que o agente consiga navegar sem mapa».

1. Amarre a chave a um escopo, para sempre

Meu primeiro design tinha um parâmetro de projeto em todas as ferramentas. Era a tradução óbvia da API REST, e estava errado por dois motivos.

Estava errado do ponto de vista da segurança. Uma chave MCP fica num arquivo de configuração e é usada por um processo autônomo. O raio de impacto dessa chave deve ser tão pequeno quanto o trabalho permitir.

Estava errado do ponto de vista da usabilidade. Com um parâmetro projectId e nenhum ID de projeto em mãos, o agente vai tentar preenchê-lo. Vai chutar proj_1. Vai passar o nome do projeto. Vai inventar algo plausível, receber um erro e tentar outra invenção. Um parâmetro que dá para chutar será chutado.

Por isso, uma chave MCP do BugDump fica vinculada a exatamente um projeto, para sempre, e nenhuma ferramenta recebe um argumento de projeto. Não há nada para chutar. Se o usuário pergunta sobre outro projeto, o servidor só consegue dizer a qual projeto essa chave pertence — pela ferramenta get_current_project, que existe só para o agente se orientar em vez de especular.

A regra geral: todo parâmetro que o agente não consegue tirar de uma saída anterior é um parâmetro que ele vai alucinar. Remova-o ou torne-o descobrível.

2. Desenhe a cadeia de drill-down de forma explícita

Minhas ferramentas formam uma sequência deliberada:

Plain Text
list_tasks            → find a task, get its taskId
get_task              → read one task
list_reports_for_task → list a task's reports, get each reportId
get_report            → read one report
get_report_telemetry  → page through everything in that report

Todo ID passado para uma ferramenta precisa ter saído de uma resposta anterior. As instruções do servidor dizem isso com essas palavras, junto com a proibição explícita de montar IDs.

O modo de falha que isso evita é sutil e caro. Agentes paralelizam de forma agressiva — e, na maioria das vezes, com razão. Mas um agente que decide disparar várias chamadas get_report antes de ter qualquer ID de relatório vai fabricá-los, receber sete erros e queimar uma boa parte do contexto para se recuperar. A instrução que resolve isso é a que diz que chamadas em paralelo só são seguras com IDs que você já tem.

3. Aceite os identificadores que as pessoas usam de verdade

As tarefas têm um UUID e um ID público exibido como #42. Os usuários dizem «tarefa 42». Ninguém nunca disse «tarefa 9f8c1a4e-...».

get_task aceita os dois. É uma concessão pequena com um efeito desproporcional, porque elimina toda uma classe de primeiras tentativas fracassadas. O agente tenta o que a pessoa disse, e funciona.

Procure o identificador que os seus usuários digitam no Slack. Aceite esse.

4. Resuma por padrão, mas diga o que cortou

Um único relatório de bug pode trazer milhares de entradas de console, centenas de requisições de rede com corpo, capturas de tela e um replay de sessão. Devolver tudo isso por padrão inundaria o contexto do agente na primeira chamada e não deixaria espaço para o trabalho em si.

Então get_report devolve, por padrão, um resumo compacto: as entradas de console mais recentes e os metadados de rede sem os corpos.

A parte crítica é a frase seguinte na resposta. O resumo informa o que foi cortado. Não em silêncio — de forma explícita, no próprio payload.

O truncamento silencioso é a pior falha que quem projeta ferramentas pode cometer, porque não gera erro. Gera uma resposta errada dita com confiança. Um agente que recebe um log de rede truncado, sem nenhuma indicação disso, vai dizer ao usuário que não houve requisições com falha. Ninguém descobre o erro até muito depois.

É a mesma disciplina do --max-count numa ferramenta de busca: informe o limite que você atingiu. Se a sua ferramenta limita qualquer coisa, o limite tem que aparecer na saída.

5. Dê ao agente um jeito de pegar o resto

Um resumo só é aceitável se houver como ler tudo. get_report_telemetry existe para isso: pagina o console, a rede com corpos ou as ações do usuário em blocos cronológicos, com offset e nextOffset, e uma flag hasMore que vira false quando o fluxo acaba.

Duas propriedades tornam isso viável para um agente, e não só para um programa.

Blocos cronológicos, não arbitrários. Com a ordem por tempo, uma leitura parcial continua fazendo sentido. Um agente que busca as duas primeiras páginas tem o começo da história, não uma amostra aleatória dela.

Uma condição de término explícita. hasMore: false é um fato com base no qual o agente pode agir. «O array veio menor que o limite» é uma inferência, e é nas inferências no fim de longos loops de ferramentas que os agentes ficam criativos.

6. Escreva as instruções para o agente, não para o usuário

Servidores MCP trazem um bloco de instruções que vai para o contexto do agente. É tentador escrever texto de marketing ali. O espaço rende muito mais com o que não dá para deduzir dos schemas das ferramentas:

  • A ordem em que as ferramentas devem ser chamadas

  • A proibição de inventar IDs

  • Quais chamadas podem ser paralelizadas com segurança e quais não

  • O que «o resumo não mostrou nada» significa e o que não significa

Esse último item não está ali à toa. Minhas instruções dizem, em essência: a resposta resumida informa o que foi cortado, então não suponha que «o resumo não mostra nada» significa «não há nada». Essa única frase evita uma conclusão errada específica que vi agentes tirarem.

Trate o bloco de instruções como um bilhete para um colega competente que entra no meio da tarefa. Ele sabe ler as assinaturas de tipo. O que ele precisa é da parte que só se aprende errando.

7. Somente leitura é um recurso

O servidor MCP do BugDump expõe sete ferramentas, e todas as sete são de leitura. Nenhuma cria uma tarefa, fecha uma tarefa ou publica um comentário.

Foi uma escolha pensada, não algo inacabado. O fluxo que o servidor possibilita é a investigação: reunir as evidências, cruzá-las com o código, propor uma correção. A metade de escrita do fluxo — decidir que um bug é real, que é duplicado, que está resolvido — é onde o julgamento humano faz o trabalho de verdade.

Há também um argumento mais simples. Uma chave somente leitura que vaza é um problema de exposição de dados. Uma chave de leitura e escrita que vaza, nas mãos de um processo autônomo, é um problema de integridade de dados. Entre os dois, e como o fluxo que eu tinha em mente não precisava de escrita, a decisão foi óbvia.

Comece com somente leitura. Adicione escrita quando um fluxo exigir, e dê a essas chaves um escopo separado.

O pré-requisito incômodo

Por baixo de tudo isso há uma condição que não tem nada a ver com o design do protocolo.

Um agente não tem como pedir mais detalhes. Não pode mandar mensagem para quem reportou e perguntar qual navegador estava usando. Ele tem exatamente uma chance com as evidências que existem e, se elas forem fracas, vai produzir uma resposta fluente, bem estruturada e inteiramente especulativa.

Ou seja, o valor de um servidor MCP é limitado pela qualidade dos dados por trás dele. O do BugDump funciona porque um relatório do BugDump já contém o console, o log de rede, o replay de sessão, as ações do usuário e o ambiente completo — capturados automaticamente no momento em que o bug aconteceu. O protocolo só os transporta.

Se você está pensando em um servidor MCP para a sua ferramenta, essa é a pergunta a responder primeiro: os dados por trás dele são completos o bastante para alguém agir sem precisar perguntar mais nada? Se sim, o MCP é uma camada fina que vale a pena. Se não, o protocolo só vai deixar as lacunas mais fáceis de alcançar.

Leia também