Smmly

Документация MCP-сервера

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

Сервер

Адрес
https://smmly.ru/api/mcp
Транспорт
Streamable HTTP. Ответ приходит обычным JSON, не потоком событий — клиентам, которые ждут SSE, ничего настраивать не нужно, они это понимают.
Состояние
Stateless: на каждый запрос поднимается свой сервер, сессий между вызовами нет. Ключ из заголовка превращается в пользователя ДО того, как появятся инструменты, поэтому инструменты всегда замкнуты на владельца ключа.
Авторизация
Authorization: Bearer smk_…
Когда нужен ключ
На любой метод, включая самый первый. Постить без каналов нечего, и пусть агент упрётся в понятный отказ при подключении, а не при первом посте.
GET
405 со ссылкой на инструкцию: эндпоинт отвечает только на POST. Открыли адрес в браузере и увидели ошибку — так и должно быть.
Потолок
60 запросов в минуту на ключ, общий с HTTP-API.
Инструментов
9 — список ниже
Версия протокола
Клиент новее нашего SDK подключится: незнакомую версию протокола мы снимаем с запроса и отвечаем на понятной. Протокол обратно совместим, а иначе агент не подключался бы вообще.

Десять инструментов

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

smmly_channels

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

С него начинается любой разговор: пока агент не увидел ярлыки, адресовать пост ему нечем. Токенов площадок и настроек подключения инструмент не отдаёт.

Таблица листается вбок →

ПолеТипНужноЧто значит
includeDisabledbooleanнетПоказать и выключенные каналы. Нужен ровно в одном случае: канал перестал получать посты и надо понять почему.
Как зовут
«Покажи мои каналы» — и агент увидит список с ярлыками.
Что приходит
channels: по строке на канал — id, provider (telegram / vk / max), name, tags, disabled, needsReauth. Если каналов нет вовсе, вместо пустоты приходит подсказка со ссылкой на кабинет.

smmly_tag_channels

Вешает и снимает ярлыки — те самые, которыми потом адресуются посты.

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

Таблица листается вбок →

ПолеТипНужноЧто значит
channelIdsnumber[]нетКаким каналам менять — id из smmly_channels.
matchTagstringнетВзять все каналы, у которых уже есть этот ярлык. Либо он, либо channelIds — без обоих отказ.
addstring[]нетКакие ярлыки повесить, не трогая остальные.
removestring[]нетКакие снять.
setstring[]нетЗАМЕНИТЬ набор целиком — прежние ярлыки пропадут. Пустой список снимает все. Вместе с add и remove не передаётся.
Как зовут
«Повесь ярлык акции на оба канала кофейни» — агент возьмёт id из списка каналов и передаст их в add.
Что приходит
channels с новыми наборами ярлыков. Если нормализация что-то срезала — придёт note: не больше 6 ярлыков на канал и 24 символов в каждом, регистр не важен («Тест» и «тест» — один ярлык).

set — самое опасное поле сервера: «поставь ярлык акции» через set снимет с канала все остальные ярлыки, и пост, который ходил туда по ярлыку новости, тихо перестанет доходить. Обычный путь — add и remove.

smmly_upload_media

Кладёт файл в хранилище smmly и возвращает готовый блок media для поста.

Для файла, которого нет в интернете: скриншот, только что нарисованная картинка, файл на диске. У кого ссылка есть — тому инструмент не нужен, ссылка кладётся прямо в mediaUrls поста.

Таблица листается вбок →

ПолеТипНужноЧто значит
datastringдаСодержимое файла в base64. Префикс data:…;base64, и переносы строк снимаются сами. Принимаем jpg, png, gif, webp, mp4, mov, webm, pdf.
namestringнетИмя файла — только подпись. На путь хранения не влияет, тип определяем по содержимому, а не по расширению.
Как зовут
«Вот скриншот, приложи его к посту» — агент закодирует файл и передаст в data.
Что приходит
media из одного элемента (type и rel) плюс размер. Этот массив кладётся в smmly_post полем media как есть, ничего в нём менять не нужно.

smmly_upload_widget

Показывает прямо в переписке окно «выберите файл или перетащите сюда» — файл уходит в smmly с вашего устройства.

Файл, который лежит у ВАС на диске, агент в base64 не переложит: содержимое шло бы через его же ответ, а туда не влезают даже сотни килобайт. Окно обходит это стороной — файл летит из браузера прямо к нам, минуя агента, и он получает назад только готовый блок media. Работает там, где чат умеет показывать окна приложений (MCP Apps); где не умеет — остаётся smmly_upload_media.

Как зовут
«Приложи картинку с моего компьютера» — агент откроет окно, вы выберете файл, дальше он сам предложит поставить пост.
Что приходит
Само окно. Загруженное оно передаёт агенту готовым блоком media — там же, в переписке. Рядом живут два служебных инструмента: один выдаёт окну пропуск на загрузку (агенту он не показывается), второй перечисляет загруженное за этот разговор, если чат потерял контекст.

smmly_post

Публикует сейчас во все каналы, попавшие в отбор. Один вызов — один пост во все выбранные каналы сразу.

Основной инструмент. Пост встаёт в ту же очередь, что и написанный руками в кабинете, и уходит в течение минуты.

Поля — общий набор публикации.

Как зовут
«Запости это в новости» — агент возьмёт ярлык и текст.
Что приходит
groupId и по строке на канал. По groupId дальше смотрится судьба поста, им же адресуются правка и снятие.

smmly_schedule_post

Тот же пост, но с датой публикации.

Отдельный инструмент, а не флаг: агент, у которого «опубликовать» и «отложить» — одно действие с необязательным полем, регулярно теряет это поле и публикует сейчас.

Поля — общий набор публикации, плюс своё:

Таблица листается вбок →

ПолеТипНужноЧто значит
publishAtstringдаКогда публиковать. ISO 8601 ОБЯЗАТЕЛЬНО со смещением зоны: 2026-09-06T09:00:00+03:00 — это 9 утра по Москве. Время в прошлом = публикация сейчас.
Как зовут
«Поставь этот пост на завтра на девять утра» — агент обязан подставить смещение вашей зоны, а не только время.
Что приходит
То же, что у публикации: groupId и строки по каналам, только state будет очередью с временем.

smmly_post_status

Состояние поста по каждому каналу: очередь, публикация, ошибка площадки, ссылка на опубликованное.

Публикация возвращает «принято», а не «опубликовано». Разница в минуту и в отказе площадки — и узнать про отказ можно только здесь.

Таблица листается вбок →

ПолеТипНужноЧто значит
groupIdstringдаИз ответа smmly_post или smmly_schedule_post. Формат — uuid, иначе отказ.
Как зовут
«Пост-то вышел?» — агент сходит с groupId и прочитает состояния.
Что приходит
По строке на канал: state, publishAt, publishedAt, externalUrl, error, число попыток.

smmly_posts

Список постов этого ключа — что стоит в отложке и что уже ушло.

Вход в правку. Между сессиями агент groupId не помнит, а правка и снятие адресуются именно им — без этого инструмента поправить вчерашнюю опечатку было бы нечем.

Таблица листается вбок →

ПолеТипНужноЧто значит
scopescheduled | published | allнетПо умолчанию scheduled — ещё не ушедшее, ближайшее сверху. published — опубликованное, свежее сверху.
limitnumberнетСколько постов вернуть: от 1 до 50, по умолчанию 20.
Как зовут
«Что у меня стоит на этой неделе?»
Что приходит
Замыслы: groupId, состояние (mixed, если каналы разошлись), время, обрезанный текст и строки по каналам. Полный текст — в самом посте, в списке он подрезан.

smmly_edit_post

Меняет уже созданный пост во всех каналах замысла сразу.

Агент, который умеет поставить пост на завтра, но не умеет поправить в нём опечатку, отправляет человека доделывать в кабинет — то есть не работает.

Таблица листается вбок →

ПолеТипНужноЧто значит
groupIdstringдаКакой замысел правим — из smmly_posts или из ответа публикации.
textstringнетНовый текст на все каналы.
textByProviderобъектнетНовый текст под площадку — ключи default, telegram, vk, max.
formatplain | htmlнетРазметка нового текста. Не передали — останется прежняя.
titlestringнетЗаголовок. Пустая строка снимает его.
mediaUrlsstring[]нетЗаменить вложения ссылками. Только у неушедших постов.
mediaобъект[]нетЗаменить вложения загруженными. Только у неушедших; пустой список снимает вложения.
buttonsобъект[]нетЗаменить кнопки. Пустой список снимает их.
firstCommentstringнетВторое сообщение. У опубликованного поста не меняется — его правят прямо в канале.
deleteAfterHoursnumberнетПоставить или сдвинуть автоснос: через столько часов после публикации пост уберут из канала. От минуты до 47 часов, дробные можно: сообщение старше двух суток телеграм снять уже не даёт. 0 или null снимают срок — пост остаётся навсегда. У уже опубликованного срок считается от публикации, а не от правки.
publishAtstringнетПеренести отложенный пост. Тот же ISO 8601 со смещением. Время в прошлом = отправить сейчас.
retrybooleanнетВернуть в очередь упавшие при отправке — новая попытка с исправленным текстом. Ушедших и черновиков не касается.
Как зовут
«Поправь в завтрашнем посте цену на 450» — агент найдёт замысел через smmly_posts и передаст только text.
Что приходит
Построчно по каналам: ok или error у каждого. Часть могла принять правку, часть отказать — при частичном успехе приходит note «приняли столько-то из стольких-то».

smmly_cancel_post

Убирает пост целиком, по всем каналам замысла.

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

Таблица листается вбок →

ПолеТипНужноЧто значит
groupIdstringдаКакой замысел снимаем.
fromChannelbooleanнетСтереть пост и из самого канала, а не только из журнала smmly. НЕОБРАТИМО, по умолчанию выключено.
Как зовут
«Отмени завтрашний пост» — снимется с очереди и не уйдёт.
Что приходит
Построчно по каналам, как у правки: снято с очереди или удалено из сети.

Уже опубликованное по умолчанию остаётся в канале: из журнала smmly пропадёт, из канала — нет. Сносить опубликованное умеет только телеграм и только сообщения не старше двух суток.

Общий набор полей публикации

Одинаков у smmly_post иsmmly_schedule_post: у второго к нему добавляется только время. Нужен текст (одним из двух полей) и адресация (одним из трёх) — остальное по желанию.

Таблица листается вбок →

ПолеТипНужноЧто значит
textstringодно изТекст поста, один на все площадки. Нужен либо он, либо textByProvider — без текста постить нечего.
textByProviderобъектодно изСвой текст под площадку, когда один на всех не годится: ключи default, telegram, vk, max.
tagsstring[]одно изЯРЛЫКИ каналов — основной способ выбора. Пост уйдёт во все живые каналы, у которых есть любой из перечисленных.
providersstring[]одно изВыбор площадкой целиком: telegram, vk, max — все телеграм-каналы, все сообщества ВК.
channelIdsnumber[]одно изТочечный выбор по id. Нужен редко. Хотя бы одно из трёх полей адресации обязано быть — иначе отказ.
formatplain | htmlнетРазметка. html понимают телеграм и MAX; ВК получит тот же текст без тегов. По умолчанию plain.
titlestringнетЗаголовок — там, где площадка его различает. Сегодня не различает ни одна из подключаемых: поле в контракте есть, но ни на что не влияет.
mediaUrlsstring[]нетВложения публичными ссылками, до 10. Файлы мы скачиваем сами.
mediaобъект[]нетВложения, загруженные через smmly_upload_media или окно smmly_upload_widget — массив из ответа как есть. Складывается с mediaUrls, вместе не больше 10.
buttonsобъект[]нетКнопки-ссылки под постом: text и url. Вешает только телеграм, до 8, по одной в ряд; ВК и MAX получат пост без них.
firstCommentstringнетВторое сообщение ответом на пост. Умеет только телеграм, остальные молча его не получат.
deleteAfterHoursnumberнетСнести пост из канала через столько часов после публикации — для рекламы, купленной на срок. Дробные можно: 0.5 — полчаса, от минуты до 47 часов. Не передали — пост висит в канале вечно, как обычно. Умеет только телеграм и только пока сообщению меньше двух суток: канал ВК, MAX или X с этим полем откажется принять пост.
idempotencyKeystringнетВечный ключ повтора: вызовы с одним ключом создают пост один раз, повторный вернёт duplicate и groupId первого. Берите id события («дивиденды SBER за 2026»), а не случайную строку.

Как адресуются каналы

Три способа сказать «куда», и они складываются: канал попадёт в отбор, если подошёл хотя бы по одному. Хотя бы один способ обязан быть указан.

  1. Ярлык — основной путь

    Ярлык вешается на канал в кабинете или черезsmmly_tag_channels, и постом адресуется он, а не номер. Смысл простой: канал, подключённый завтра и помеченный тем же ярлыком, подхватится сам — переписывать промпт агенту не придётся. Ярлыки нечувствительны к регистру, до 24 символов, не больше 6 на канал.

  2. Площадка целиком

    Все телеграм-каналы, все сообщества ВК, все чаты MAX. Грубо, но иногда ровно то, что нужно: «продублируй во все телеграмы».

  3. Список id — запасной

    Точечно, когда ярлыка нет. Хрупко: id приходится держать в промпте, а канал могут удалить — тогда придёт отказ со списком ненайденных.

Время публикации

publishAt — ISO 8601 обязательно со смещением зоны:2026-09-06T09:00:00+03:00 — это девять утра по Москве.

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

Время в прошлом ошибкой не считается — отправщик возьмёт такой пост первым же тиком. Перенести уже поставленный пост можно тем же полем черезsmmly_edit_post.

Вложения

Три пути: публичная ссылка в mediaUrls — если файл уже в интернете; загрузка через smmly_upload_media — если файл есть у агента; окно smmly_upload_widget — если файл лежит у вас на устройстве. Вместе не больше десяти на пост.

Правка и снятие

И то и другое адресуется groupId — идентификатором замысла, а не отдельного поста в канале. Меняется сразу всё, что из этого замысла ушло.

Ограничения

Повтор вызова: защита есть, но короткая

Одинаковый вызов в те же каналы в пределах пяти минутвторого поста не создаёт: вернётся duplicate и группа уже созданного. Это ловит ровно один случай — вызов отвалился по таймауту уже ПОСЛЕ создания поста, и агент честно повторил его теми же аргументами.

Дальше окно кончается: тот же текст в те же каналы через час — не ретрай, а осознанный повтор, и он опубликуется. «Одинаковый» тоже понимается буквально: агент, который перед повтором перегенерировал текст, отправит уже другой пост. Когда задвоение недопустимо в принципе — передавайтеidempotencyKey с идентификатором события: он вечен. В кроне вместо этого проще взятьHTTP-вход с паройsource + externalKey.

Что означает отказ

Строки ниже — те самые, что агент покажет вам в чате.

Частые затыки

Агент говорит «пост опубликован», а в канале пусто

Инструмент публикации возвращает «принято в очередь», а не «вышло», и агент честно пересказывает этот ответ. Отправка идёт в течение минуты, а отказ площадки виден только в состоянии поста. Попросите агента проверить через smmly_post_status по groupId — там будет и текст ошибки, и ссылка на опубликованное.

Пост ушёл на три часа раньше, чем я просил

Классика: publishAt без смещения зоны. Время без хвоста +03:00 читается как всемирное, и «девять утра» превращается в шесть по Москве. Правильный вид — 2026-09-06T09:00:00+03:00. Если агент уже поставил пост криво, время переносится через smmly_edit_post тем же полем.

Агент повторил вызов и создал два поста

Одинаковый запрос в те же каналы в пределах пяти минут повтором не считается — вернётся duplicate и второй пост не создастся. Но окно короткое, а «одинаковый» значит побайтово: агент, перегенерировавший текст, отправит уже другой пост. Когда задвоение недопустимо в принципе, передавайте idempotencyKey — он вечный.

Почему агент не может подключить мне канал?

Наружу это не выставлено, и не по недосмотру. Подключение канала — живые шаги в чужих интерфейсах: диплинк в телеграме, вход во ВКонтакте через браузер, добавление бота руками в MAX. Ключ даёт право вести каналы, а не управлять аккаунтом; выдача ключей, тариф, оплата и токены площадок закрыты по той же причине.

Агент снял пост, а в канале он остался

Так и задумано. По умолчанию снятие убирает пост из журнала smmly и с очереди, но опубликованное из канала не стирает — это необратимо, а «убери этот пост» слишком часто значит «сними с очереди». Стереть из канала можно, но агент должен передать fromChannel явно, и лучше, чтобы он у вас перед этим спросил.

Клиент не подключается: сервер отвечает 405

405 приходит на GET — например, если адрес открыли в браузере. MCP-эндпоинт отвечает только на POST. Если же приходит 401 при подключении, дело в ключе: он обязателен на любой метод, включая самый первый.

Куда дальше