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_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нетВторое сообщение. У опубликованного поста не меняется — его правят прямо в канале.
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 — массив из его ответа как есть. Складывается с mediaUrls, вместе не больше 10.
buttonsобъект[]нетКнопки-ссылки под постом: text и url. Вешает только телеграм, до 8, по одной в ряд; ВК и MAX получат пост без них.
firstCommentstringнетВторое сообщение ответом на пост. Умеет только телеграм, остальные молча его не получат.
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 — если нет. Вместе не больше десяти на пост.

  • Файл передаётся в base64, целиком, одним сообщением протокола. Потолок на вызов — 30 МБ закодированного текста, то есть примерно 22 МБ самого файла. Больше — отказ, и это не наша жадность: тело запроса лежит в памяти целиком, а веб-сервер режет запросы на 55 МБ.
  • Дальше действуют обычные пределы хранилища: картинка до 10 МБ, видео и pdf до 50. Через MCP до пятидесяти вы не дотянетесь — большое видео придётся выложить по ссылке и передать mediaUrls.
  • Публичной ссылки на загруженный файл не появляется, и она не нужна: площадке файл уходит содержимым, а не адресом. Инструмент возвращает внутренний блок media — его переносят в пост как есть.
  • Загруженное и никуда не приложенное живёт сутки и удаляется. Попавшее в пост живёт вместе с постом — включая отложенный: ссылку на файл он ставит сразу, поэтому вложение у него не пропадёт. А вот загрузить файл сегодня, чтобы приложить его к посту завтра, нельзя.
  • Тип файла определяется по содержимому, а не по имени: принимаем jpg, png, gif, webp, mp4, mov, webm, pdf. Имя — только подпись.

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

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

  • Ответ построчный. У каждого канала своиok или error: телеграм правку принял, ВК отказал — и это нормальный, ожидаемый исход, а не сбой. При частичном успехе приходит отдельная строка «приняли столько-то из стольких-то» именно затем, чтобы агент не отчитался об успехе за половину сделанного.
  • Неушедший пост правится целиком: текст, разметка, вложения, кнопки, время публикации.
  • Опубликованный переписывается прямо в канале, и там меняется только текст — вложения и второе сообщение площадка подменить не даёт. Переписывать опубликованное умеет один телеграм; ВК и MAX ответят отказом, и там пост придётся снять и опубликовать заново.
  • Телеграм не даёт боту трогать сообщения старше двух суток — ни править, ни удалять. Позавчерашнюю опечатку исправить уже нельзя.
  • Пост, который отправщик уже взял в работу, не правится и не снимается: попробуйте через минуту, когда он опубликуется.
  • Снятие по умолчанию не трогает канал: пост уходит из журнала smmly и с очереди, но опубликованное остаётся на месте. Стереть из канала — отдельный явный флаг, необратимо.

Ограничения

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

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

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

  • Подключить или отключить канал нельзя. Это живые шаги в чужих интерфейсах — диплинк в телеграме, вход во ВКонтакте через браузер, добавление бота руками в MAX. Только кабинет.
  • Выдать или отозвать ключ нельзя, как и посмотреть тариф, оплатить подписку или получить токены площадок. Ключ даёт право вести каналы, а не управлять аккаунтом.
  • Черновик из агента положить нельзя. Публикация всегда встаёт в очередь; поле состояния наружу не выставлено. Без действующей подписки это означает отказ, а не «сохранили на потом».
  • Сессий нет. Сервер не помнит предыдущий вызов, иgroupId между разговорами не сохраняется — искать пост придётся через smmly_posts.
  • Заголовок поста ничего не делает. Поле в схеме есть, но ни одна из подключаемых площадок отдельного заголовка не различает. Всё решает текст.
  • Отказ площадки приходит не сразу. Инструмент публикации возвращает «принято в очередь». Что пост реально вышел — видно только в его состоянии.

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

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

  • Нужен ключ: заголовок Authorization: Bearer smk_…

    Клиент не передал ключ вовсе. Проверьте конфиг клиента: у большинства это блок headers рядом с адресом сервера.

  • Ключ недействителен

    Ключа нет в базе или он удалён в кабинете. «Нет такого» и «отозван» снаружи не различаются намеренно. Выпустите новый на странице ключей.

  • Подтвердите почту аккаунта

    Тот же гейт, что и в кабинете: неподтверждённый ящик не постит. Ключ его не обходит.

  • Нет действующей подписки

    Постановка в очередь закрыта. Из MCP это тупик: черновики агенту наружу не выставлены, так что пост придётся положить в кабинете или продлить подписку.

  • Слишком часто: не больше 60 запросов в минуту на ключ

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

  • Не указано, куда постить

    Ни ярлыков, ни площадок, ни id. Агент не сходил в smmly_channels — попросите его сначала показать каналы.

  • Под этот отбор не нашлось ни одного живого канала

    Ярлык написан не так, как в кабинете, либо все каналы с ним выключены. Список ярлыков — в smmly_channels.

  • Канал не найден, удалён или выключен

    Постинг по id в канал, которого больше нет. В ответе придёт список missing. Ещё одна причина адресовать ярлыками.

  • Пост не проходит по площадкам

    Проверка длины, вложений и кнопок идёт по ВСЕМ выбранным каналам ДО записи, и не прошло по одной — не создаём ничего. В issues лежит по строке на канал, что именно не так.

  • Не разобрали publishAt

    Время не в ISO 8601. Нужен формат со смещением зоны: 2026-09-06T09:00:00+03:00.

  • Файл слишком большой для загрузки через MCP

    Не пролезает одним сообщением протокола. Выложите файл по публичной ссылке и передайте mediaUrls.

  • Внутренняя ошибка smmly

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

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

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

Инструмент публикации возвращает «принято в очередь», а не «вышло», и агент честно пересказывает этот ответ. Отправка идёт в течение минуты, а отказ площадки виден только в состоянии поста. Попросите агента проверить через 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 при подключении, дело в ключе: он обязателен на любой метод, включая самый первый.

Куда дальше