Yandex Metrika

Full Yandex Metrika API coverage (Stat, Management, Logs) with tool definitions generated from Yandex's own docs. Ten tools exposed by default so it does not flood the context; never silently rewrites your query.

Documentation

Yandex Metrika MCP Server

MCP-сервер к API Яндекс Метрики. Покрыты все 108 методов; по умолчанию объявляются десять — те, которыми считают. Остальное включается одной переменной.

mcp-name: io.github.artgas1/yandex-metrika-mcp-server

npm CI License: MIT

English

npx -y yandex-metrika-mcp-server

Форк atomkraft/yandex-metrika-mcp (апстрим — Vadim Bezymianyi, MIT). С версии 2.0.0 инструменты не пишутся руками, а порождаются из спеки, собранной по официальной документации.

Покрытие

APIметодовиз них в профиле coreпримеры инструментов
Management95 (21 ресурс)4metrika_counter_list, metrika_goal_create, metrika_segment_update
Logs7metrika_logs_create, metrika_logs_get, metrika_logs_download
Stat66metrika_stat_data, metrika_stat_bytime, metrika_stat_pivot

Имя инструмента — metrika_<ресурс>_<действие>, где ресурс взят из URL самого API без переименований. Поэтому metrika_goal_list однозначно отображается в GET /management/v1/counter/{id}/goals и в свою страницу документации.

Контракт

Сервер переписан из-за двух наблюдавшихся отказов: он отдавал не то, что просили, и молча подмешивал фильтр. Отсюда четыре правила, каждое закрыто тестом.

  1. Никакой молчаливой подмены. Что попросили — то и уходит в API. Сервер не досочиняет ни измерений, ни периода, ни фильтров.
  2. Всё, что сервер добавил от себя, видно в ответе. Ответ приходит как {"_meta": {...}, "data": {...}}, где _meta.applied_by_server перечисляет добавленное, а _meta.notes — принятые за вызывающего решения.
  3. Отказ остаётся отказом. Ошибка API возвращается с isError: true и телом ответа Метрики. Повтор делается по статусу (429/500/502/503/504 и сетевые сбои), а не по подстроке в тексте; у 429 соблюдается Retry-After с потолком 30 секунд. Число повторов всегда видно в _meta.retries.
  4. Обрезание выдачи видно. В _meta едут rows_returned, rows_total и truncated — Метрика режет ответ по умолчанию, и молчать об этом нельзя. Если сервер сам урезал ответ по потолку длины, это отдельно объявлено в _meta.truncated_by_server с числом выброшенных строк.
  5. Секреты не уезжают в ответ. У metrika_measurement_delete есть параметр token; в показанном _meta.request_url его значение заменено на REDACTED. Сам OAuth-токен уходит только заголовком и в ответе не появляется никогда.

Фильтр роботов

В отчётах Stat API по умолчанию применяется собственный флаг робота Метрики, и только он:

ym:s:isRobot=='no'

Он объявлен: виден в схеме инструмента, отключается параметром human_traffic_only: false и всегда перечислен в _meta.applied_by_server. Если в запросе есть метрики ym:ad: или ym:ev:, фильтр не применяется (Метрика отвечает на такое сочетание 400) — и это попадает в _meta.notes, а не остаётся молчаливым исключением.

Своё условие задаётся переменной METRIKA_TRAFFIC_FILTERцеликом, включая isRobot, если он нужен:

METRIKA_TRAFFIC_FILTER="ym:s:isRobot=='no' AND ym:s:browserName!='HeadlessChrome'"

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

Заданное своё условие сервер называет в stderr при старте — оно меняет числа в каждом отчёте, и молчать об этом нельзя.

Сравнение периодов: ответ, который выглядит валидным

У metrika_stat_comparison и metrika_stat_comparison_drilldown даты периодов необязательны, и Метрика на их отсутствие не ругается. Она подставляет собственное окно (последняя неделя) в оба набора и возвращает сравнение периода с самим собой:

metrika_stat_comparison(ids, metrics)  →  totals a == b
                                          query  date1_a == date1_b

Отказывать сервер не будет — запрос ушёл ровно тем, каким его собрали. Но такой ответ приходит с пометкой в _meta.notes: и когда даты не заданы, и когда периоды совпали явно.

Как устроена спека

Публичного openapi.json у Метрики нет, но каждая страница метода сгенерирована из OpenAPI движком Diplodoc и отдаётся как text/markdown. Семантика (тип, required, комбинатор, ассертация) лежит в CSS-классах вида {.json-schema-property}, поэтому спека собирается построчным сканером по классам, а не markdown-парсером.

npm run spec:fetch   # скачать llms.txt и 108 страниц в .cache/docs/
npm run spec:build   # разобрать их в spec/metrika-api.json
npm test             # тесты спеки и схем инструментов
npm run smoke        # живые вызовы к API (нужен YANDEX_API_KEY)

spec/metrika-api.json коммитится — это состав API на момент сборки. Тест на дрейф сверяет его с llms.txt: Яндекс добавил или удалил метод — тест краснеет.

Разбор привязан к версии генератора (Diplodoc Platform v5.57.3): вся семантика висит на его классах, поэтому расхождение версии останавливает сборку спеки, а не молча портит её.

Запуск

По умолчанию объявляются десять инструментов из 108 — те, которыми считают. Управление счётчиками и целями, доступы и Logs API включаются переменной METRIKA_PROFILE; подробности ниже, в разделе «Почему по умолчанию не всё».

Спросить у самого сервера тоже можно: инструмент metrika_catalog_list перечисляет, что объявлено, что скрыто и как это включить.

npm install
npm run build
YANDEX_API_KEY=<OAuth-токен с scope direct:api / metrika> npm start

Токен — OAuth Яндекса, тот же, что используется для Директа и Вебмастера.

Подключение к клиенту

{
  "mcpServers": {
    "yandex-metrika-mcp": {
      "command": "npx",
      "args": ["-y", "yandex-metrika-mcp-server@3"],
      "env": { "YANDEX_API_KEY": "..." }
    }
  }
}

Из локальной сборки — то же самое, но "command": "node" и путь до build/index.js.

Мажор в строке запуска закреплён намеренно: смена мажорной версии меняет набор инструментов по умолчанию, и получать это молча при старте агента не нужно.

Переменные окружения

ПеременнаяПо умолчаниюЧто делает
YANDEX_API_KEYOAuth-токен. Без него сервер не стартует.
METRIKA_PROFILEcoreКакая часть каталога объявляется: core (10 инструментов), read (все 51 читающих), all (все 108). Неизвестное значение роняет старт.
METRIKA_ALLOW_WRITESне задана1 разрешает и объявляет 57 инструментов, меняющих данные. Пока не задана — их нет в tools/list вовсе.
METRIKA_TOOLSпустоСвоя выборка через запятую: раздел (stat, logs, management), префикс имени (metrika_goal) или точное имя. Задана — побеждает профиль.
METRIKA_TRAFFIC_FILTERym:s:isRobot=='no'Условие сегментации, добавляемое к отчётам Stat. Задаётся целиком.
METRIKA_MAX_OUTPUT_CHARS120000Потолок длины ответа одного вызова. Выгрузка Logs API в него обычно не помещается — сутки визитов это сотни тысяч символов; урезание объявляется в _meta.truncated_by_server.
METRIKA_API_BASEпустоПодмена адреса API (прокси, заглушка в тестах). Факт подмены печатается в stderr.

Как узнать, что скрыто, не открывая README

Инструмент metrika_catalog_list объявлен в любом профиле и отвечает из спеки, лежащей в пакете, — ни токена, ни сети ему не нужно:

{
  "profile": "METRIKA_PROFILE=core",
  "api_methods_total": 108,
  "api_methods_declared": 10,
  "api_methods_hidden": 98,
  "writes_enabled": false,
  "declared_tools": { "Stat API — отчёты": ["metrika_stat_data", "…"] },
  "hidden_tools": { "Management API — …": ["metrika_goal_create", "…"] },
  "how_to_widen": ["METRIKA_PROFILE=read — …", "METRIKA_PROFILE=all вместе с METRIKA_ALLOW_WRITES=1 — …"]
}

Он существует по простой причине: сервер, который что-то скрыл, обязан уметь сказать, что именно и как это включить. instructions видит модель, но не человек — в интерфейс клиента они не показываются; стартовую строку в stderr в обычной работе тоже никто не открывает. Без этого инструмента узнать про остальные 98 можно было только придя сюда.

Список инструментов в ответе строится из того же отбора, по которому они регистрируются, — разойтись с реальностью ему негде, и это проверено тестом.

Почему по умолчанию не всё

Описания объявленных инструментов лежат в контексте модели, когда клиент их загрузил. Это цена сервера, которую платят за сам факт подключения, а не за вызовы. Замер tools/list (09.09.2026):

ПрофильИнструментовtools/listтокенов
core (по умолчанию)10 + каталог32 181 Б14,8 тыс.
read51 + каталог68 074 Б~31 тыс. — оценка
all + METRIKA_ALLOW_WRITES=1108 + каталог158 301 Б~73 тыс. — оценка

Замер core — 14,5 тысячи до появления каталога и 14,8 после: сам инструмент стоит около 670 байт схемы, примерно 2% набора. Его ответ не входит в эту цену — он платится только при вызове.

Байты точные, их воспроизведёт любой: сериализуй ответ tools/list и посчитай длину. С токенами сложнее, и здесь стоит сказать прямо.

⚠️ Замер честный только у core — его дал /context клиента, который считает собственным токенизатором. Две другие строки пересчитаны из байтов по калибровке 2,17 байта на токен, снятой с той же строки core.

Ходовая эвристика «4 символа на токен» здесь врёт почти вдвое: она выведена на английском тексте, а описания у этого сервера русские, и кириллица в BPE токенизируется примерно вдвое хуже латиницы. Первая редакция этой таблицы была построена именно на ней и называла для core 7,9k вместо 14,5k. Если считаешь бюджет контекста для сервера с не-английскими описаниями — считай токенизатором, а не делением на четыре.

Состав core выведен из замера реального использования, а не из вкуса: шесть отчётов Stat плюс справочники, без которых отчёт не собрать (metrika_counter_list, metrika_counter_get, metrika_goal_list, metrika_segment_list). Порог веса стоит тестом — манифест не может подорожать молча. Порог в тесте стоит на байтах: они не зависят ни от токенизатора, ни от языка описаний.

Безопасность

  • Запись выключена по умолчанию, и меняющие инструменты не объявляются вовсе. Среди методов четырнадцать DELETE и пять удаляющих POST (.../measurement/delete, .../expense/delete, .../logrequest/{id}/clean и т. д.). Цена ошибочного вызова — удалённый счётчик или цель без возможности восстановить историю. Модель не может позвать то, чего не видит в tools/list; как включить — сказано в instructions сервера.
  • Аннотации проставлены на всех инструментах (readOnlyHint, destructiveHint, idempotentHint, openWorldHint). Клиент по ним отличает чтение от удаления: удаление под глаголом POST помечено разрушающим, PUT — тоже, потому что заменяет сущность целиком.
  • Ответы Метрики — недоверенные данные. В отчётах лежат поисковые фразы, заголовки страниц, реферера и значения UTM, то есть строки, которые пишут посетители сайта. Любой может зайти на сайт по ссылке с текстом внутри и увидеть его в отчёте. У всех инструментов openWorldHint: true, а в _meta.notes отчётов и выгрузок едет напоминание, что это данные, а не инструкции.
  • Транспорт — только stdio, токен передаётся переменной окружения; сетевого слушателя сервер не открывает.

Политика приватности

Сервер не собирает, не хранит и никуда не передаёт данные о вас. Ни телеметрии, ни аналитики, ни обращений к серверам автора — их не существует: под этот пакет не поднято никакой инфраструктуры.

Единственный сетевой адресат — https://api-metrika.yandex.net. Токен читается из YANDEX_API_KEY в память процесса и никуда не пишется: ни в файл, ни в stdout, ни в тело ответа. Данные отчётов не кэшируются на диск и не переживают процесс.

Данные, которые вы запрашиваете, обрабатывает Яндекс как оператор Метрики — на это распространяется его политика, а не эта.

Полный текст: PRIVACY.md.

Установка одним файлом (MCPB)

Для Claude Desktop и других клиентов, понимающих MCP-бандлы, есть .mcpb-файл — он лежит в релизах. Открываете файл, вводите токен в окне установки — всё.

Бандл собирается из того же кода тем же тегом (npm run mcpb), а его манифест генерируется из package.json и профиля — не пишется руками, поэтому разойтись с сервером ему негде; это проверяется тестом.

⚠️ В бандле нельзя включить запись. Цена ошибочного вызова — удалённый счётчик или цель без возможности восстановить историю, и щёлкать таким переключателем в окне установки нечего. Нужна запись — ставьте пакет с npm и включайте её осознанно, переменной окружения.

Проверки

npm test          # 67 тестов: спека, схемы, протокол MCP, поверхность и её бюджет
npm run protocol  # только протокольные: stdio, tools/list, tools/call, отказы
npm run smoke     # живые вызовы к API (нужен YANDEX_API_KEY)

Протокольные тесты поднимают сервер как подпроцесс и говорят с ним по JSON-RPC — тем же способом, каким это делает клиент. Сеть при этом не нужна: METRIKA_API_BASE уводит запросы на заглушку. Проверяется в том числе то, чего не видно изнутри: что в stdout не попадает ничего, кроме JSON-RPC, что отказ API приезжает как isError, а не как успешный текст, и что запись действительно заблокирована.

Чего в проверках НЕТ

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

Здесь это осознанный пропуск, а не забытый пункт. Профиль по умолчанию — десять инструментов, из них шесть отчётов Stat различаются формой ответа, а не темой, и путать их модели особо не с чем. Евал становится нужен, когда поверхность по умолчанию расширяется или когда в неё попадают инструменты с пересекающимися описаниями, — тогда его надо писать до расширения, а не после.

Что изменилось в 2.0.0

Удалены 26 инструментов-обёрток над пресетами Stat API (get_visits, sources_summary, get_page_performance и прочие). Они покрывали малую часть API, зашивали измерения и период в код и не давали задать произвольный запрос. Их заменяют metrika_stat_*, принимающие параметры Stat API как есть.

Появились методы, которых не было вовсе: список счётчиков, цели, сегменты, фильтры, разрешения, расходы, офлайн-конверсии и весь Logs API. Раньше идентификатор счётчика приходилось знать заранее — теперь его можно найти.

Что изменилось в 2.1.0

Сервер довели до состояния, в котором его не страшно оставить агенту.

  • Аннотации на всех 108 инструментах. До этого клиент не отличал metrika_counter_list от metrika_counter_delete.
  • Запись выключена по умолчанию (METRIKA_ALLOW_WRITES).
  • Найден и починен дефект разбора документации. Ассертации размечены строкой, где значение стоит после закрывающей скобки класса, — распознаватель свойств заякорен на конец строки и такие строки не матчил вовсе. В итоге до спеки не доезжало ни одного примера, значения по умолчанию или границы, а часть их падала в описание соседнего поля. Сейчас в спеке 288 примеров, 69 значений по умолчанию и 155 ограничений; ограничения переносятся в схему инструмента, примеры и значения по умолчанию — в описания параметров.
  • Найдена и починена потеря обязательности. Параметры вида «один из N типов» (goal у создания и правки цели, grant у выдачи доступа) собирались как z.unknown(), а он в zod необязателен, — обязательное поле уезжало клиенту как опциональное. Теперь это объединение реальных форм, и обязательность на месте.
  • Ссылки на сущности разворачиваются на один уровень: у 23 параметров тела вместо свободного объекта видны настоящие поля.
  • Послабления на входе там, где они безвредны. Число строкой, булево словом, список через запятую в строке запроса — принимаются; в теле запроса, где важен точный JSON, не принимаются.
  • Потолок длины ответа с объявленным урезанием: выгрузка Logs API бывает в сотни мегабайт.
  • Вычистка секретов из показанного request_url.
  • Повтор на 429 с соблюдением Retry-After.
  • SDK обновлён до 1.30 — на 1.17 висели три опубликованных уязвимости, две высокие; npm audit --audit-level=high теперь часть CI.
  • Починена джоба дрейфа в CI. Она запускала тесты через | tee без pipefail, поэтому код возврата брался у tee и джоба оставалась зелёной при любом падении теста.