СодержаниеРазделов: 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?