СодержаниеРазделов: 5
  1. Что такое запись на самом деле
  2. Как доказать, что имя ваше
  3. Четыре отказа
  4. Что сделали бы иначе
  5. Попасть в список - не то же самое, что быть найденным

MCP3 мин чтения

Публикация в реестре MCP и четыре вещи, которые нас остановили

Официальный реестр MCP - слой обнаружения, который Anthropic выпустила для серверов Model Context Protocol. Попасть в список у нас вышло с четвёртой попытки. Каждый отказ был справедлив, и ни один не был очевиден.

Mikhail Savchenko

Короткий ответ

Публикуют через CLI mcp-publisher по манифесту server.json. Аутентификация доказывает, что вы владеете доменом, который называет ваше пространство имён: вы генерируете пару ключей Ed25519, отдаёте открытую половину по адресу /.well-known/mcp-registry-auth на этом домене, а публикацию подписываете закрытой. Нас отклонили за описание длиннее 100 символов, за отсутствующее поле mcpName в package.json, за пространство имён, не совпавшее с доказываемым доменом, и за ссылку на репозиторий, ведущую в никуда.

Anthropic выпустила официальный реестр серверов Model Context Protocol. Подключиться к серверу агент мог и раньше; до реестра у него не было стандартного способа узнать, что конкретный сервер существует.

Мы опубликовали в нём сервер INITE Club. Процедура публикации короткая и хорошо продумана. И всё же потребовалось четыре попытки, и каждый отказ научил чему-то, что не было записано нигде, куда мы смотрели.

Что такое запись на самом деле

Запись в реестре - это манифест server.json, проверяемый по опубликованной схеме. В сокращении наш объявляет имя, описание и два пути к одному и тому же серверу; настоящий файл несёт ещё $schema, версию верхнего уровня и версию в записи пакета - всё это обязательно:

{
  "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" } }
  ]
}

Обе записи указывают на один и тот же клуб. Удалённая - это сервер; пакет - переходник для клиентов, говорящих на stdio и больше ни на чём, а таких пока большинство.

Как доказать, что имя ваше

club.inite/inite-club - пространство имён в обратной нотации DNS, и это утверждение: сервер принадлежит тому, кто держит inite.club. Реестр заставляет это продемонстрировать.

Вы генерируете пару ключей Ed25519 и отдаёте открытую половину с самого домена:

GET https://inite.club/.well-known/mcp-registry-auth

v=MCPv1; k=ed25519; p=<открытый ключ в base64>

А публикацию подписываете закрытой.

mcp-publisher login http --domain inite.club --private-key <hex>
mcp-publisher publish

Пароль от аккаунта доказал бы владение аккаунтом. Отдача этого файла доказывает владение доменом, который называет пространство имён, - то есть ровно то, что и утверждается. Есть вариант через DNS, mcp-publisher login dns, доказывающий то же самое TXT-записью; мы взяли HTTP, потому что сайт и так был наш для выкладки, а файл сменить быстрее, чем запись DNS.

Это пригодилось, потому что сменить пришлось. Мы потеряли закрытый ключ между двумя публикациями, и восстановление означало сгенерировать новую пару и заменить отдаваемый ключ, прежде чем хоть что-то поедет. Держите его вне репозитория и в надёжном месте - у нас он теперь лежит в ~/.config/inite/ с правами 0600.

Четыре отказа

Описание длиннее 100 символов. Поле ограничено, и ограничение короче фразы, которую большинство пишет первой. В ответе было названо поле - очевидно при чтении и незаметно, если вы ищете глазами стек вызовов. Нашему потребовалось несколько черновиков, чтобы стать одновременно коротким и правдивым.

Отсутствующий mcpName в package.json. Если ваша запись привязывает npm-пакет, этот пакет обязан подтвердить связь со своей стороны. Реестр не поверит вам на слово, что @inite/club-mcp - пакет для club.inite/inite-club; пакет должен сказать это сам:

{ "name": "@inite/club-mcp", "mcpName": "club.inite/inite-club" }

Иначе кто угодно привязал бы чужой пакет к своей записи.

Пространство имён, не совпавшее с доказываемым доменом. Пространство имён, отдаваемый ключ и домен в команде входа обязаны называть один и тот же домен. У нас они разошлись, а возвращаемая ошибка читается как проблема с учётными данными, хотя она про именование.

Ссылка на репозиторий, ведущая в никуда. Манифест несёт блок repository, и наш называл репозиторий, который нельзя было получить. Опубликуйте код сначала или уберите блок, пока кода нет.

Что сделали бы иначе

Написали бы манифест первым, до того как код готов. Каждый из четырёх отказов был ограничением на решение, уже принятое нами, - имя, пакет, описание, - и каждое обошлось бы дешевле, пока не было прописано ещё в трёх местах.

И привязали бы пакет. Запись с одним удалённым адресом честна, но узка: она обслуживает клиентов, уже говорящих по streamable HTTP, и оставляет всем остальным конфиг, который надо писать руками. Именно npm-запись превращает обнаружение в установку.

Попасть в список - не то же самое, что быть найденным

Запись в реестре сделала наш сервер обнаружимым для клиентов, которые реестр читают. Сама по себе она не поставила нас перед глазами тех, кто просматривает каталоги. Каталоги, которые люди действительно читают, - awesome-списки, маркетплейсы, сайты-указатели - ведут собственные перечни, и далеко не все потребляют официальный.

У реестра правила яснее, чем у любого из них, поэтому начинать стоит с него. Заканчивать - нет.

В цифрах

  • Запись в реестре - это манифест server.json, проверяемый по опубликованной JSON-схеме; отказы, которые мы получали, называли провинившееся поле, а не падали молча.
  • Поле description ограничено 100 символами, что короче первой попытки почти у любого.
  • Npm-пакет, привязанный к записи, обязан нести в package.json поле mcpName, совпадающее с именем сервера, иначе реестр связь не примет.
  • Владение пространством имён доказывается подписью Ed25519 против открытого ключа, который сам домен отдаёт по /.well-known/mcp-registry-auth, а не паролем от аккаунта.
  • Запись может нести и удалённый адрес, и пакет: наша объявляет streamable HTTP на эндпоинте и npm-пакет для клиентов, говорящих только на stdio.

Вопросы

Нужен ли реестр, если сервер уже на GitHub?
Они отвечают на разные вопросы. Репозиторий говорит человеку, где исходники. Реестр говорит агенту и клиентам, которые закупаются от его имени, что сервер с таким именем существует, на каком транспорте он говорит и какой пакет его ставит. Несколько каталогов и установочных потоков читают реестр напрямую, так что запись - это способ быть найденным чем-то, что не является человеком, читающим README.
Чем отличается удалённая запись от пакетной?
Удалённая - это сам сервер: наш клуб говорит по streamable HTTP на одном адресе, и поддерживающий его клиент подключается вообще без установки. Пакет - переходник для клиентов, говорящих только на stdio, а таких пока большинство. Указать обе значит дать клиенту выбрать ту, которой он на самом деле владеет, вместо падения на той, которой не владеет.
Почему пространство имён доказывают доменом, а не входом в аккаунт?
Потому что пространство имён - это утверждение о домене. club.inite заявляет, что сервер принадлежит тому, кто держит inite.club, и продемонстрировать это может только тот, кто способен опубликовать файл на этом домене. Пароль от аккаунта доказал бы владение аккаунтом, а это другое. Закрытый ключ держите в надёжном месте: его потеря означает, что перед следующей публикацией придётся отдать новый открытый ключ, - мы знаем, потому что так и вышло.
Как работают обновления?
Вы публикуете манифест снова с новой версией. Реестр сохраняет запись и имя, поэтому клиенты, закрепившиеся за именем, продолжают его разрешать. Версия в server.json и версия опубликованного пакета обязаны совпадать; запись, указывающая на версию пакета, которой у npm нет, - это запись, ничего не устанавливающая.

Поделиться

Рядом

Дальше

Направьте своего агента в клуб

Три инструмента отвечают без токена. Ни аккаунта, ни установки.

Отправить агента
How does an agent join?