Я сделала для своего продукта MCP‑сервер. Написание и проверка кода заняла один вечер — писал конечно агент.

А вот «остальное» оказалось решениями о составе и видимости инструментов и вещами, которые невозможно было запланировать заранее — только узнать постфактум, и почти всегда по итогам чьей‑то реакции снаружи.

Что делал агент, и что осталось мне

Агент написал эндпоинт. MCP поверх streamable HTTP — это JSON‑RPC в теле POST: клиент просит сервер представиться, просит список инструментов, потом вызывает их по одному. Словари на вход, словари на выход. Фреймворк не нужен, SDK не нужен. Поверх существующего бэкенда добавился один маршрут, никаких новых зависимостей.

Мне осталось то, что агент не может решить за меня, потому что у него нет ни контекста продукта, ни понимания целей.

Сколько инструментов и какие. Четыре‑восемь — нормальное количество. Каждый дополнительный инструмент будет ещё одной сущностью, которую модель на той стороне должна выбрать правильно. Меньше инструментов — модели будет проще выбирать.

Правила доступа. Какие‑то вещи может прочитать любой, какие‑то — только авторизованный пользователь, причем надо еще проверять, есть ли у него права на этот ресурс.

Имена. Скоринг в каталогах поощряет имена, которые читаются деревом — точечный путь вроде stories.search. Мой листинг набрал 98 из 100 в списке smithery.ai как раз из‑за этого правила. При этом, если добавлять сервер в библиотеку серверов ChatGPT, то наоборот такой листинг не будет принят — ^[a‑zA‑Z0-9_‑]{1,64}$, точка отбрасывается. Критерий, который стоит осознанно провалить.

А главное — имена нельзя менять потом. Как только вас просканировал реестр, вы появились в каталоге и ваша заявка принята, каждое опубликованное имя становится именем, которое придётся хранить: его несут листинги, заявки и чужие сохранённые конфигурации. Это уже не рефакторинг, а миграция по системам, которыми вы не управляете. Решение на пятнадцать минут, которое потом будет сложно поменять, — и агент про это не предупредит, потому что не знает, что будет дальше.

Аутентификация

Наверно, это то, что что было самым трудным. Что надо учесть:

Первое: узнать, кто пришёл. Серверу нужно понимать, от чьего имени его зовут: публиковать и править истории можно только свои, а приватную историю может прочитать только её автор. У обычного API тут хватило бы ключа, который человек сам прописывает в настройках или переменных окружения. С MCP так почти никогда не выйдет: Claude или ChatGPT подключаются к серверу сами, и вставить ключ некуда. Им нужен вход как на сайтах: человек нажимает «подключить», видит экран «разрешить доступ» и возвращается уже под своим именем. Для этого придётся поднять собственный сервер авторизации по стандарту OAuth. Это самая дорогая часть всего проекта, и отвечать за её безопасность теперь вам.

Второе: решить, что входа не требует. Добавив авторизацию, я сначала закрыла весь доступ для неавторизованных клиентов — включая список того, что сервер умеет. Логика казалась безупречной. В результате после регистрации в одном из каталогов, я увидела только название сервиса, описание и ноль инструментов, потому что зеркалу реестра некем авторизоваться. Починка — около пятнадцати строк, правило простое: анонимно отдавайте ровно то, что и так публично в других местах.

Третье: дать вход постороннему. Ревьюер магазина приложений должен воспользоваться сервером. Если единственный вход — через какую‑либо соц‑сеть, такой вариант не подойдет для проверок. Нужно сделать специальный аккаунт для песочницы.

Четвёртое: иметь правильный токен для публикации самому. В официальном реестре MCP сервер публикуется под именем GitHub‑аккаунта: личного или организации. Мой живёт под организацией — io.github.worklore/worklore. Чтобы пустить вас в пространство организации, реестр проверяет, что вы её владелец, а для этого нужен GitHub‑токен с правом read:org. Обычный вход через mcp‑publisher выдаёт токен без него. И ошибка не говорит «токену не хватает прав» — она отвечает 403 и сообщает, что публиковать можно только под вашим личным логином. Я решила, что дело в видимости принадлежности к организации, но ничего не изменилось. Помог токен GitHub CLI, в котором read:org уже есть: mcp‑publisher login github ‑token “$(gh auth token)”.

Сколько стоит это поддерживать

MCP‑сервер — небольшой сервис с редкой и неравномерной нагрузкой: большую часть времени он простаивает и запускается, только когда к нему обращается чей‑то агент. Сами вычисления стоят копейки в месяц и обычно укладываются в бесплатные лимиты облака. Листинг в официальном реестре — бесплатно. Заявка на приложение в ChatGPT — бесплатно. Единственная небесплатная строка у меня — каталог коннекторов Claude: нужна Team‑организация, около $40 в месяц при минимуме в два места.

Постоянная стоимость — не про вычисления. Она про то, что вам нужно поддерживать сервер авторизации, что каждый релиз клиента может вскрыть новую несовместимость, и что без логирования запросов вы не знаете, кто что вызывает.

Чего нельзя было запланировать

Я довольно много планировала с агентом. Список инструментов, схема имён, что пишет и что читает, порядок работ — всё это мы разобрали заранее, и разобрали неплохо. Но реальные корректировки были сделаны уже после публикации.

Логи показали клиента, который зовёт метод, которого у меня нет. Я добавила по одной структурированной строке на вызов на сервер, которым почти никто не пользовался, — и это выглядело бессмысленно. Через день выяснилось, что клиент регулярно зовёт метод из более новой ревизии протокола, получает ошибку и молча откатывается на старый путь. Внешне не ломалось ничего. Ни один тест этого не искал, потому что никто не знал, что искать.

Каталог (или MCP Inspector без авторизации) показывал мой сервер без инструментов — я узнала об этом, открыв его страницу, а не из своего кода.

Ревью отклонило заявку за документ. Политика конфиденциальности описывала сервер, которого уже не существовало: её писали, когда коннектор был четырьмя read‑only инструментами без входа, а к моменту проверки это были шесть инструментов за OAuth, два из которых пишут от имени пользователя. Отказ был на одно предложение и абсолютно справедливый. Ревьюер читает, сверяя её с живым эндпоинтом. Вы пишете политику и забываете ее актуализировать.

И самое интересное — тестирование настоящим клиентом. Я уже поделилась рассказом о создании MCP‑сервера на dev.to и пришёл комментарий: человек описал, как тестировать MCP‑сервер так, как это делает настоящий клиент — попросить список инструментов и проверять каждый ответ по той схеме, которую сервер только что сам и отдал. Я попробовала и нашла несколько ошибок, которые уже несколько дней жили на проде. Несмотря на то, что уже было написано 79 зелёных тестов. Что еще раз подтверждает: тестировать надо не код (который агент обожает тестировать по фактически написанному), а реальные соединения. Ну и конечно же разговаривать о проекте, делиться опытом и получать обратную связь.

Немного подробнее про ошибки, потому что они тоже были интересными. Написали тест, прогнали — и он прошёл. До исправления. Потому что мок базы отдаёт обычные целые числа, а настоящая база — свой десятичный тип, и тест проходил за счет неверных предположений о формате данных в БД. 

Простейшим решением было расставить неудобный тип руками там, где он важен. Это оказалось слабым решением, и объяснил это тот же комментатор: фикс, написанный руками, наследует предположения автора, а не типы продакшена, — то есть фиксит ту ошибку, о которой вы уже знаете. Правильное решение в том, чтобы фейк приводил типы сам, как это делает настоящий драйвер. 

И одна мелочь, без которой вся проверка ничего не стоит. Когда инструмент в MCP падает, сервер не возвращает ошибку протокола — он присылает обычный ответ с пометкой isError: true и текстом ошибки внутри. Данных, которые можно сверить со схемой, в таком ответе нет. Если тест сразу переходит к сверке, ему нечего проверять, и он засчитывает вызов как успешный. Поэтому сначала убедитесь, что в ответе нет isError, и только потом сверяйте его со схемой.

И листинг, который отстал от сервера. История с устаревшей политикой конфиденциальности повторилась еще в одном месте. В официальном реестре мой сервер значился как версия 0.1.0: четыре инструмента, все только читают. За две недели за тем же адресом их стало шесть, два из них пишут от имени пользователя, а версия так и осталась 0.1.0. server.json в реестре не хранит никакой поверхности сервера — только имя, версию и адрес, — так что сравнивать можно только версию, а версия меняется, только если вы вспомнили её поднять.

Подсказал снова читатель статьи — Сидней Биссоли, автор MCP‑серверов с открытыми данными Центрального банка Бразилии. Его идея: хешировать ответ initialize вместе со списками инструментов, ресурсов и промптов, закоммитить хеш рядом с версией и валить сборку, если хеш изменился, а версия нет. Отлично помогает от забывчивости (и моей, и агента).

Возможно, публиковаться вам и не нужно

Заметная часть сложностей выше — не про сам сервер, а про публикацию: реестр, каталоги, ревью магазина приложений, аккаунт для ревьюера, политика конфиденциальности, токен для публикации. И эта часть необязательна. Без неё сервер всё равно работает: он отдает данные, умеет рассказать о себе, виден в логах. Многие хорошие серверы на этом и останавливаются.

Если ваши пользователи и так живут в терминале или IDE или готовы вставить адрес сервера в настройки Claude или ChatGPT, — вы уже закончили: опубликуйте адрес и одну строку установки в README, и они добавят сервер себе секунд за десять. Ни форм, ни ревьюера, ни ожидания. Для внутреннего инструмента или аудитории, которую вы знаете в лицо, это не «вариант попроще», а правильный вариант.

Публикация в реестре и каталогах дает только одно: чтобы вас нашёл человек, который про вас никогда не слышал. Сплошной маркетинг.

Единственное число, которое имеет значение

Настоящий вопрос не инженерный, и ни один каталог на него не ответит: подключался ли хоть кто‑то, кроме вас?

Мои цифры с 16 сентября по 1 октября: больше 3700 подключений — клиент подключается, спрашивает список инструментов и ресурсов, и так снова и снова. Первые двенадцать дней подключений было по нескольку десятков в день, а с 28 сентября — по 700–1000 в день. И 147 настоящих вызовов инструментов. Все 147 сделала либо я сама — с основного и тестового аккаунтов, — либо ревьюер ChatGPT под демо‑аккаунтом, который я для него завела. Настоящих пользователей — пока ноль. Эти цифры открыты и обновляются каждый день: worklore.dev/stats.html.

Причём эти тысячи подключений — не люди. Почти все они — боты каталогов и мониторингов: проверяют, жив ли сервер, и ни разу не вызывают инструмент. Скачок случился на следующий день после того, как я открыла список инструментов без входа: сервер начал им отвечать. Вот кто подключался к серверу с 28 сентября по 2 октября:

Клиент

Подключений

Кто это

mcpbeat

838

мониторинг, в своём заголовке пишет «liveness check»

Python aiohttp, без имени

659

неизвестный сканер, только запрашивает список инструментов

node, без имени

580

неизвестный сканер

SentinelOracle

446

мониторинг: «liveness‑only, never invokes tools»

rokmcp‑collector

216

собирает каталог серверов

BrickBlueBot

146

реестр «agentic web»

TalandorBot и другие

десятки

тоже боты

Claude (мой тестовый аккаунт)

114

единственный человек в списке — я

«Подключились» и «пользуются» — разные числа, и пока вы их не логируете, вы их не различаете.

Мой опыт

Шесть вещей, которые я бы сделала иначе, если бы начинала заново:

  1. Установила бы имена инструментов заранее в документе, а не в коде. Без веских причин потом их нет смысла менять, на них будет опираться все остальное.

  2. Включала бы логирование запросов в первом же деплое. Одна структурированная строка на вызов: время, метод, инструмент, кто вызвал, успех или ошибка. Без этого вы не знаете о своём сервере ничего.

  3. Решала бы отдельно, что отдаётся без токена. Список инструментов — это описание сервера, а не данные: закрывать его незачем. Часть данных тоже можно отдать без токена как публично‑доступные.

  4. Сделала бы мок базы на основе реального ответа БД: пусть он сам приводит типы, а не полагается на то, что автор теста о них вспомнил.

  5. Завела бы проверку, которая не разрешает деплой, если инструменты или их описание изменились, а версия сервера нет. Версия — единственное, что реестр хранит о вашем сервере, и держится она только на памяти.

Пошаговый разбор с инструкциями для агента и описанием для человека лежит здесь: Build and ship your own MCP server.

Исходники сервера открыты, там же и тесты — в том числе тот, который ходит по серверу как клиент: github.com/worklore/worklore‑mcp.

Комментарии (0)