yandex-messenger-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@yandex-messenger-mcpshow my unread chats"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
yandex-messenger-mcp
MCP-сервер для Яндекс Мессенджера. Даёт агенту читать переписку, искать по ней, скачивать вложения и отправлять текст от вашего имени.
Работает на той же сессии, что и веб-клиент: Playwright один раз логинится в Яндекс и держит persist-профиль, дальше протокол (WebSocket + HTTP) гоняется в Node на извлечённых cookie. Публичного API у Мессенджера нет, поэтому протокол снят реверсом веб-клиента chats-web - отсюда честный раздел Ограничения внизу, его стоит прочитать до того, как полагаться на инструмент.
Инструменты
Инструмент | Что делает |
| Список чатов: последнее сообщение, счётчик непрочитанных, свежие первыми |
| Страница сообщений чата с пагинацией по курсору |
| Одно сообщение по |
| Окно сообщений вокруг метки: N сообщений до и N после |
| Поиск по сообщениям, людям и чатам |
| Отправка текста, с упоминаниями/ответом/пересылкой. Двухшаговая: draft, затем confirm |
| Отправка картинки или файла. Двухшаговая: draft (байты не льются), затем confirm (необратимо) |
| Поставить/снять реакцию. Одним вызовом, без confirm (реверсибельно) |
| Полный список поставивших реакцию и прочитавших (кто и когда), без обрезки |
| Отметить чат прочитанным. Одним вызовом, без confirm |
| Закрепить/открепить сообщение. Одним вызовом, без confirm |
| Удаление своего сообщения. Двухшаговая: draft, затем confirm (необратимо) |
| Правка своего сообщения. Двухшаговая: draft, затем confirm (необратимо) |
| Чтение опроса: варианты, мой выбор, результаты. Одним вызовом, без confirm |
| Голос в опросе. Двухшаговая: draft, затем confirm. Форма подтверждена живьём |
| Сообщения треда как микро-чата (по |
| Подписка на тред и выход. Одним вызовом, без confirm |
| Скачивает вложение по рефу в папку загрузок |
list_chats
Параметр | Тип | По умолчанию |
| 1-500 | 50 |
| bool | false |
| bool | false |
Отдаёт {chats, total_chats, unread_chats}. У чата: chat_id, name, kind (private/group), last_activity, unread_count, unread, muted, last_message.
include_last_message_text:true возвращает полный текст последнего сообщения каждого чата. По умолчанию отдаются только метаданные последнего сообщения (без текста, цитат и имён файлов) - чтобы содержимое чужих переписок не попадало в контекст модели без явного запроса.
get_history
Параметр | Тип | По умолчанию |
| ChatId либо поисковый запрос | обязателен |
| 1-200 | 40 |
| курсор: мкс строкой | нет |
| ISO-дата/время, нижняя граница включающая | нет |
| ISO-дата/время, верхняя граница исключающая | нет |
| ISO-дата/время, строго после метки (альтернатива | нет |
Если chat задан запросом и совпадений несколько, инструмент возвращает status:"ambiguous_chat" со списком кандидатов и не гадает. Пагинация: взять next_before из выдачи и передать его в before следующего вызова.
Фильтр по времени - альтернатива курсору. from_date включает сообщения от этой даты и позже, to_date исключает сообщение ровно на границе (верхняя граница исключающая), поэтому «сообщения за сегодня» берутся одним вызовом: from_date = начало дня, to_date = начало следующего. after - то же, что from_date, но строго после метки. При совпадении обеих форм одной границы курсор before приоритетнее to_date, а after приоритетнее from_date.
Self-чат «Избранное» резолвится по имени, а не только литеральным ChatId: резолвер узнаёт его по гейту <myGuid>_<myGuid> (пара одинаковых guid, PrivateChatInfo + PartnerInfo.Guid == myGuid) и больше не выбрасывает вас самих как собеседника. Резолв не-self чатов при этом не меняется.
Вложения приходят только рефами (file_id, name, size, kind). Ничего не качается - это отдельное явное действие через download_attachment.
Каждое сообщение помимо v1-полей несёт обогащение (аддитивно, старые ключи не меняются):
Ключ | Что несёт |
| автор == я; |
| прочтения: |
| упоминания в имена; нет имени для guid - явный |
| реакции сгруппированы по типу: |
| признак |
| оригинал пересылки: |
Реакции: type - id артворка, emoji - аппроксимация. Яндекс рендерит реакции PNG-артворком по id (/reactions/{type}/{size}), а не Unicode-эмодзи. Карта type -> {name, emoji} лежит данными в src/config/reaction-map.json (52 записи, перегенерируется свипом по /reactions/{type}/small). Авторитетны type (пришёл с провода) и name (имя ассета с сервера); колонка emoji - наша аппроксимация артворка, её нельзя выдавать за «эмодзи от Яндекса». Пространство типов открыто (сервер принимает любой int), поэтому карта - lookup-с-фолбэком, а не полный справочник: неизвестный тип отдаётся как unknown, chr(type) в emoji не подставляется. Акторы и actors_complete в этом обогащении - усечённый сиблинг history; полный список поставивших/прочитавших (кто и когда), без обрезки, - инструмент list_reactions (двумя WS-вызовами: Mode - дискриминатор, дефолт -> UserReactions, Mode:1 -> UserReads+ReadsCount).
Форматирование - сырая строка. Протокол не даёт структурированных entities (ranges/spans): Text несёт ровно MessageText, markdown-символы едут как есть, разметку рисует клиент (проверено живьём, §17.14). text отдаётся без разбора; структуру форматирования тут не выдумываем.
get_message
Параметр | Тип | По умолчанию |
| ChatId либо поисковый запрос; нужен вместе с | - |
| timestamp сообщения в микросекундах (строка); нужен вместе с | - |
| join-ссылка Мессенджера (альтернатива паре | - |
| bool: тянуть детальные реакции/прочтения (2 доп. WS-вызова) | true |
Одно сообщение без загрузки истории (message_info, один WS-вызов). Адресация - либо парой chat+message_id (то же резолвение чата, что у get_history), либо готовой join-ссылкой (url): из неё резолвится и чат, и метка сообщения. Трёхсегментная ссылка (сообщение внутри треда) адресуется той же деривацией thread_id, что и get_thread; для бизнес-чата (2/...), где деривация недоступна, возвращается status:"thread_unsupported" с причиной, а не тихий отказ.
with_reactions по умолчанию true: для ОДНОГО сообщения два дополнительных вызова list_reactions дёшевы, поэтому reactions_detail (полные реакции и прочтения, без обрезки) приезжает вместе с сообщением. with_reactions:false возвращает только само сообщение с обычным обогащением (усечённые сиблинги reactions/reads, как в get_history).
Ответ также несёт my_reactions (мои реакции на сообщение; ключ пропадает, если своих реакций нет) и сырой chat_info (метаданные чата с провода, отдаются как есть).
get_message_context
Параметр | Тип | По умолчанию |
| ChatId либо поисковый запрос | обязателен |
| timestamp целевого сообщения в микросекундах (строка) | обязателен |
| сколько сообщений ДО метки, 0-200 | 10 |
| сколько сообщений ПОСЛЕ метки, 0-200 | 10 |
Окно вокруг конкретного сообщения - когда важен не курсор, а то, что было сказано непосредственно до и после метки. Отдельного «context»-метода на проводе нет: окно строится теми же границами history, что и get_history. Выдача несёт три ключа: before (от старых к новым), message (само сообщение по метке, если оно ещё живо - не удалено и не отфильтровано), after (от старых к новым).
search
Параметр | Тип | По умолчанию |
| строка | обязателен |
|
| все три |
| стартовый limit | 50 |
Серверной пагинации у поиска нет (см. Ограничения), поэтому полнота достигается эскалацией limit: пока выдача насыщена, limit поднимается и запрос повторяется. Как это отработало, видно в ответе: escalation: {start_limit, final_limit, requests}. Если упёрлись в клиентский потолок, придёт truncated:true с причиной, а не молча обрезанный список.
Entity contacts невалиден и отвергается на входе.
send_message
Параметр | Тип |
| ChatId либо поисковый запрос |
| текст сообщения |
| упоминания участников (опционально) |
| timestamp сообщения-цели ответа, мкс строкой (опционально) |
| timestamp пересылаемого сообщения, мкс строкой (опционально) |
|
|
| токен из draft, обязателен при |
Подробности двухшаговой отправки ниже: Двухшаговая отправка.
Упоминания (mentions). Двухшаговый контракт: на draft элементы mentions - это запросы (@Имя, @<guid> либо голый guid), каждый резолвится в guid участника; неоднозначность (несколько кандидатов) или отсутствие результата отклоняет весь вызов целиком, не только спорное упоминание. Ответ несёт mentions: [{guid, name?}] - уже резолвнутые. На confirm нужно предъявить РОВНО те же guid, в том же порядке, что вернул draft: изменение состава или порядка отклоняет отправку (порядок значим - он часть отпечатка нагрузки). Резолв идёт по каталогу организации (глобальный поиск людей), а НЕ по участникам конкретного чата: @Имя может найти не того тёзку. Для приватного чата есть бесплатная проверка принадлежности - оба guid собеседников лежат в самом chat_id (<guidA>_<guidB>), резолвнутый guid вне этой пары отклоняется (mention_not_in_chat). Для группового чата такой проверки нет (эндпоинта участников группы в протоколе не нашлось) - точный адрес гарантирует только явный @<guid>, а не поиск по имени.
Ответ (reply_to_message_id). Timestamp цели входит в отпечаток нагрузки - его изменение между draft и confirm отклонит отправку. Цитата цели (reply_quote в draft) перечитывается с сервера отдельным запросом и в отпечаток НЕ входит: правка текста цели между draft и confirm отправку не отклоняет, потому что цитата на confirm берётся заново с сервера, а не из draft. Цитата обрезается до 200 символов (quote_truncated:true, если обрезана) - число выведено из реверса, живого случая цитаты длиннее 200 символов не было.
Пересылка (forward_from). Timestamp пересылаемого сообщения, тоже часть отпечатка. В отличие от ответа, пересылка идёт без цитаты.
send_file
Параметр | Тип |
| ChatId либо поисковый запрос |
| абсолютный путь к файлу или картинке на диске |
|
|
| токен из draft, обязателен при |
Отправка картинки (image) или произвольного файла (file); тип определяется по расширению. Voice и gallery не отправляются - для них закрыт только долг чтения (см. [Non-Goals]). Двухшаговая: draft показывает имя/размер/тип/чат и НЕ льёт байты - заливка и все 3 шага (§12.1: upload_to_disk → PUT сырых байт → add_files) идут только на confirm. После confirm уходит обычное сообщение с file_info.id, и его можно прочитать обратно по file_id и скачать через download_attachment. Ошибки загрузки разделены: 507/403 → квота, 413 → размер, не «upload failed». Ответ без числового Status = отказ, как и у send_message.
Перезаливка при повторном confirm после рестарта. Локальная память токена живёт в процессе. Если процесс рестартовал между draft и confirm, повторный confirm пройдёт 3 шага загрузки заново - байты уйдут повторно. Дубля сообщения при этом нет: PayloadId зафиксирован в токене на draft и переживает рестарт, поэтому повтор придёт серверу тем же id и вернётся DUPLICATE (нового сообщения не создаст). Потери - только повторно израсходованные байты, и они ограничены квота-ошибкой. Не блокер, но знать полезно.
set_reaction
Параметр | Тип |
| ChatId либо поисковый запрос |
| timestamp сообщения в микросекундах (строка) |
| целочисленный id реакции (артворк, не emoji) из поля |
|
|
Реверсибельно, поэтому без confirm: снятие откатывает постановку тем же вызовом. Подтверждено живьём (US-009, self-чат): постановка (Reaction без Action) -> реакция видна через list_reactions; снятие (Action:REMOVE) -> реакция исчезает; Status:1 в обоих случаях. type валидируется по карте reaction-map.json ДО отправки - неизвестный тип отвергается на входе со status: invalid_type и на провод не уходит (сервер Type не валидирует и принял бы любой int, включая мусор). Реакция уходит полным конвертом ClientMessage (плоский push({Reaction}) дал бы ложный NO_SUCH_CHAT).
list_reactions
Параметр | Тип | По умолчанию |
| ChatId либо поисковый запрос для резолва чата | обязателен |
| timestamp целевого сообщения в микросекундах (строка) | обязателен |
| лимит на провод в обоих вызовах | 50 |
| для чтения по join-ссылке | нет |
Без confirm (чтение). Полный список «кто и когда» по сообщению - реакции (сгруппированы по типу, actors_complete:true ВСЕГДА, в отличие от get_history/get_message/get_message_context/get_thread, где акторы реакций - усечённый сиблинг агрегата) и прочтения. Стоит ДВА WS-вызова (UserReactions + UserReads/Mode:1) - дороже обогащения на сиблингах, зато без обрезки: сиблинги history обрезаны и источником истины не служат (живьём видено ReadsCount:10 при RecentUserReads длиной 3). limit обязателен на проводе в обоих вызовах - без него сервер отвечает BACKEND_CALL_ERROR(2).
mark_read
Параметр | Тип |
| ChatId либо поисковый запрос |
| опционально: timestamp (мкс), до которого отметить прочитанным; без него - до самого свежего сообщения |
| опционально: SeqNo той же границы |
Без confirm (безобидно). Без message_id тянет последнюю страницу истории и отмечает прочитанным до самого свежего сообщения. Форма и эффект подтверждены живьём (US-009): маркер SeenMarker бэкенд принимает, и именно он пишет seen-позицию, обнуляя непрочитанное (перебором: в уже прочитанном self-чате SeenMarker отвечает DUPLICATE, а ReadMarker/UnseenMarker коммитят заново; на чате с реальным непрочитанным от другого аккаунта unread_count ушёл с 2 до 0). В выдаче form_status: verified (см. Ограничения).
pin_message
Параметр | Тип |
| ChatId либо поисковый запрос |
| опционально: timestamp (мкс) закрепляемого сообщения; без него - открепить |
Без confirm (легко откатить). Семантика подтверждена живьём (US-009): закреп с меткой добавляет PinnedMessageInfo на это сообщение, а Pin без метки его убирает (открепление) - проверено на проводе через ChatData. В выдаче form_status: verified.
delete_message
Параметр | Тип |
| ChatId либо поисковый запрос |
| timestamp (мкс) удаляемого сообщения |
|
|
| токен из draft, обязателен при |
Двухшаговая (удаление необратимо). Шаг 1 (без confirm): резолвит чат, перечитывает удаляемое (автор/время/текст) и возвращает превью с confirm_token. В сокет не уходит ничего - превью строится чтением. Шаг 2 (confirm:true + токен): удаляет пустым Plain{ChatId, Timestamp} (§9.3). На confirm chat и message_id сверяются с подтверждёнными; расхождение - отказ. Форма подтверждена живьём (US-009, self-чат): Status:1, повторное чтение даёт deleted:true и пустой текст. Серверного дедупа на повторе нет: сырой replay того же удаления дважды на уже удалённом вернул снова FULLY_COMMITTED (переприменяет), от повтора защищает локальная память confirm-слоя. Удаление чужого сообщения отклоняет сервер (внятный commit-статус вроде NO_PERMISSION) - своего ограничения тут нет.
edit_message
Параметр | Тип |
| ChatId либо поисковый запрос |
| timestamp (мкс) правимого сообщения |
| новый текст |
|
|
| токен из draft, обязателен при |
Двухшаговая (правка необратима). Шаг 1: резолвит чат, перечитывает сообщение и возвращает превью was_text -> will_text с confirm_token, ничего не меняя. Шаг 2: правит через convertMessageToPlain + Timestamp (§9.3). На confirm сверяются chat, message_id и new_text (смена текста инвалидирует токен). Форма подтверждена живьём (US-009, self-чат): Status:1, после правки сообщение читается с новым текстом, edited:true и непустым edited_at (LastEditTimestamp). Повторный confirm тем же токеном отдаёт запомненный результат, второго push не шлёт. Правку чужого отклоняет сервер.
get_poll
Параметр | Тип |
| ChatId либо поисковый запрос |
| timestamp (мкс) сообщения-опроса |
Без confirm (чтение). message_info даёт вопрос/варианты/лимит выбора, poll_info (§14.3) - агрегат: {is_poll, answers, my_choices, is_anonymous, voted_count, recent_voters, results} плюс сырой ответ. answers[i] несёт votes и, для не-анонимного опроса с голосами, voters (имя+время из AnswerVotes); у анонимного опроса сервер скрывает список голосующих даже по явному запросу - это отражено флагом voters_hidden:true (виден только агрегат и свой выбор). Признак «это опрос» виден и здесь (is_poll), и в обычной выдаче сообщения (kind:'poll'). Не опрос - статус not_a_poll. Чтение разрешено против любого реального опроса в любом чате.
vote_in_poll
Параметр | Тип |
| ChatId либо поисковый запрос |
| timestamp (мкс) сообщения-опроса |
| массив выбранных вариантов (индексы/id) |
|
|
| токен из draft, обязателен при |
Двухшаговая. Форма Vote{ChatId, Timestamp, Action:0, Choices} (§9.3/§11.4) подтверждена живьём (2026-07-17, commit_status:1 FULLY_COMMITTED): Action:0 обязателен (без него - BACKEND_CALL_ERROR(2)), Results не шлётся, Choices - 0-based индексы в Poll.Answers[] и полный набор выбора. Голос публичен и меняемый: повторная отправка заменяет прежний выбор целиком (несколько вариантов - все индексы в одном Choices) - это подтверждено повторным чтением, где my_choices сменился с [0] на [1] после повторной отправки. form_status в выдаче - verified (AC-29 закрыт). Почему confirm сохранён, хотя голос меняемый: сам факт голоса необратим - voted_count растёт, а в не-анонимном опросе голосующий попадает в список голосовавших; отменить голос до нуля протоколом не подтверждено (единственный оставшийся мелкий вопрос).
download_attachment
Параметр | Тип |
| из |
| опционально, только контекст вызывающего: в запрос не идёт |
|
|
Возвращает {path, bytes, content_type}. Повторный вызов не идемпотентен: имена вложений не уникальны (photo.jpg у всех), затирать чужой файл нельзя, поэтому повтор кладёт рядом копию с суффиксом.
Related MCP server: time-messenger-mcp-server
Треды
Тред - это чат: у него собственный ChatId, и всё, что умеет обычный чат (чтение, отправка, реакции, прочтения), работает в треде тем же способом - паритет с обычным чатом, а не отдельная механика.
Чтение:
get_threadоткрывает тред по готовомуthread_idлибо по пареchat+message_idродительского сообщения. Пагинация та же, что уget_history:limit(1-200, по умолчанию 40) и курсорbefore(мкс строкой - вернуть сообщения строго старше него; значение для следующей страницы -next_beforeиз выдачи). Пустой (ещё не материализованный) тред приходит сempty:true, а не ошибкой доступа.Создание = деривация, без сети. Отдельного серверного «создать тред» нет.
thread_idвыводится строкой из родительского сообщения (§17.10, radix 10), и тред материализуется первым отправленным в него сообщением. «Обсудить» из веб-клиента - ровно эта деривация, реверса не требует. Бизнес-чаты (2/...) недоступны для деривации - это зафиксировано, а не забыто.Отправка в тред идёт обычным
send_message, где в полеchatпереданthread_id(тред = валидный ChatId) - с той же двухшаговой отправкой draft->confirm.Подписка:
join_to_thread/leave_threadпоthread_id- вступление и выход, обратимы, поэтому без confirm.
Требования
Node.js >= 22
Chromium для Playwright
Установка
Через npx (рекомендуется)
Отдельно устанавливать пакет не нужно - npx скачивает его из реестра по требованию и
запускает. Разово нужен только браузерный движок, которым сервер управляет для входа:
npx playwright install chromiumДальше сервер запускает MCP-клиент командой npx yandex-messenger-mcp - см.
Подключение к MCP-клиенту. Руками её запускать не требуется.
Из исходников (для разработки)
git clone https://github.com/conarti/yandex-messenger-mcp.git
cd yandex-messenger-mcp
npm install
npx playwright install chromium
npm run buildСкрипты сборки, тестов и typecheck - в разделе Разработка.
Первый запуск и авторизация
Отдельного шага логина нет: авторизация ленивая и случается на первом вызове инструмента, который реально идёт к серверу. tools/list отвечает и без сессии.
Как это выглядит:
Первый вызов. Поднимается headed-браузер Playwright на странице Мессенджера. Войдите обычным способом: QR-код, пароль, что настроено у вас. Сервер ждёт появления сессии до 5 минут.
Сессия сохраняется в persist-профиле (
~/.config/yandex-messenger-mcp/profile/). Дальше браузер не нужен: cookie извлекается, протокол гоняется в Node.Последующие запуски работают на сохранённом профиле, ручной вход не требуется.
Протухание. Когда Яндекс отвергает cookie, профиль поднимается headless и даёт Паспорту рефрешнуть сессию (обычно пара секунд, незаметно). Если рефреш не помог, происходит эскалация в headed-логин, и вас снова попросят войти руками.
Единственный канал хрупкости здесь - сама cookie-сессия: долгий простой может потребовать ре-логина. Ничего другого не истекает.
Профиль и загрузки - это ваши личные данные. Каталог
~/.config/yandex-messenger-mcp/содержит живую сессию Яндекса: он не должен попадать в git, синхронизацию или бэкапы, из которых его кто-то достанет.
Конфигурация
Файл: ~/.config/yandex-messenger-mcp/config.json. Целиком опционален - без него всё работает на дефолтах. Переопределять можно точечно, любую секцию и любое поле.
Ключ | Дефолт | Смысл |
|
| Persist-профиль Playwright. Относительный путь резолвится от каталога конфига |
|
| Папка загрузок, туда же |
|
| TTL автоочистки загрузок. |
|
| Дефолтный |
|
| Стартовый |
| см. ниже | Протокольные константы: на случай, если Яндекс их поменяет |
Секция protocol существует ради устойчивости к ротации: если новая версия chats-web уедет на другие хосты, их можно поправить в конфиге, не трогая код. Значения по умолчанию и полный пример - в config.example.json.
Пример минимального конфига:
{
"downloads": { "ttlDays": 3 },
"limits": { "listChatsDefaultLimit": 100 }
}Уровень логов задаётся переменной YANDEX_MESSENGER_MCP_LOG_LEVEL (debug/info/warn/error, по умолчанию info). Лог идёт в stderr: stdout принадлежит MCP stdio-транспорту. Секреты сессии и содержимое переписки в логах редактируются.
Подключение к MCP-клиенту
Сервер говорит по stdio. Путь до dist/index.js - абсолютный.
Таймаут вызова инструмента у MCP-клиента - минимум 5 минут. Первый вызов, идущий к серверу, поднимает браузерный вход и ждёт логина до 5 минут (см. Первый запуск). Дефолтные таймауты многих клиентов (30-60 секунд) короче этого ожидания и оборвут первый вызов молчаливым таймаутом ещё до того, как вы успеете войти - авторизация при этом выглядит «сломанной», хотя дело в таймауте. Поднимите таймаут вызова инструмента до >= 5 минут. Запас нужен и после первого входа: худший легальный read доходит до ~90 секунд, что тоже длиннее дефолтов.
Claude Code
Через npx (основной способ, после установки пакета из npm - см. Установка):
claude mcp add yandex-messenger -- npx -y yandex-messenger-mcpИз собранных исходников (разработка):
claude mcp add yandex-messenger -- node /абсолютный/путь/yandex-messenger-mcp/dist/index.jsClaude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json (macOS), через npx:
{
"mcpServers": {
"yandex-messenger": {
"command": "npx",
"args": ["-y", "yandex-messenger-mcp"]
}
}
}Из собранных исходников (разработка) - тот же конфиг с command: "node" и путём до dist/index.js:
{
"mcpServers": {
"yandex-messenger": {
"command": "node",
"args": ["/абсолютный/путь/yandex-messenger-mcp/dist/index.js"]
}
}
}Первый вызов любого инструмента откроет окно браузера для входа - это ожидаемо, см. Первый запуск.
Автоочистка загрузок (TTL)
Скачанное вложение - это копия чужой переписки на диске, и она не должна жить вечно только потому, что агент один раз её открыл.
Что удаляется: файлы в
downloads/, у которыхmtimeстарше TTL (дефолт 7 дней). Строго старше: файл ровно на границе остаётся.Когда: на старте сервера и перед каждым скачиванием. Второе важнее первого - сервер может месяцами не рестартовать, и старт как единственная точка очистки не сработал бы.
Границы: только обычные файлы непосредственно в
downloads/. Без рекурсии, без следования за симлинками, за пределы папки очистка не выходит.Выключить:
downloads.ttlDays: 0. Это именно «не подметать», а не «удалить всё разом».
Confirm-политика
Подтверждение (draft->confirm) стоит только у необратимых мутаций. Критерий один: откатывается ли последствие тем же инструментом за один вызов. Раньше необратимой была только отправка текста; с добавлением файлов, правки, удаления и голоса необратимых операций стало пять, и политика обобщена под общий критерий.
С confirm (двухшаговые, необратимые):
send_message,send_file,delete_message,edit_message,vote_in_poll. Сообщение или файл уходят живому собеседнику, правка перезаписывает текст, удаление стирает, голос не снимается - отменить одним вызовом нельзя.Без confirm (одним вызовом, обратимые или безобидные):
set_reaction(Action:REMOVEоткатывает тем же вызовом),pin_message(легко открепить),mark_read(безобидна). Read-пути (get_*,search,list_chats) и подписка на тред (join_to_thread/leave_thread) confirm тоже не требуют.
Почему confirm не раздан всем мутациям: дешёвый confirm обесценивает дорогой. Если подтверждать приходится и обратимую реакцию, его начинают жать не глядя - и тогда прожмут на delete_message. Подтверждение бережётся для того, что действительно необратимо.
Токен confirm несёт дискриминатор операции (op): токен, выданный одной операции, при предъявлении другой отвергается как op_mismatch - перепутать draft удаления с draft правки нельзя. На confirm заново резолвится чат и заново считается отпечаток нагрузки; любое расхождение - отказ, а не отправка «наиболее вероятного».
Двухшаговая отправка (send_message как образец)
Шаг 1 - draft. Обычный вызов резолвит чат и возвращает превью:
{
"status": "draft",
"chat_id": "...",
"chat_name": "Имя чата",
"text": "текст",
"confirm_token": "...",
"next_step": "Ничего не отправлено. ..."
}В сокет не уходит ничего.
Шаг 2 - confirm. Повторный вызов с confirm:true, тем же confirm_token и неизменёнными chat и text.
Почему на confirm идёт повторная сверка, а не «отправить то, что в токене»: между draft и confirm может смениться всё. Тот же запрос chat завтра резолвится в другой чат - человек переименовался, появился однофамилец. Или к старому токену подставили другой текст. Поэтому токен несёт резолвнутый ChatId и хэш текста, на confirm чат резолвится и текст хэшируется заново, и результаты сверяются. Расхождение - отказ, а не отправка «наиболее вероятного».
Токен намеренно не подписан: подделывать его бессмысленно. Он не полномочие, а память о драфте - отправка всё равно идёт в заново проверенный чат.
Идемпотентность держится двумя слоями, потому что ретрая у отправки нет:
Локально: израсходованный токен возвращает запомненный результат, второй push не уходит.
На сервере:
PayloadIdфиксируется в токене на шаге 1, поэтому даже если локальная память потерялась (рестарт), повтор придёт какDUPLICATE- тоже успех, но нового сообщения не создаст.
Неоднозначный чат отправку блокирует: наружу уходят кандидаты, push не отправляется.
Ограничения и пробелы в доказательствах
Раздел честный. Часть протокола подтверждена живыми прогонами, часть - только фикстурами по реверсу веб-клиента, и это разные уровни уверенности.
Чего нет по решению (отложено)
Real-time. Никаких подписок на LIVE-события: новые сообщения, typing, seen, presence. Инструмент отвечает на запрос, а не слушает поток. Бота и автоответы на нём не построить.
Отправка вложений - только image и file. Картинки и произвольные файлы отправляются (
send_file); voice и gallery отправлять нельзя (только читать). Голосовые и галереи читаются, но не создаются.Мутации - частично. Реализованы реверсибельные (реакции
set_reaction, отметка прочтенияmark_read, закрепpin_message- одним вызовом, без confirm) и необратимые (отправка файлаsend_file, правкаedit_message, удалениеdelete_message, голосvote_in_poll- двухшаговые draft->confirm). Чтение опроса -get_poll. Пока НЕ реализованы: отправка voice/gallery, звонки, управление чатами, мульти-аккаунт.Только cookie-авторизация. OAuth не поддержан: в бандле это другой транспорт с другим форматом кадров, а не «второй режим», и он потребует отдельного WS-клиента.
Что не подтверждено живьём
Reply на исходящем подтверждён живьём, forward и refs - нет (US-010, 2026-07-21). Отправка ответа (
reply_to_message_id) даётStatus:1, и перечитывание возвращаетcontext.is_reply:trueс непустымquotes- цитата и признак ответа работают. Forward (forward_from) уходит сStatus:1, но перечитывание НЕ показывает признака пересылки (contextотсутствует,forwardedпуст) - на self-чате форма не рендерится.context.refsпуст и у reply: ссылка на оригинал (ForwardedMessageRefs) на чтении не восстанавливается, reply держится наQuote. Оба пробела и гипотезы (cross-chat forward, дискриминатор формы) - в issue #13. Входящий reply/forward в профиле по-прежнему не наблюдался (0 на 372), поэтому форма чтенияrefs/forwardedостаётся доко-выведенной.Чтение вложений:
image,fileиgalleryпроверены живьём;voice- долг. Скачивание типо-агностично (voice/gallery_image- это те жеfile_id, тянутся тем же generic-путём). Живьём (US-009) скачана настоящая галерейная картинка с аккаунта (gallery_image, непустые байты +content_type) - долг по галерее закрыт. Реального голосового на профиле нет, поэтомуvoiceживьём по-прежнему не проверено - долг остаётся дословно, фикстура доказательством не объявляется.Отправка вложений: путь не подтверждён до конца, но 403→квота поймана живьём (US-010, 2026-07-21). 3 шага загрузки (
upload_to_disk→ PUT →add_files) и обычное сообщение сPlain.Image/Plain.MiscFile+FileInfo.Id2взяты из реверса веб-клиента: входящие вложения живьём наблюдались, а исходящий путь - нет.Width/Heightкартинки намеренно не проставляются (для доставки достаточноfile_info). Живой прогон в self-чате упёрся вupload_to_disk HTTP 403, и код классифицировал его какupload quota(не generic «upload failed») - ветка 403→квота подтверждена живьём. Полный трёхшаговый путь и ветка 413→размер остаются доко-выведенными (заливка отклонена сервером до конца), см. issue #5. Билдеры вsrc/protocol/push.ts, загрузка вsrc/attachments/uploader.ts.Форма ответа на отправку подтверждена живьём (US-009). Успешный
pushтекста вернул{ Status:1, MessageInfo:{TimestampMcs, PrevTimestampMcs, SeqNo, Version}, DebugInfo }- имена контейнераMessageInfo/PrevTimestampMcs/TimestampMcs/SeqNo/Versionтеперь наблюдены, а не реконструированы.DebugInfo(адреса/тайминги/попытки) сервер тоже отдаёт - не парсится, безвреден.RateLimitна успешной отправке не приходит. Парсер по-прежнему принимает оба написания; ответ без числовогоStatusтрактуется как отказ. Инвариант распространён и на путь вложений.Постановка/снятие реакции подтверждены живьём (US-009, долг закрыт). Постановка (
Reactionбез поляAction, серверный дефолт ADD) -> реакция появляется вlist_reactions; снятие (Action:REMOVE=1) -> реакция исчезает;Status:1в обоих случаях.REPLACE(2)живьём не гонялся.Голос
vote_in_poll: отмена до нуля не подтверждена. ФормаVote{ChatId, Timestamp, Action:0, Choices}(§9.3/§11.4) закрыта живьём (2026-07-17,commit_status:1 FULLY_COMMITTED):Action:0обязателен,Resultsне шлётся,Choices- 0-based индексы вPoll.Answers[], полный набор выбора. Голос публичен и меняемый - повторная отправка заменяет выбор целиком (подтверждено повторным чтением:my_choicesсменился с[0]на[1]).form_statusв выдаче -verified, AC-29 закрыт. Confirm сохранён, потому что сам факт голоса необратим:voted_countрастёт, а в не-анонимном опросе голосующий попадает в список голосовавших. Единственный оставшийся мелкий вопрос - отмена голоса до нуля (пустойChoicesлибо инойAction) протоколом не подтверждена (кнопки в веб-UI нет).
Известные грубости
Единица
rate_limit.wait_forнеизвестна. Ни в доке, ни в живом захвате она не встретилась (на успешной отправкеrate_limitне приходит вовсе). Гадать не стали: сырое значение трактуется как миллисекунды и зажимается в 1-60 секунд. Кламп ограничивает ущерб при любой из трёх гипотез: если это секунды, минимум не даст устроить ретрай-шторм; если микросекунды, максимум не даст зависнуть на часы; если миллисекунды, значение проходит как есть. Точность здесь принесена в жертву осознанно. В лог пишется сырое значение - первый живой случай позволит определить единицу.У поиска нет серверной пагинации. Параметры
page/offset/from/skipсервером игнорируются, поляpage/pagesв ответе вестигиальны (всегда 1), аtotal- это число возвращённых элементов, а не общее число совпадений. Полнота достигается эскалациейlimit, то есть несколькими запросами вместо одного. Серверного потолкаlimitнайти не удалось (1000 отвечает штатно), поэтому потолок эскалации клиентский: при упоре -truncated:true.Автоматизация личного аккаунта. Это личный инструмент на неофициальном протоколе. Риски по ToS вы принимаете на себя. Отправка не распараллеливается, массовых рассылок здесь нет.
Протокол может уехать. Он снят с конкретной версии
chats-web. Обновление веб-клиента может сломать инструмент; протокольные константы вынесены вprotocol.*конфига, чтобы часть таких поломок чинилась без правки кода.
Границы доверия
Сводка открытых пунктов из разделов выше - для быстрой сверки, без деталей (детали в тексте по ссылке-issue или в самом разделе).
Пункт | Статус | Issue |
Чтение | долг | |
Разделение ошибок загрузки | долг | |
| долг | |
Отмена голоса в опросе до нуля не подтверждена (кнопки в UI нет) | долг | |
Единица | долг | |
Обрезка цитаты reply до 200 символов ( | выведено | - |
| выведено | - |
Разработка
npm run build # tsc
npm run typecheck # tsc с тестами
npm test # vitest, e2e при этом скипается
npm run test:watchE2E
Живой smoke-тест ходит на реальный аккаунт и по умолчанию скипается. Запуск явный:
YMCP_E2E=1 npm testОн намеренно только читает (whoami, list_chats, get_history): ничего не отправляет и ничего не качает, чтобы его можно было безопасно гонять повторно. Ассерты идут только по числам и булям - живые данные не попадают ни в вывод, ни в диагностику падений.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/conarti/yandex-messenger-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server