Back to the catalog

SEO Tools: XMLRiver SERP (Google + Yandex)

Google & Yandex SERP, image/news verticals and URL indexation checks via XMLRiver (read-only).

Open source Open in the app JSON README (API)

About

Google & Yandex SERP, image/news verticals and URL indexation checks via XMLRiver (read-only).

Details

Kind
MCP servers
Topic
Marketing & analytics
Publisher
antohins
Origin
official
Category
ferramentas
Transport
local
Version
1.8.0
Stars
3
Open pull requests
1
Last push
2026-09-04T17:38:12Z
Repository state
ativo
Language
TypeScript
License
MIT
Added
2026-08-29 03:02:26
Updated
2026-08-29 03:02:26
Origin id
io.github.antohins/seo-tools-mcp-xmlriver

README

<p align="center">
  <img src="assets/logo-128.png" width="96" height="96" alt="seo-tools-mcp" />
</p>

# seo-tools-mcp

[![CI](https://github.com/antohins/seo-tools-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/antohins/seo-tools-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![GitHub MCP Registry](https://img.shields.io/badge/GitHub%20MCP%20Registry-8%20servers-24292e?logo=github)](https://github.com/mcp/antohins/seo-tools-mcp-gsc)

**Русский** | [English](README.en.md)

Восемь **универсальных** stdio MCP-серверов для SEO: доступ к SERP, Wordstat, Google Search Console, Google Analytics 4, Яндекс.Вебмастеру, Яндекс.Метрике и self-hosted A-Parser прямо из Claude Code (и любого MCP-клиента). Все инструменты **read-only** — ничего не публикуют и не меняют в твоих аккаунтах, вывод — строгий JSON. Машиночитаемо это заявлено аннотацией `readOnlyHint`; её намеренно **нет** у двадцати инструментов, каждый вызов которых тратит платный ресурс (запрос к XMLStock/XMLRiver, прокси-трафик A-Parser) — иначе клиент счёл бы их безобидными и перестал спрашивать подтверждение перед прогоном по большому пулу. К конкретному сайту не привязаны: дефолты (свойство GSC, свойство GA4, хост Вебмастера, счётчик Метрики) настраиваются на лету.

> 🛰 Эти серверы мы используем в продакшене в **[PBN Workers](https://pbn-workers.com/ru/tools/seo-tools-mcp/)** — инфраструктура поискового топа: семантика, PBN и сателлиты, автоматизация SEO. Нужен стабильный органический трафик — [приходите](https://pbn-workers.com/ru/tools/seo-tools-mcp/).

| Сервер | Рабочие инструменты | Авторизация |
|---|---|---|
| `xmlstock` | `xmlstock_serp`, `xmlstock_images`, `xmlstock_news`, `xmlstock_video`, `xmlstock_wordstat`, `xmlstock_wordstat_dynamics`, `xmlstock_wordstat_regions`, `xmlstock_wordstat_regions_tree`, `xmlstock_balance` | API-ключ |
| `xmlriver` | `xmlriver_serp`, `xmlriver_images`, `xmlriver_news`, `xmlriver_maps`, `xmlriver_check_index`, `xmlriver_suggest`, `xmlriver_related_questions`, `xmlriver_balance` | API-ключ |
| `wordstat` | `wordstat_frequency`, `wordstat_dynamics`, `wordstat_regions`, `wordstat_regions_tree` | Api-Key Yandex Cloud |
| `gsc` | `gsc_query`, `gsc_inspect_url`, `gsc_list_sites`, `gsc_get_site`, `gsc_list_sitemaps`, `gsc_get_sitemap` | OAuth (все свойства аккаунта) / service account |
| `ga4` | `ga4_list_properties`, `ga4_property_details`, `ga4_metadata`, `ga4_check_compatibility`, `ga4_report`, `ga4_bytime`, `ga4_traffic_sources`, `ga4_geo`, `ga4_devices`, `ga4_top_pages`, `ga4_events`, `ga4_funnel`, `ga4_annotations`, `ga4_realtime` | OAuth (все свойства аккаунта) / service account |
| `ywm` | `ywm_hosts`, `ywm_summary`, `ywm_search_queries`, `ywm_queries_history`, `ywm_recommended_queries`, `ywm_popular`, `ywm_indexing_history`, `ywm_sqi_history`, `ywm_external_links`, `ywm_broken_links`, `ywm_diagnostics`, `ywm_important_urls`, `ywm_sitemaps` | OAuth (авто-refresh) |
| `metrika` | `metrika_report`, `metrika_bytime`, `metrika_counters`, `metrika_goals`, `metrika_traffic_sources`, `metrika_geo`, `metrika_devices`, `metrika_landing_behavior`, `metrika_search_phrases`, `metrika_top_landings` | OAuth (авто-refresh) |
| `aparser` | `aparser_ping`, `aparser_status`, `aparser_proxies`, `aparser_parsers`, `aparser_parser_fields`, `aparser_get_preset`, `aparser_serp_google`, `aparser_serp_yandex`, `aparser_suggest`, `aparser_request`, `aparser_bulk_request` | self-hosted A-Parser (URL + пароль API) |

Где опубликовано: [npm](https://www.npmjs.com/search?q=seo-tools-mcp) (восемь пакетов), [официальный MCP Registry](https://registry.modelcontextprotocol.io), **[GitHub MCP Registry](https://github.com/mcp/antohins/seo-tools-mcp-gsc)** (все восемь серверов), маркетплейс плагинов Claude Code (см. ниже) и `.mcpb`-бандлы в [релизах](https://github.com/antohins/seo-tools-mcp/releases/latest).

У каждого сервера дополнительно есть auth-инструменты `<server>_auth_status` и `<server>_set_credentials` (см. [Интерактивная авторизация](#интерактивная-авторизация-в-любой-сессии)).

## Инструменты по сервисам

### xmlstock — SERP Google/Яндекс
- `xmlstock_serp` — веб-выдача Google/Яндекса (органика + подсветки + SERP-фичи): регион, устройство, safe search, сортировка (Яндекс), период, рекламные блоки; третий движок `yandex_xml` — официальный Яндекс XML (groupby до 100 за 1 запрос, hlword на любых устройствах, статистика found/found-docs; тариф от 24 ₽/1000)
- `xmlstock_images` — поиск картинок Google (url страницы + url изображения + заголовок)
- `xmlstock_news` — новости Google (заголовок, источник, дата, сниппет)
- `xmlstock_video` — видео Google (url, заголовок, превью, хост, канал, длительность)
- `xmlstock_wordstat` — Яндекс Wordstat: топ + похожие запросы с частотностью (можно по региону), операторы Wordstat
- `xmlstock_wordstat_dynamics` — динамика частотности по времени (день/неделя/месяц)
- `xmlstock_wordstat_regions` — спрос по регионам (count, share, affinity index + имена регионов)
- `xmlstock_wordstat_regions_tree` — дерево регионов Wordstat (id + имя + путь)
- `xmlstock_balance` — баланс аккаунта / проверка ключа (бесплатно)

> Wordstat через XMLStock — тем же ключом `XMLSTOCK_*`, что и SERP; **не нужен Yandex Cloud** (в отличие от отдельного сервера `wordstat`).

### xmlriver — SERP Google/Яндекс + проверка индексации
- `xmlriver_serp` — органика Google/Яндекса (глубина добирается пагинацией: каждые 10 позиций = 1 платный запрос), флаг наличия AI Overview; опция `includeAIOverview` — полный текст Обзора от ИИ + цитируемые ссылки (платный `ai=1`, только Google); `includeAdditional` — доп. SERP-блоки Google из `<addresults>` (knowledge_graph, localresultsplace, rs и др.; наполнение зависит от платных опций кабинета XMLRiver, непришедшие блоки — в `additional.unavailable`); гео-таргетинг Google — `location` (город → `loc`, «Moscow»/«1011969») и `country` (ISO/числовой id, автовыводится из города); `device` — desktop/mobile/tablet, `os` (ios/android) отправляется только при `device=mobile`
- `xmlriver_images` — картинки Google (страница + url картинки + заголовок + источник + размеры); гео — `location`/`country`
- `xmlriver_news` — новости Google (заголовок, источник, дата, сниппет), фильтр по времени; гео — `location`/`country`
- `xmlriver_maps` — поиск заведений по Google Maps (`setab=maps`, обязательные `zoom` 1–15 и `coords` «широта,долгота», `count` 5–50): название, рейтинг, адрес, телефон, сервисы, координаты, place_id, число отзывов. ВАЖНО: формат по доке, лайвом не подтверждён (на тестовом аккаунте эндпоинт устойчиво отвечает кодом 500 — вероятно, нужна платная опция кабинета)
- `xmlriver_check_index` — проверка индексации URL в Google/Яндексе (`inindex`)
- `xmlriver_suggest` — поисковые подсказки Google (до 50 фраз за вызов, платно за каждую фразу); гео подсказок — `location`/`country`
- `xmlriver_related_questions` — блок «Вопросы по теме» / People Also Ask Google (вопросы всегда; ответы — только при включённой платной опции «Related Questions с ответами» в кабинете)
- `xmlriver_balance` — баланс аккаунта / проверка ключа (бесплатно)

### wordstat — частотности Яндекса
- `wordstat_frequency` — широкая и точная частотность, уточняющие запросы (related) и ассоциации
- `wordstat_dynamics` — частотность по времени (день/неделя/месяц)
- `wordstat_regions` — распределение по регионам с индексом аффинити и именами регионов
- `wordstat_regions_tree` — полное дерево регионов Вордстата (id + имя)

### gsc — Google Search Console
- `gsc_query` — Search Analytics (клики/показы/CTR/позиция), авто-пагинация, `dataState` final/all, произвольные фильтры измерений (`filters`, AND-семантика) и `aggregationType` (auto/byProperty/byPage)
- `gsc_inspect_url` — URL Inspection: статус индексации, покрытие, canonical, последний обход, mobile usability, rich results
- `gsc_list_sites` — свойства, доступные авторизации
- `gsc_get_site` — уровень доступа к свойству
- `gsc_list_sitemaps` — отправленные sitemap со статусом
- `gsc_get_sitemap` — детали одного sitemap

Даты Search Analytics — по Pacific Time (не МСК); история ~16 месяцев; финальные данные отстают на ~2-3 дня (свежие — `dataState=all`); `ctr` в ответе — доля 0..1.

### ga4 — Google Analytics 4
- `ga4_list_properties` — свойства GA4, доступные авторизации (отсюда берётся `propertyId` — это **не** Measurement ID `G-XXXXXXX`)
- `ga4_metadata` — какие измерения и метрики доступны в ЭТОМ свойстве, включая кастомные (`customEvent:…`); поиск подстрокой, `blockedReasons` (по такой метрике отчёт вернёт нули) и `type` (целое/дробное для `metricFilters`)
- `ga4_check_compatibility` — совместима ли связка измерений/метрик в этом свойстве, без тяжёлого отчёта; при несовместимости — какие поля убрать
- `ga4_report` — произвольный отчёт: любые измерения × метрики, фильтры по измерениям, сортировка (полный Data API `runReport`)
- `ga4_bytime` — динамика метрик по времени (день/час/неделя/месяц)
- `ga4_traffic_sources` — источники трафика: группа каналов, source/medium, кампания; `organicOnly` — только органика
- `ga4_geo` — страна/регион/город
- `ga4_devices` — тип устройства/ОС/браузер
- `ga4_top_pages` — топ страниц по `pagePath`, странице входа или заголовку; фильтры `organicOnly` и `pathContains`
- `ga4_events` — события по `eventName`; `keyEventsOnly` — только ключевые события (бывшие конверсии)
- `ga4_realtime` — отчёт в реальном времени (последние 30 минут)
- `ga4_funnel` — воронка (`runFunnelReport`): сколько дошло до каждого шага и где отвалились; шаг = событие и/или условия по измерениям, разбивка по измерению. Внутри шагов действует схема Exploration API (`pagePath` там недоступен), корзина квоты отдельная и запрос дороже обычного отчёта
- `ga4_annotations` — аннотации свойства: пометки на датах, включая созданные самой GA4 (`systemGenerated`) — частое объяснение необъяснимого скачка в динамике
- `ga4_property_details` — карточка свойства: таймзона отчётов, валюта, уровень сервиса (STANDARD/360) и потоки данных с их Measurement ID `G-XXXXXXX`

Во всех отчётных инструментах есть `includeQuota` — сколько «токенов» Data API съел запрос и сколько осталось на час/сутки.

Единицы и даты: `bounceRate`/`engagementRate` GA4 отдаёт **долей 0..1** (не процентами); даты считаются в таймзоне **свойства** — принимаются `YYYY-MM-DD` и ключевые слова GA4 (`today`, `yesterday`, `28daysAgo`), фактическая таймзона возвращается в ответе. В ответах есть `totalRows`/`truncated`, а `thresholded: true` означает, что часть данных скрыта порогом конфиденциальности GA4.

### ywm — Яндекс.Вебмастер
- `ywm_hosts` — id пользователя + подтверждённые сайты
- `ywm_summary` — ИКС, страниц в поиске, исключено, проблемы сайта по важности
- `ywm_search_queries` — аналитика запросов по URL (~2 недели по умолчанию; переопределяется dateFrom/dateTo)
- `ywm_queries_history` — суммарные показы/клики/позиции по времени
- `ywm_recommended_queries` — приближённые рекомендованные запросы (спрос + недобор кликов)
- `ywm_popular` — популярные запросы хоста
- `ywm_indexing_history` — страниц в поиске по времени
- `ywm_sqi_history` — ИКС по времени
- `ywm_external_links` — выборка внешних ссылок + общее число
- `ywm_broken_links` — битые внутренние/внешние ссылки
- `ywm_diagnostics` — проблемы сайта
- `ywm_important_urls` — отслеживаемые URL со статусом индексации/поиска
- `ywm_sitemaps` — sitemap со статусом

### metrika — Яндекс.Метрика
- `metrika_report` — произвольный отчёт: любые dimensions × metrics, фильтры, сортировка (полный Stat API)
- `metrika_bytime` — метрики по времени (день/неделя/месяц/час)
- `metrika_traffic_sources` — визиты/пользователи/отказы по источникам трафика
- `metrika_geo` — визиты по стране/региону/городу
- `metrika_devices` — визиты по устройству/ОС/браузеру
- `metrika_goals` — список целей (конверсий)
- `metrika_counters` — доступные счётчики
- `metrika_landing_behavior` — поведение на посадочных + достижения целей
- `metrika_search_phrases` — поисковые фразы (органика)
- `metrika_top_landings` — топ органических посадочных

### aparser — мост к self-hosted A-Parser
- `aparser_ping` — проверка связи с инстансом и пароля API
- `aparser_status` — вердикт готовности: версия, установленные парсеры, очередь, живые прокси
- `aparser_proxies` — живые прокси инстанса (можно по пачкам proxy checkers; креды прокси не выводятся)
- `aparser_parsers` — парсеры, установленные на инстансе
- `aparser_parser_fields` — поля результата, которые умеет вернуть парсер (flat + arrays)
- `aparser_get_preset` — опции config-пресета парсера (чувствительные значения маскируются)
- `aparser_serp_google` — органика Google (парсер `SE::Google`); прокси по умолчанию + preflight живых прокси
- `aparser_serp_yandex` — органика Яндекса (`SE::Yandex`); регион через `lr`
- `aparser_suggest` — поисковые подсказки Google/Яндекса
- `aparser_request` — универсальный синхронный запрос к любому парсеру (`oneRequest`)
- `aparser_bulk_request` — пакетный запрос: один парсер, много запросов в N потоков (`bulkRequest`)

> Нужен **свой** запущенный инстанс [A-Parser](https://a-parser.com/?ref=38832) (лицензия + сервер): мост им управляет, но не хостит и не проксирует его. Прокси и прокси-чекеры (пачки) настраиваются один раз в GUI A-Parser — мост их читает, проверяет (preflight) и выбирает (`checkers`), но не создаёт. v1 синхронный и read-only: очередь задач и большие асинхронные выгрузки не подключены.

## Быстрый старт

### Вариант 1 — в один клик для Claude Desktop (.mcpb)

Самый простой способ, ничего ставить руками не нужно: скачай нужный `.mcpb` со [страницы релиза](https://github.com/antohins/seo-tools-mcp/releases/latest) и **открой двойным кликом** — Claude Desktop поставит сервер сам и спросит ключи в диалоге установки.

- Серверы с API-ключом (`xmlstock`, `xmlriver`, `wordstat`, `aparser`) — ключи вводятся прямо в установщике.
- Серверы на OAuth (`gsc`, `ga4`, `ywm`, `metrika`) ничего не спрашивают: авторизация проходит в чате (`<server>_oauth_start` → `<server>_oauth_finish`).

Бандлы самодостаточны (~0.2 МБ, зависимости внутри), Node.js 20+ нужен только для варианта с npx. Собрать самому: `pnpm build:mcpb`.

### Вариант 2 — плагин для Claude Code (маркетплейс)

Аналог `.mcpb`, но для Claude Code: сервер, ключи и подсказки ставятся одной командой, ключи спрашиваются диалогом, секреты уходят в системное хранилище, а не в открытый файл.

```bash
claude plugin marketplace add antohins/seo-tools-mcp
```

Дальше — **только те источники, которые нужны**; каждый плагин тянет ровно один сервер:

```bash
claude plugin install xmlstock@seo-tools-mcp
claude plugin install gsc@seo-tools-mcp
claude plugin install ga4@seo-tools-mcp
```

Доступны `xmlstock`, `xmlriver`, `wordstat`, `gsc`, `ga4`, `ywm`, `metrika`, `aparser` — и `seo-tools`, который ставит все восемь сразу. Бандл удобен, но это ~100 инструментов в каждой сессии: если работаешь только с Вебмастером и Метрикой, ставь два плагина, а не бандл.

Ключи можно ввести сразу (`--config KEY=VALUE`) или потом через `/plugin configure <плагин>@seo-tools-mcp`:

```bash
claude plugin install xmlstock@seo-tools-mcp --config XMLSTOCK_USER=12345 --config XMLSTOCK_KEY=...
```

Поля, помеченные как секретные (API-ключи, OAuth-секреты), Claude Code кладёт в системное хранилище; в `settings.json` они не попадают. Плагины на OAuth (`gsc`, `ga4`, `ywm`, `metrika`) при установке спрашивают только client_id/secret — сам вход проходит в чате через `<сервер>_oauth_start` → `<сервер>_oauth_finish`.

Вместе с сервером плагин приносит **навыки** — процедурные инструкции по своему источнику:
как не сжечь баланс на снятии позиций, почему `freq_broad` завышает трафик в разы, отчего
GA4 молча отдаёт нули, чем усреднённая позиция GSC отличается от снятой из выдачи. В контексте
они всегда занимают ~110 токенов на навык и разворачиваются, только когда действительно нужны.

### Вариант 3 — через npx (без клонирования)

Каждый сервер — самодостаточный npm-пакет `seo-tools-mcp-<сервер>`; ставится одной командой:

```bash
claude mcp add xmlstock --scope user -- npx -y seo-tools-mcp-xmlstock
claude mcp add xmlriver --scope user -- npx -y seo-tools-mcp-xmlriver
claude mcp add wordstat --scope user -- npx -y seo-tools-mcp-wordstat
claude mcp add gsc      --scope user -- npx -y seo-tools-mcp-gsc
claude mcp add ga4      --scope user -- npx -y seo-tools-mcp-ga4
claude mcp add ywm      --scope user -- npx -y seo-tools-mcp-ywm
claude mcp add metrika  --scope user -- npx -y seo-tools-mcp-metrika
claude mcp add aparser  --scope user -- npx -y seo-tools-mcp-aparser
```

#### Нужен только один сервер?

Серверы **не связаны** между собой: возьмите один пакет и игнорируйте остальные. Каждый самодостаточен — общий код `@seo-tools/shared` вшит в сборку, так что лишних зависимостей и «хвоста» монорепы не тянется. Достаточно установить нужный пакет с npm — там уже всё из коробки (`npx -y` скачает и запустит его сам):

| Пакет (npm) | Сервер |
|---|---|
| [`seo-tools-mcp-xmlstock`](https://www.npmjs.com/package/seo-tools-mcp-xmlstock) | SERP Google/Яндекс + Wordstat |
| [`seo-tools-mcp-xmlriver`](https://www.npmjs.com/package/seo-tools-mcp-xmlriver) | SERP Google/Яндекс + проверка индексации |
| [`seo-tools-mcp-wordstat`](https://www.npmjs.com/package/seo-tools-mcp-wordstat) | частотности Яндекса (Yandex Cloud) |
| [`seo-tools-mcp-gsc`](https://www.npmjs.com/package/seo-tools-mcp-gsc) | Google Search Console |
| [`seo-tools-mcp-ga4`](https://www.npmjs.com/package/seo-tools-mcp-ga4) | Google Analytics 4 |
| [`seo-tools-mcp-ywm`](https://www.npmjs.com/package/seo-tools-mcp-ywm) | Яндекс.Вебмастер |
| [`seo-tools-mcp-metrika`](https://www.npmjs.com/package/seo-tools-mcp-metrika) | Яндекс.Метрика |
| [`seo-tools-mcp-aparser`](https://www.npmjs.com/package/seo-tools-mcp-aparser) | мост к self-hosted A-Parser |

```bash
# добавить один сервер в Claude Code
claude mcp add xmlstock --scope user -- npx -y seo-tools-mcp-xmlstock

# или запустить напрямую (ключи через env)
XMLSTOCK_USER=... XMLSTOCK_KEY=... npx -y seo-tools-mcp-xmlstock
```

В любом MCP-клиенте (Claude Desktop, Cursor…) — прописывается один блок в `mcpServers`:

```json
{
  "mcpServers": {
    "xmlstock": {
      "command": "npx",
      "args": ["-y", "seo-tools-mcp-xmlstock"],
      "env": { "XMLSTOCK_USER": "...", "XMLSTOCK_KEY": "..." }
    }
  }
}
```

> Прямая установка одного пакета по GitHub-ссылке (`npm i github:antohins/seo-tools-mcp`) **не поддерживается**: это pnpm-монорепа, отдельный подпакет так не ставится. Для установки из исходников — вариант Б ниже (клонировать + собрать). Готовые пакеты живут на npm.

### Вариант 4 — из исходников

```bash
git clone https://github.com/antohins/seo-tools-mcp.git && cd seo-tools-mcp
pnpm install && pnpm build
ROOT=$(pwd)
for s in xmlstock xmlriver wordstat gsc ga4 ywm metrika aparser; do
  claude mcp add "$s" --scope user -- node "$ROOT/servers/$s/dist/index.js"
done
```

Дальше (любой вариант) — **прямо в диалоге Claude Code**: «настрой доступ к xmlstock» → агент вызовет `xmlstock_auth_status`, подскажет, какие ключи нужны и где их взять, примет их через `xmlstock_set_credentials` и сохранит. После этого спрашивайте данные обычным языком: «сними топ-10 Яндекса по запросу X», «частотность фраз …», «клики/показы из GSC за месяц». Ключи и OAuth настраиваются один раз (см. [Получение доступов](#получение-доступов-по-сервису)).

## Интерактивная авторизация (в любой сессии)

У каждого сервера есть auth-инструменты — ключи можно выдавать прямо в диалоге, без правки файлов и перезапуска:

- `<server>_auth_status` — вызывается в начале работы: показывает, какие ключи заданы (маскированно), каких не хватает и как их получить (шаги регистрации).
- `<server>_set_credentials` — сохраняет переданные значения в `~/.config/seo-tools-mcp/.env` (права 600) и применяет сразу.
- `gsc_save_sa_json` — принимает содержимое JSON-ключа сервис-аккаунта, кладёт его в конфиг-директорию и возвращает email, который нужно добавить в GSC.
- `ywm_oauth_start` / `metrika_oauth_start` → ссылка авторизации Яндекса; пользователь открывает, разрешает, копирует код → `*_oauth_finish` обменивает код на access+refresh токены. Дальше токен **обновляется автоматически** при протухании (code flow, не implicit).

Типовой сценарий новой сессии: «настрой доступ к xmlstock» → агент вызывает `xmlstock_auth_status` → просит недостающие ключи → `xmlstock_set_credentials` → работает.

⚠ Ключи, переданные через чат, проходят через контекст модели. Для максимальной гигиены можно по-прежнему вписать их в `~/.config/seo-tools-mcp/.env` руками — серверы подхватят файл сами.

## Мультиаккаунт

Клиентские сайты раскиданы по разным аккаунтам Google/Яндекса — поддерживаются **именованные профили**:

- Каждый рабочий инструмент принимает опциональный параметр **`account`** («clientX», «agency»...). Без него используется основной профиль — обратная совместимость полная.
- Ключи профиля хранятся в том же конфиге с суффиксом: `GSC_REFRESH_TOKEN__clientX`, `YANDEX_OAUTH_TOKEN__clientX`, `XMLSTOCK_KEY__clientX`…
- Добавление профиля: `gsc_oauth_start(account="clientX")` → пользователь авторизуется под **другим** Google-аккаунтом → `gsc_oauth_finish(account="clientX")`. Аналогично `ywm_oauth_start/finish(account=...)` для Яндекса; API-ключи — `<server>_set_credentials(account="clientX", ...)`.
- **OAuth-приложения общие**: один Google-client и одно Яндекс-приложение обслуживают все профили (клиент создаётся один раз, авторизаций — сколько угодно). Per-account хранятся только токены; refresh обновляет токен своего профиля.
- Резолв строгий: `account="clientX"` без настроенных ключей → ошибка со списком настроенных профилей (никаких тихих фолбэков в чужой аккаунт). Дефолты (`GSC_SITE_URL__clientX`, `YWM_HOST_ID__clientX`, `METRIKA_COUNTER_ID__clientX`) — тоже per-account.
- `<server>_auth_status` показывает все профили и их ключи (маскированно).
- Альтернатива для жёсткой изоляции: отдельный env-файл через `SEO_TOOLS_MCP_ENV` (при заданном пути домашний конфиг НЕ читается).

## Установка

```bash
cd seo-tools-mcp
pnpm install
pnpm build
```

## Секреты

Единый env-файл: `~/.config/seo-tools-mcp/.env` (права 600). Все серверы читают его при старте, а `*_set_credentials`/`*_oauth_finish` пишут в него сами — ручная правка не обязательна. Шаблон — [.env.example](.env.example). Переменные из окружения процесса имеют приоритет над файлом. Альтернативный путь к файлу — `SEO_TOOLS_MCP_ENV` (так один хост может держать несколько независимых профилей: разные `claude mcp add` с разным `SEO_TOOLS_MCP_ENV`).

## Регистрация в Claude Code

```bash
ROOT=/path/to/seo-tools-mcp
claude mcp add xmlstock --scope user -- node $ROOT/servers/xmlstock/dist/index.js
claude mcp add wordstat --scope user -- node $ROOT/servers/wordstat/dist/index.js
claude mcp add gsc      --scope user -- node $ROOT/servers/gsc/dist/index.js
claude mcp add ga4      --scope user -- node $ROOT/servers/ga4/dist/index.js
claude mcp add ywm      --scope user -- node $ROOT/servers/ywm/dist/index.js
claude mcp add metrika  --scope user -- node $ROOT/servers/metrika/dist/index.js
```

`--scope user` — доступно во всех сессиях/проектах. Для шаринга на команду — `--scope project` (создаст `.mcp.json` в репозитории; секреты подставлять только через `${VAR}`).

## Получение доступов (по сервису)

> Всё из этого раздела продублировано в ответах `<server>_auth_status` — агент сам подскажет шаги. Ниже — для чтения человеком.

### XMLStock (приоритет 1) — SERP Google + Яндекс

1. Регистрация: https://xmlstock.com → личный кабинет, пополнить баланс (Google XML и Яндекс Live — от 12 ₽/1000 запросов).
2. Взять ID пользователя и API-ключ → `XMLSTOCK_USER`, `XMLSTOCK_KEY` (или через `xmlstock_set_credentials`).
3. Проверка: `xmlstock_balance`.

Нюансы (выяснено на живых ответах):
- подсветки выдачи (`text_bolds`) — параметр `hlword=1`, тег `<hlword>` вложенным XML (парсится через stopNodes, соседние слова склеиваются во фразы); PAA и related searches — `related=1` (PAA только у Google);
- **mobile-выдача не отдаёт hlword/PAA/related** — мобильный слепок только позиции+сниппеты, подсветки снимать с desktop;
- страницы с 0 у обоих движков; органики на странице бывает <10 — сервер сам добирает страницей (+1 платный запрос);
- `lr` принимает id регионов Яндекса для обоих движков (XMLStock маппит на Google сам);
- ошибки HTTP 200 + `<error code>`: 20–25/101/110/111/500 ретраятся, 55 — rate-limit с паузой, 15 = пустая выдача (деньги списаны), 31/42 — фатальные (авторизация);
- **Wordstat у XMLStock НЕТ** — частотности через отдельный сервер (официальный API Вордстата Яндекса).

### Wordstat (приоритет 1) — частотности Яндекса

Официальный **Wordstat API v2** (в составе Yandex Cloud Search API) — бесплатный, без заявок и OAuth. Один раз в https://console.yandex.cloud:

1. Создать каталог (folder) или взять существующий → его ID в `WORDSTAT_FOLDER_ID`.
2. Создать сервисный аккаунт с ролью **`search-api.webSearch.user`**.
3. Выпустить для него **API-ключ** с областью действия **`yc.search-api.execute`** → `WORDSTAT_API_KEY`.
4. Проверка: `wordstat_frequency` по любой фразе.

Нюансы: точная частотность = операторы `"!слово !слово"` (поддерживаются в topRequests/regions; в dynamics — только при period=daily); данные topRequests — за последние 30 дней; `count` приходит строками (парсится); квоты **10 rps / 100 запросов в час** (429 ретраится, но для массового съёма закладывать троттлинг); associations максимум 20.

### Google Search Console (приоритет 1)

Два пути; **рекомендуемый — OAuth**: токен наследует доступ твоего Google-аккаунта и видит **все его свойства GSC разом** (включая будущие), добавлять пользователя в каждое свойство не нужно.

**Путь A — OAuth (один раз):**

1. https://console.cloud.google.com → проект → APIs & Services → Library → включить **Google Search Console API**.
2. **OAuth consent screen**: тип External; себя — в Test users. (Для refresh-токена дольше 7 дней — нажать **Publish app**; предупреждение «unverified» при авторизации — норма для личного использования.)
3. **Credentials → Create credentials → OAuth client ID → Desktop app** → взять client ID + secret.
4. В чате: `gsc_oauth_start` (передать clientId+secret) → открыть ссылку → разрешить → браузер редиректнется на `localhost:8585`, код подхватится автоматически → `gsc_oauth_finish`.
5. Проверка: `gsc_list_sites` — покажет все свойства аккаунта.

**Путь B — сервис-аккаунт (для headless-кронов):** IAM → Service Accounts → JSON-ключ → `gsc_save_sa_json` (или путь в `GSC_SA_JSON`) → добавить email аккаунта в **каждое** нужное свойство GSC (Настройки → Пользователи и права, «Полный»).

Если заданы оба — приоритет у OAuth.

### Google Analytics 4 (приоритет 1)

Авторизация та же, что у GSC, и **OAuth-приложение общее** (`GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` переиспользуются). Но scope у GA4 свой, поэтому нужна отдельная авторизация — один раз.

1. В том же проекте console.cloud.google.com → APIs & Services → Library → включить **Google Analytics Data API** и **Google Analytics Admin API**.
2. В чате: `ga4_oauth_start` (если client ID/secret уже сохранены для GSC — без аргументов) → открыть ссылку → разрешить → браузер редиректнется на `localhost:8586` (порт отличается от GSC, чтобы серверы не конфликтовали), код подхватится автоматически → `ga4_oauth_finish`.
3. Проверка: `ga4_list_properties` — покажет все свойства аккаунта и их `propertyId`.
4. Удобно сохранить свойство по умолчанию: `ga4_set_credentials` → `GA4_PROPERTY_ID` (числовой id из п. 3), иначе передавать `propertyId` в каждом вызове.

**Путь B — сервис-аккаунт:** JSON-ключ → `ga4_save_sa_json` → добавить email аккаунта в свойство GA4 (Администратор → Управление доступом к ресурсу, роль «Просмотр»).

### Яндекс OAuth (Вебмастер + Метрика — одно приложение, один токен)

1. Один раз: https://oauth.yandex.ru/client/new → «Веб-сервисы», Redirect URI: `https://oauth.yandex.ru/verification_code`. Права (scope): **Яндекс.Вебмастер** — «Получение информации о сайтах» (`webmaster:hostinfo`) + «Управление сайтами» (`webmaster:verify`); **Яндекс.Метрика** — «Получение статистики» (`metrika:read`). Взять ClientID и Client secret.
2. Дальше — интерактивно в чате: `ywm_oauth_start` (передать ClientID + secret, сохранятся) → открыть ссылку под аккаунтом-владельцем сайта/счётчика → скопировать код → `ywm_oauth_finish`. Получатся access+refresh токены, общие для ywm и metrika; **обновляются автоматически**.
3. Дефолты: `YWM_HOST_ID` (список — `ywm_hosts`), `METRIKA_COUNTER_ID` (список — `metrika_counters`) — задать через `*_set_credentials`, либо передавать в каждом вызове.
4. Ручная альтернатива: получить токен implicit-flow (`response_type=token`) и сохранить в `YANDEX_OAUTH_TOKEN` — но без refresh он протухнет (Вебмастер ~6 мес, Метрика ~1 год).

Ограничения API Яндекса (не баги серверов): фильтр по URL в Вебмастере есть только в query-analytics (данные ~2 недели); эндпоинта «рекомендованные запросы» в API v4 нет — `ywm_recommended_queries` аппроксимирует через спрос (DEMAND) + недобор кликов; поисковые фразы в Метрике в основном «Не определено» (шифрование).

### A-Parser (self-hosted) — SERP и сотни парсеров через свою коробку

1. Свой запущенный инстанс [A-Parser](https://a-parser.com/?ref=38832) (лицензия + сервер) — мост им управляет, но не хостит и не проксирует его.
2. В A-Parser: **Settings → API** — включить API-сервер, запомнить порт (обычно 9091) и пароль.
3. `APARSER_URL` = `http://<IP-инстанса>:<порт>/API` (обязательно с путём `/API`), `APARSER_PASSWORD` = пароль оттуда же → `aparser_set_credentials`.
4. Проверка: `aparser_ping`, затем `aparser_status` (готовность инстанса + живые прокси).

Нюансы: прокси и прокси-чекеры (пачки) настраиваются один раз в GUI — без живых прокси Google/Яндекс быстро банят, поэтому serp/suggest-инструменты делают preflight и предупреждают (`use_proxy=false` — на свой риск); пресеты и пачки по умолчанию задаются env (`APARSER_GOOGLE_PRESET`, `APARSER_YANDEX_PRESET`, `APARSER_PROXY_CHECKERS`, `APARSER_USE_PROXY`); v1 синхронный и read-only — очередь задач и мутирующие методы API не подключены.

## Формат дат и регионы

Даты — `YYYY-MM-DD` (МСК). Регионы: имя из встроенного списка частых регионов («Москва», «спб», «Казахстан»…) **или** числовой id региона Яндекса (`213`, `225`…) — числовой id работает всегда. Несколько регионов через запятую поддерживает только сервер `wordstat`; SERP-инструменты `xmlstock_*`/`xmlriver_*` принимают ОДИН регион. Полный справочник id — инструмент `wordstat_regions_tree`.

## Где и как использовать

Серверы — обычные stdio-процессы без привязки к машине. Четыре сценария:

### 1. Claude Code, локально

Зарегистрировать через `claude mcp add --scope user` (блок «Регистрация в Claude Code» выше) — доступно во всех проектах и сессиях.

### 2. Claude Code, другая машина

```bash
git clone https://github.com/antohins/seo-tools-mcp.git && cd seo-tools-mcp
pnpm install && pnpm build
# зарегистрировать серверы (блок «Регистрация в Claude Code» выше)
# ключи: скопировать ~/.config/seo-tools-mcp/.env со старой машины (chmod 600)
# ЛИБО выдать в диалоге через <server>_auth_status → <server>_set_credentials
```

### 3. Claude Desktop (локально)

В `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`):

```json
{
  "mcpServers": {
    "xmlstock": { "command": "node", "args": ["/ABS/PATH/seo-tools-mcp/servers/xmlstock/dist/index.js"] },
    "wordstat": { "command": "node", "args": ["/ABS/PATH/seo-tools-mcp/servers/wordstat/dist/index.js"] }
  }
}
```

Ключи подхватятся из `~/.config/seo-tools-mcp/.env` автоматически.

### 4. Удалённо: claude.ai / Claude Code с любого места

claude.ai (web/mobile) умеет только **remote MCP** (Streamable HTTP по публичному HTTPS). Наши stdio-серверы выносятся на VPS через мост [supergateway](https://github.com/supercorp-ai/supergateway):

```bash
# на сервере: клонировать/собрать как в сценарии 2, ключи в ~/.config/seo-tools-mcp/.env
npx -y supergateway --stateful --outputTransport streamableHttp --port 8801 \
  --stdio "node /opt/seo-tools-mcp/servers/xmlstock/dist/index.js"   # и так для каждого сервера, порты 8801–8805
```

Дальше nginx: TLS + proxy_pass на `127.0.0.1:880X` под **секретным путём** (например `/mcp-<длинный-случайный-токен>/xmlstock/`) — supergateway слушать только на localhost. Подключение:

- **Claude Code**: `claude mcp add --transport http xmlstock https://host/<секретный-путь>/xmlstock/mcp`
- **claude.ai**: Settings → Connectors → Add custom connector → тот же URL.

⚠ Секретный путь — минимальный гейт (custom connectors claude.ai не передают произвольные заголовки авторизации). За эндпоинтом — все ключи сервисов, поэтому: только HTTPS, длинный токен в пути, отдельный access-лог.

Альтернатива для Claude Code без HTTP-моста — stdio через ssh:

```bash
claude mcp add xmlstock --scope user -- ssh root@SERVER node /opt/seo-tools-mcp/servers/xmlstock/dist/index.js
```

## Разработка

```bash
pnpm build        # собрать все воркспейсы
pnpm typecheck    # только типы
pnpm test         # юнит-тесты (vitest, без сети)
pnpm test:live    # лайв-смоук по реальным API (нужны креды в конфиге; free-эндпоинты)
node servers/xmlstock/dist/index.js   # ручной запуск (stdio)
```

Юнит-тесты покрывают чистую логику: маскирование секретов, классификацию OAuth-ошибок, пагинацию Метрики/GSC (дедуп, `truncated`), фильтры, парсер SERP, регионы. Лайв-смоук поднимает каждый сервер и дёргает бесплатный инструмент (`xmlstock_balance`, `xmlriver_balance`, `wordstat_frequency`, `gsc_list_sites`, `ywm_hosts`, `metrika_counters`, `aparser_ping`) — проверка авторизации end-to-end.

Общий код (`shared/`): HTTP-клиент с ретраями на 429/5xx (3 попытки, экспоненциальный backoff, Retry-After), загрузчик env + персистентный конфиг, фабрика auth-инструментов, Яндекс-OAuth с авто-refresh, JSON-хелперы MCP, счётчик расхода платных вызовов. XMLStock дополнительно ретраит свои «временные» коды из тела XML, код 15 («ничего не найдено») трактуется как пустая выдача.

Сборка серверов — `tsup`: `shared/` вбивается в единый `dist/index.js` каждого сервера (рантайм-зависимости остаются external), поэтому npm-пакет самодостаточен.

## Публикация в npm (мейнтейнерам)

Каждый сервер публикуется как отдельный пакет `seo-tools-mcp-<сервер>`; `shared/` приватный и в npm не уходит (вбит в серверы). Версии всех серверов держим синхронно.

```bash
npm login
pnpm -r build                 # shared (tsc) → серверы (tsup-бандл)
pnpm -r publish --access public   # публикует 8 серверов; private-пакеты (shared, корень) пропускаются
```

`pnpm publish` сам подставляет реальные версии вместо `workspace:*` и не даст опубликовать при грязном рабочем дереве.

Бамп версии — **только через корневой `package.json`**: правишь версию там и запускаешь `pnpm version:sync`, который разносит её по всем 42 местам (`package.json` и `server.json` каждого сервера, литерал в `new McpServer({ version })`, манифесты плагинов). `pnpm -r exec npm version patch` для этого НЕ годится: он обновит только пакеты серверов, остальное останется на старой версии, и `pnpm version:check` в CI упадёт. Проверить без записи — `pnpm version:check`.

## Контрибьютинг

PR приветствуются — см. [CONTRIBUTING.md](CONTRIBUTING.md). История изменений — [CHANGELOG.md](CHANGELOG.md). Уязвимости — приватно через [Security Advisories](https://github.com/antohins/seo-tools-mcp/security/advisories/new) (детали — [SECURITY.md](SECURITY.md)).

## Лицензия

[MIT](LICENSE) © antohins

More