io.github.artgas1/yandex-metrika-mcp-server
Полное покрытие API Яндекс Метрики: Stat, Management и Logs — 108 методов, 108 инструментов
Open source Open in the app JSON README (API)
About
Полное покрытие API Яндекс Метрики: Stat, Management и Logs — 108 методов, 108 инструментов
Details
- Kind
- MCP servers
- Topic
- No topic detected
- Publisher
- artgas1
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 3.2.1
- Last push
- 2026-09-08T22:49:00Z
- Repository state
- ativo
- Language
- JavaScript
- License
- NOASSERTION
- Added
- 2026-09-08 11:16:12
- Updated
- 2026-09-08 22:05:06
- Origin id
io.github.artgas1/yandex-metrika-mcp-server
README
# Yandex Metrika MCP Server
MCP-сервер к API Яндекс Метрики. Покрыты все 108 методов; по умолчанию объявляются **десять** —
те, которыми считают. Остальное включается одной переменной.
mcp-name: io.github.artgas1/yandex-metrika-mcp-server
[](https://www.npmjs.com/package/yandex-metrika-mcp-server)
[](https://github.com/artgas1/yandex-metrika-mcp/actions/workflows/ci.yml)
[](./LICENSE)
*[English](./README.en.md)*
<img src="https://raw.githubusercontent.com/artgas1/yandex-metrika-mcp/main/assets/case.svg" alt="Слева семь шагов в интерфейсе Метрики, справа тот же результат одним вопросом: таблица источников с визитами, целями и конверсией" width="100%">
```bash
npx -y yandex-metrika-mcp-server
```
Форк [atomkraft/yandex-metrika-mcp](https://github.com/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`
и в свою страницу документации.
## Контракт
Сервер переписан из-за двух наблюдавшихся отказов: он отдавал не то, что просили, и молча
подмешивал фильтр. Отсюда четыре правила, каждое закрыто тестом.
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-парсером.
```bash
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` перечисляет, что
> объявлено, что скрыто и как это включить.
```bash
npm install
npm run build
YANDEX_API_KEY=<OAuth-токен с scope direct:api / metrika> npm start
```
Токен — OAuth Яндекса, тот же, что используется для Директа и Вебмастера.
### Подключение к клиенту
```json
{
"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-токен. Без него сервер не стартует. |
| `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`** объявлен в любом профиле и отвечает из спеки, лежащей в
пакете, — ни токена, ни сети ему не нужно:
```json
{
"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 можно было только придя сюда.
Список инструментов в ответе строится из того же отбора, по которому они регистрируются, —
разойтись с реальностью ему негде, и это проверено тестом.
### Почему по умолчанию не всё
<img src="https://raw.githubusercontent.com/artgas1/yandex-metrika-mcp/main/assets/surface.svg" alt="Из 108 инструментов по умолчанию объявляются 10, остальные 98 вычеркнуты" width="100%">
Описания объявленных инструментов лежат в контексте модели, когда клиент их загрузил. Это
цена сервера, которую платят за сам факт подключения, а не за вызовы. Замер `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**, токен передаётся переменной окружения; сетевого слушателя
сервер не открывает.
## Политика приватности
Сервер не собирает, не хранит и никуда не передаёт данные о вас. Ни телеметрии, ни аналитики,
ни обращений к серверам автора — их не существует: под этот пакет не поднято никакой
инфраструктуры.
Единственный сетевой адресат — `https://api-metrika.yandex.net`. Токен читается из
`YANDEX_API_KEY` в память процесса и никуда не пишется: ни в файл, ни в stdout, ни в тело
ответа. Данные отчётов не кэшируются на диск и не переживают процесс.
Данные, которые вы запрашиваете, обрабатывает Яндекс как оператор Метрики — на это
распространяется [его политика](https://yandex.ru/legal/confidential/), а не эта.
Полный текст: [`PRIVACY.md`](./PRIVACY.md).
## Установка одним файлом (MCPB)
Для Claude Desktop и других клиентов, понимающих MCP-бандлы, есть `.mcpb`-файл — он лежит в
[релизах](https://github.com/artgas1/yandex-metrika-mcp/releases). Открываете файл, вводите
токен в окне установки — всё.
Бандл собирается из того же кода тем же тегом (`npm run mcpb`), а его манифест **генерируется**
из `package.json` и профиля — не пишется руками, поэтому разойтись с сервером ему негде; это
проверяется тестом.
⚠️ **В бандле нельзя включить запись.** Цена ошибочного вызова — удалённый счётчик или цель
без возможности восстановить историю, и щёлкать таким переключателем в окне установки нечего.
Нужна запись — ставьте пакет с npm и включайте её осознанно, переменной окружения.
## Проверки
```bash
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` и джоба оставалась зелёной при любом падении теста.