Yandex Metrika
Полное покрытие API Яндекс Метрики (Stat, Management, Logs) с определениями инструментов, сгенерированными из документации Яндекса. По умолчанию доступно десять инструментов, чтобы не перегружать контекст; никогда не переписывает ваш запрос молча.
Документация
Yandex Metrika MCP Server
MCP-сервер к API Яндекс Метрики. Покрыты все 108 методов; по умолчанию объявляются десять — те, которыми считают. Остальное включается одной переменной.
mcp-name: io.github.artgas1/yandex-metrika-mcp-server
npx -y yandex-metrika-mcp-server
Форк atomkraft/yandex-metrika-mcp (апстрим — Vadim Bezymianyi, MIT). С версии 2.0.0 инструменты не пишутся руками, а порождаются из спеки, собранной по официальной документации.
Покрытие
| API | методов | из них в профиле core | примеры инструментов |
|---|---|---|---|
| Management | 95 (21 ресурс) | 4 | metrika_counter_list, metrika_goal_create, metrika_segment_update |
| Logs | 7 | — | metrika_logs_create, metrika_logs_get, metrika_logs_download |
| Stat | 6 | 6 | metrika_stat_data, metrika_stat_bytime, metrika_stat_pivot |
Имя инструмента — metrika_<ресурс>_<действие>, где ресурс взят из URL самого API без переименований.
Поэтому metrika_goal_list однозначно отображается в GET /management/v1/counter/{id}/goals
и в свою страницу документации.
Контракт
Сервер переписан из-за двух наблюдавшихся отказов: он отдавал не то, что просили, и молча подмешивал фильтр. Отсюда четыре правила, каждое закрыто тестом.
- Никакой молчаливой подмены. Что попросили — то и уходит в API. Сервер не досочиняет ни измерений, ни периода, ни фильтров.
- Всё, что сервер добавил от себя, видно в ответе. Ответ приходит как
{"_meta": {...}, "data": {...}}, где_meta.applied_by_serverперечисляет добавленное, а_meta.notes— принятые за вызывающего решения. - Отказ остаётся отказом. Ошибка API возвращается с
isError: trueи телом ответа Метрики. Повтор делается по статусу (429/500/502/503/504 и сетевые сбои), а не по подстроке в тексте; у 429 соблюдаетсяRetry-Afterс потолком 30 секунд. Число повторов всегда видно в_meta.retries. - Обрезание выдачи видно. В
_metaедутrows_returned,rows_totalиtruncated— Метрика режет ответ по умолчанию, и молчать об этом нельзя. Если сервер сам урезал ответ по потолку длины, это отдельно объявлено в_meta.truncated_by_serverс числом выброшенных строк. - Секреты не уезжают в ответ. У
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 Яндекса, тот же, что используется для Директа и Вебмастера.
По умолчанию сервер сохраняет stdio-режим. Для одного локального процесса, к которому подключаются несколько MCP-клиентов, включите stateless Streamable HTTP:
YANDEX_API_KEY=<OAuth-токен> \
MCP_TRANSPORT=http MCP_HOST=127.0.0.1 MCP_PORT=13404 \
npm start
Endpoint — http://127.0.0.1:13404/mcp. При loopback-привязке сервер также
проверяет Host, чтобы локальный endpoint нельзя было вызвать через DNS rebinding.
Подключение к клиенту
{
"mcpServers": {
"yandex-metrika-mcp": {
"command": "npx",
"args": ["-y", "yandex-metrika-mcp-server@3"],
"env": { "YANDEX_API_KEY": "..." }
}
}
}
Из локальной сборки — то же самое, но "command": "node" и путь до build/index.js.
Мажор в строке запуска закреплён намеренно: смена мажорной версии меняет набор инструментов по умолчанию, и получать это молча при старте агента не нужно.
Переменные окружения
| Переменная | По умолчанию | Что делает |
|---|---|---|
YANDEX_API_KEY | — | OAuth-токен. Без него сервер не стартует. |
MCP_TRANSPORT | stdio | Транспорт: stdio или stateless Streamable http. |
MCP_HOST | 127.0.0.1 | Адрес HTTP listener. Используется только при MCP_TRANSPORT=http. |
MCP_PORT | 3000 | Порт HTTP listener, целое число от 1 до 65535. |
METRIKA_PROFILE | core | Какая часть каталога объявляется: core (10 инструментов), read (все 51 читающих), all (все 108). Неизвестное значение роняет старт. |
METRIKA_ALLOW_WRITES | не задана | 1 разрешает и объявляет 57 инструментов, меняющих данные. Пока не задана — их нет в tools/list вовсе. |
METRIKA_TOOLS | пусто | Своя выборка через запятую: раздел (stat, logs, management), префикс имени (metrika_goal) или точное имя. Задана — побеждает профиль. |
METRIKA_TRAFFIC_FILTER | ym:s:isRobot=='no' | Условие сегментации, добавляемое к отчётам Stat. Задаётся целиком. |
METRIKA_MAX_OUTPUT_CHARS | 120000 | Потолок длины ответа одного вызова. Выгрузка 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 тыс. |
read | 51 + каталог | 68 074 Б | ~31 тыс. — оценка |
all + METRIKA_ALLOW_WRITES=1 | 108 + каталог | 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 остаётся транспортом по умолчанию. HTTP включается только явно через
MCP_TRANSPORT=http; безопасный дефолт слушает127.0.0.1и проверяетHost.
Политика приватности
Сервер не собирает, не хранит и никуда не передаёт данные о вас. Ни телеметрии, ни аналитики, ни обращений к серверам автора — их не существует: под этот пакет не поднято никакой инфраструктуры.
Единственный сетевой адресат — https://api-metrika.yandex.net. Токен читается из
YANDEX_API_KEY в память процесса и никуда не пишется: ни в файл, ни в stdout, ни в тело
ответа. Данные отчётов не кэшируются на диск и не переживают процесс.
Данные, которые вы запрашиваете, обрабатывает Яндекс как оператор Метрики — на это распространяется его политика, а не эта.
Полный текст: PRIVACY.md.
Установка одним файлом (MCPB)
Для Claude Desktop и других клиентов, понимающих MCP-бандлы, есть .mcpb-файл — он лежит в
релизах. Открываете файл, вводите
токен в окне установки — всё.
Бандл собирается из того же кода тем же тегом (npm run mcpb), а его манифест генерируется
из package.json и профиля — не пишется руками, поэтому разойтись с сервером ему негде; это
проверяется тестом.
⚠️ В бандле нельзя включить запись. Цена ошибочного вызова — удалённый счётчик или цель без возможности восстановить историю, и щёлкать таким переключателем в окне установки нечего. Нужна запись — ставьте пакет с npm и включайте её осознанно, переменной окружения.
Без MCP: скилл и командная строка
MCP подходит не всем и не всегда: клиент может не уметь MCP вовсе, а описания инструментов занимают контекст постоянно — они лежат в нём, пока сервер подключён, вызываешь ты их или нет.
Для этого случая тот же сервер умеет запускаться командой:
npx -y yandex-metrika-mcp-server catalog --search goal
npx -y yandex-metrika-mcp-server describe metrika_stat_data
npx -y yandex-metrika-mcp-server call metrika_stat_data \
--ids <ID счётчика> --dimensions ym:s:trafficSource \
--metrics ym:s:visits,ym:s:users --date1 7daysAgo --date2 today
Поверх этого лежит скилл — папка с инструкцией для агента, которая ставится одной строкой:
npx skills add artgas1/yandex-metrika-mcp # в текущий проект
npx skills add artgas1/yandex-metrika-mcp -g # глобально, во все проекты
Скилл не добавляет клиенту инструментов и ничего не держит в контексте: он читается только когда речь зашла о Метрике. Внутри — та же команда, справочник всех 108 методов и словарь измерений.
Где он работает. Установщик кладёт один экземпляр в .agents/skills/yandex-metrika/
и симлинкует его в папки конкретных агентов. Проверено запуском на двух:
| агент | обнаружение | чем проверено |
|---|---|---|
| Claude Code | .claude/skills/ → симлинк | /yandex-metrika отвечает из содержимого скилла |
| Codex | .agents/skills/ напрямую | называет путь к SKILL.md; ни строки в AGENTS.md, ни настройки в config.toml для этого не нужно |
Установщик заявляет ещё около двадцати агентов через тот же универсальный каталог (Amp, Cline, Antigravity, Augment и другие) — там мы не проверяли.
Почему это не вторая реализация. CLI не делает ни одного собственного запроса: он разбирает
аргументы и зовёт executeMethod — ту же функцию, что и MCP-инструменты. Отсюда одинаковые
гарантии: фильтр роботов в отчётах, потолок ответа с распиской об урезании, вычистка секретов
из показываемого URL, повтор по статусу. Разойтись им негде, потому что расходиться нечему.
Справочник методов внутри скилла генерируется из spec/metrika-api.json — той самой спеки,
которая обновляется из документации Яндекса ежедневно. Тест сверяет закоммиченный файл с тем,
что сгенерировалось бы сейчас, поэтому «скилл отстал от API» здесь красное, а не незаметное.
Два сознательных отличия команды от MCP:
| MCP | команда | |
|---|---|---|
METRIKA_PROFILE | действует, по умолчанию core | не действует — доступны все 108 методов |
METRIKA_ALLOW_WRITES | нужен для меняющих данные | нужен так же |
Профиль существует, чтобы не платить контекстом за описания невызванных инструментов; у команды в терминале такой цены нет. Гейт записи — про другое: удалённую цель нечем восстановить, и послабление здесь было бы дырой в обход сервера.
Проверки
Не макет — запустите сами
npm run demo
Всё на записи приходит из ответа сервера по JSON-RPC: строка добавленного фильтра — из _meta.applied_by_server, строки отчёта — из тела ответа. Ни токена, ни сети: запросы уводятся на локальную заглушку, поэтому прогон повторяется где угодно, включая CI. Переснять запись — npm run demo:record.
npm test # 87 тестов: спека, схемы, протокол 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и джоба оставалась зелёной при любом падении теста.