Gerei 207 ferramentas MCP a partir de uma especificação OpenAPI. Gerar foi a parte fácil.
Todo servidor MCP que eu li que envolve uma API REST de terceiros tem a mesma estrutura: alguém escolheu quinze ou vinte endpoints que pareciam úteis, escreveu manualmente um esquema Zod para cada um e o enviou.
Isso funciona por cerca de três meses.
Então a API adiciona um campo, deprecia um valor de enum, renomeia um parâmetro de consulta. O wrapper não percebe, porque nada nele está conectado à descrição da API sobre si mesma. Os esquemas se desviam. O modelo começa a ser rejeitado pela API upstream por razões que ele não consegue ver, e você precisa depurar um LLM adivinhando uma forma que deixou de ser verdadeira em fevereiro.
A outra falha é mais silenciosa. Você precisa do endpoint vinte e um — aquele sobre discos persistentes, ou escalonamento, ou grupos de ambiente — e o autor o pulou. Agora você está de volta ao curl, exceto que o agente está segurando metade do contexto e você está segurando a outra metade.
Eu não queria nenhum dos dois, então construí render-useful-mcp: um servidor MCP para Render onde cada ferramenta de API é gerada a partir do próprio documento OpenAPI da Render. Todos os 207 endpoints, sem curadoria.
A parte de geração levou um fim de semana. Tudo depois disso foi o trabalho real.
Faça o gerador se recusar a adivinhar
A maneira tentadora de escrever um gerador de especificação para ferramentas é torná-lo permissivo. Pule o que você não consegue analisar, volte para { type: "object" } quando um $ref ficar complicado, registre um aviso e siga em frente. Você obtém 207 ferramentas na primeira execução e se sente ótimo.
Você também obtém ferramentas que mentem. Um parâmetro tipado como um objeto livre quando a API realmente quer um dos quatro valores de enum é pior do que nenhuma ferramenta — o modelo produzirá lixo com confiança, e a falha aparece três camadas longe da causa.
Portanto, o gerador é fail-closed. Ele aborta a construção, barulhento, em:
- Uma operação cujo tag não mapeia para um conjunto de ferramentas conhecido. A Render adicionou uma categoria de recurso e eu ainda não a classifiquei. Esse é meu problema a resolver, não algo para encobrir.
- Uma colisão de nomes de ferramentas. Duas operações derivando o mesmo nome significa que meu esquema de nomenclatura está errado.
-
Um
$refcíclico que não consegue achatar. O JSON Schema lida com recursão; o atalho "apenas in-line tudo" não. Melhor saber. - Um parâmetro de caminho que não aparece no modelo de URL, ou aparece no modelo, mas não está declarado. Qualquer direção significa que eu construiria uma URL errada em tempo de execução.
CI executa o gerador. Uma mudança na especificação que o gerador não consegue lidar corretamente falha a construção em vez de enviar uma ferramenta sutilmente quebrada. Essa é toda a proposta de valor — um wrapper escrito à mão não pode ter essa propriedade, porque não há nada para verificar.
O retorno é que as próprias restrições da Render alcançam o modelo intactas. Enums permanecem enums. Padrões de string permanecem padrões. Campos obrigatórios permanecem obrigatórios. O modelo é rejeitado pelo meu esquema, localmente, com uma mensagem legível, em vez de pela API da Render após uma viagem de ida e volta.
O problema que ninguém avisa
207 ferramentas de API mais 4 ferramentas de fluxo de trabalho mais uma meta-ferramenta são 212 entradas em tools/list.
Você não pode enviar isso.
Cada definição de ferramenta — nome, descrição, esquema de entrada completo — vai para o contexto do modelo em cada única vez. 212 delas são dezenas de milhares de tokens gastos antes que o usuário tenha digitado qualquer coisa. Pior do que o custo é a diluição: um modelo escolhendo entre 212 vizinhos próximos escolhe errado muito mais frequentemente do que um escolhendo entre quinze. Cobertura completa, entregue de forma ingênua, torna o servidor menos útil do que o subconjunto curado que eu estava tentando superar.
A solução é separar o que o servidor pode fazer do que ele exibe.
As 207 ferramentas são agrupadas em 17 conjuntos de ferramentas pelo tag em sua operação de origem: services, postgres, disks, blueprints, logs, metrics, webhooks, network, maintenance, workflows, e assim por diante. Tudo está ativado por padrão, porque um servidor que oculta capacidade por padrão é um servidor que falha silenciosamente para a pessoa que precisava da coisa oculta. Mas uma variável de ambiente estreita isso:
RENDER_MCP_TOOLSETS=services,logs,metrics
Agora o modelo vê algumas dezenas de ferramentas em vez de 212, escolhidas por você, para o trabalho que você realmente está fazendo. Há um RENDER_MCP_READ_ONLY=true em cima disso, que elimina toda operação mutante — o padrão certo para qualquer coisa que você apontaria para produção.
A restrição interessante é que essa seleção é lida uma vez, na inicialização, e nunca muda. Eu voltarei a isso.
Quatro ferramentas que não estão na especificação
As ferramentas geradas são um espelho fiel da API, o que significa que herdam a ergonomia da API. Algumas sequências que são um pensamento para um humano são quatro chamadas para um agente, e os agentes são ruins em sequências de quatro chamadas.
Portanto, há quatro ferramentas escritas à mão por cima:
-
render_find_service— você conhece o serviço como "a api", a Render o conhece comosrv-d1a2b3c4e5f6g7h8i9j0. Faz correspondência difusa pelo nome e retorna alternativas próximas quando não há acerto exato, em vez de falhar. -
render_wait_for_deploy— bloqueia até que um deploy atinja um estado terminal, para que o agente pare de re-pesquisar em um loop e queime turnos. -
render_service_status— uma chamada que responde "este negócio está saudável?" combinando detalhes do serviço, último deploy e logs de erro recentes. Esta é a ferramenta que eu uso mais. -
render_recent_logs— a consulta de log que você realmente deseja, sem construir um objeto de filtro manualmente.
Essa é a divisão que eu defenderia geralmente: gere a superfície, escreva à mão a intenção. A especificação sabe o que a API pode fazer. Apenas você sabe o que as pessoas estão tentando realizar com isso.
Acompanhando uma API que você não controla
Uma ação do GitHub é executada semanalmente. Ela busca o documento OpenAPI atual da Render, regenera o catálogo e compara o resultado com o que está comprometido.
Se o conjunto de ferramentas não mudou, ele sai silenciosamente. Sem barulho. Se algo mudou, ele abre um PR com a diferença e um resumo — e sinaliza se a mudança é quebrante, significando operações removidas ou restrições apertadas, em vez de aditivas.
Pequeno detalhe com uma lição: o documento OpenAPI da Render não é servido de uma URL estável. Os endpoints documentados .json e .yaml retornam 404. Portanto, o buscador extrai a especificação do HTML dos documentos e valida o resultado extraído antes de entregá-lo ao gerador, porque raspar algo estrutural sem validá-lo é apenas uma maneira mais lenta de estar errado.
Se você está construindo algo semelhante contra uma API cuja especificação não está formalmente publicada: assuma que o caminho de recuperação irá quebrar e faça-o falhar barulhento quando isso acontecer.
A reescrita de 2026-07-28
A revisão de 2026-07-28 do MCP tornou o núcleo do protocolo sem estado — requisição/resposta, sem handshake de sessão, sem conexão de longa duração. Ótimo para qualquer um que esteja implantando em serverless. Para um servidor stdio como o meu, a migração foi menor do que o esperado, mas forçou uma verdadeira decisão de design.
Sob o novo modelo, as endpoints de listagem não variam por conexão. As respostas de tools/list carregam dicas de cache — um TTL e um escopo — para que os clientes possam tratar a lista de ferramentas
Para empresas brasileiras que utilizam APIs, a implementação de servidores MCP que se adaptam automaticamente a mudanças nas especificações pode reduzir falhas e melhorar a eficiência. Isso garante que as ferramentas estejam sempre atualizadas, evitando problemas de integração e aumentando a confiabilidade dos serviços.
