Índice5 seções
MCP4 min de leitura
Publicar no MCP registry, e as quatro coisas que nos barraram
O MCP registry oficial é a camada de descoberta que a Anthropic lançou para servidores Model Context Protocol. Entrar na listagem levou quatro tentativas. Cada rejeição estava certa e nenhuma delas era óbvia.
Mikhail Savchenko
A resposta curta
A publicação é feita com a CLI mcp-publisher a partir de um manifesto server.json. A autenticação prova que você controla o domínio que o seu namespace nomeia: você gera um par de chaves Ed25519 e serve a metade pública em /.well-known/mcp-registry-auth nesse domínio, e depois assina a publicação com a metade privada. O que nos rejeitou foi uma descrição acima de 100 caracteres, um campo mcpName ausente no package.json, um namespace que não batia com o domínio que estávamos provando e uma URL de repositório que não apontava para nada público.
A Anthropic lançou um registry oficial para servidores Model Context Protocol. Um agente já conseguia se conectar a um deles; até o registry, não havia maneira padrão de ele descobrir que um servidor específico existia.
Publicamos nele o servidor do INITE Club. O fluxo de publicação é curto e bem desenhado. Ainda assim foram quatro tentativas, e cada rejeição nos ensinou algo que não estava escrito em nenhum lugar onde tínhamos olhado.
O que uma listagem é, de fato
Uma entrada no registry é um manifesto server.json validado contra um schema publicado. Resumida, a nossa declara um nome, uma descrição e as duas formas de alcançar o mesmo servidor — o arquivo real também carrega $schema, um version no nível superior e uma versão na entrada do pacote, todos obrigatórios:
{
"name": "club.inite/inite-club",
"description": "Ask the agent of someone whose calendar you could not get. No credential needed to try.",
"remotes": [
{ "type": "streamable-http", "url": "https://inite.club/api/mcp" }
],
"packages": [
{ "registryType": "npm", "identifier": "@inite/club-mcp",
"transport": { "type": "stdio" } }
]
}
As duas entradas apontam para o mesmo clube. A remota é o servidor; o pacote é um adaptador para os clientes que falam stdio e mais nada, que ainda são a maioria.
Provar que o nome é seu
club.inite/inite-club é um namespace em DNS reverso, e é uma afirmação: este servidor pertence a quem opera inite.club. O registry obriga você a demonstrar isso.
Você gera um par de chaves Ed25519 e serve a metade pública a partir do próprio domínio:
GET https://inite.club/.well-known/mcp-registry-auth
v=MCPv1; k=ed25519; p=<base64 public key>
Depois, assina a publicação com a metade privada.
mcp-publisher login http --domain inite.club --private-key <hex>
mcp-publisher publish
A senha de uma conta provaria que você é dono de uma conta. Servir aquele arquivo prova que você controla o domínio que o namespace nomeia, que é o que está de fato sendo afirmado. Existe uma variante em DNS, mcp-publisher login dns, que prova a mesma coisa com um registro TXT; ficamos com a de HTTP porque o site já era nosso para fazer deploy e um arquivo é mais rápido de rotacionar do que um registro DNS.
O que importa, porque tivemos de rotacionar. Perdemos a chave privada entre uma publicação e a seguinte, e a recuperação significou gerar um novo par e substituir a chave servida antes que qualquer coisa pudesse sair. Guarde-a fora do repositório e em algum lugar durável — a nossa hoje mora em ~/.config/inite/, com modo 0600.
As quatro rejeições
Uma descrição acima de 100 caracteres. O campo tem limite, e o limite é menor do que a frase que a maioria das pessoas escreve primeiro. O que voltou nomeava o campo — claro depois de lido, invisível para quem está procurando um stack trace. A nossa levou vários rascunhos até ficar ao mesmo tempo curta o bastante e verdadeira.
Um mcpName ausente no package.json. Se a sua listagem anexa um pacote npm, esse pacote precisa reivindicar a associação de volta. O registry não aceita a sua palavra de que @inite/club-mcp é o pacote de club.inite/inite-club; o pacote também precisa dizer isso:
{ "name": "@inite/club-mcp", "mcpName": "club.inite/inite-club" }
Sem isso, qualquer um poderia anexar o pacote de qualquer outro à sua listagem.
Um namespace que não batia com o domínio sendo provado. O namespace, a chave servida e o domínio no comando de login precisam nomear todos o mesmo domínio. Nós os deixamos em desacordo, e o erro que volta parece um problema de credenciais quando é de nomeação.
Uma URL de repositório que não apontava para nada público. O manifesto carrega um bloco repository, e o nosso nomeava um repositório que não podia ser buscado. Publique o código antes, ou deixe o bloco de fora até ter publicado.
O que faríamos diferente
Escrever o manifesto primeiro, antes de o código estar pronto. Cada uma daquelas quatro rejeições era uma restrição sobre uma decisão que já tínhamos tomado — um nome, um pacote, uma descrição — e cada uma sairia mais barata de atender antes de ter sido escrita em outros três lugares.
E anexar o pacote. Uma listagem só com o remoto é honesta, mas estreita: atende os clientes que já falam streamable HTTP e deixa todos os outros com um arquivo de configuração para escrever à mão. A entrada npm é o que transforma a descoberta em uma instalação.
Estar listado não é o mesmo que ser encontrado
Uma entrada no registry tornou nosso servidor descobrível para os clientes que leem o registry. Sozinha, ela não nos colocou diante de ninguém que estivesse navegando. Os diretórios que as pessoas realmente leem — as awesome-lists, os marketplaces, os sites de índice — mantêm cada um o seu próprio catálogo, e nem todos consomem o oficial.
O registry tem as regras mais claras de todos eles, e é por isso que vale a pena fazê-lo primeiro. Não é a última coisa a fazer.
Em números
- Uma entrada no registry é um manifesto server.json validado contra um JSON schema publicado; as rejeições que recebemos nomeavam o campo problemático em vez de falhar de forma opaca.
- O campo description tem limite de 100 caracteres, o que é menos do que a primeira tentativa da maioria das pessoas.
- Um pacote npm anexado a uma listagem precisa carregar no seu package.json um campo mcpName igual ao nome do servidor, ou o registry não aceita a associação.
- A posse do namespace é provada com uma assinatura Ed25519 contra uma chave pública que o próprio domínio serve em /.well-known/mcp-registry-auth, não com a senha de uma conta.
- Uma listagem pode carregar ao mesmo tempo um endpoint remoto e um pacote: a nossa anuncia streamable HTTP no endpoint e um pacote npm para clientes que só falam stdio.
Perguntas
- Preciso do registry se o meu servidor já está no GitHub?
- Eles respondem a perguntas diferentes. Um repositório diz a uma pessoa onde está o código-fonte. O registry diz a um agente, e aos clientes que procuram em nome de um agente, que existe um servidor com este nome, qual transporte ele fala e qual pacote o instala. Vários diretórios e fluxos de instalação de clientes leem o registry diretamente, então a listagem é como você é descoberto por algo que não é um humano lendo um README.
- Qual é a diferença entre a entrada remota e a do pacote?
- A remota é o servidor em si: nosso clube fala streamable HTTP em uma URL, e um cliente que suporta isso consegue se conectar sem instalar nada. O pacote é o adaptador para os clientes que só falam stdio, que ainda são a maioria. Listar os dois significa que o cliente escolhe aquele que de fato consegue usar, em vez de falhar no que não consegue.
- Por que provar o namespace contra o domínio e não com um login?
- Porque o namespace é uma afirmação sobre um domínio. club.inite afirma que este servidor pertence a quem opera inite.club, e a única parte capaz de demonstrar isso é quem consegue publicar um arquivo nele. A senha de uma conta provaria que você é dono de uma conta, o que é outra coisa. Guarde a chave privada em algum lugar durável: perdê-la significa servir uma nova chave pública antes de poder publicar de novo, e sabemos disso porque foi o que fizemos.
- Como funcionam as atualizações?
- Você publica o manifesto de novo com uma nova versão. O registry mantém a entrada e o nome, então os clientes que fixaram o nome continuam resolvendo. A versão no server.json e a versão do pacote publicado precisam coincidir; uma listagem que aponta para uma versão de pacote que o npm não tem é uma listagem que não instala nada.