Skip to main content
Glama
README.md
# Yandex MCP

A monorepo of MCP (Model Context Protocol) servers for Yandex APIs. Provides AI assistants with access to Yandex Search, Wordstat, Webmaster, Metrika, and Direct through a unified interface.

Built for Russian and CIS market analysis -- keyword research, search analytics, site monitoring, web traffic insights, and ad campaign management.

## Packages

| Package | Description | Tools |
|---------|-------------|-------|
| [yandex-search-mcp](packages/yandex-search-mcp) | Yandex Search API v2 (Cloud) | 1 |
| [yandex-wordstat-mcp](packages/yandex-wordstat-mcp) | Yandex Wordstat (Cloud Search API v2) -- keyword research | 5 |
| [yandex-webmaster-mcp](packages/yandex-webmaster-mcp) | Yandex Webmaster API v4 -- site analytics | 33 |
| [yandex-metrika-mcp](packages/yandex-metrika-mcp) | Yandex Metrika API -- web analytics | 12 |
| [yandex-direct-mcp](packages/yandex-direct-mcp) | Yandex Direct API v5 -- ad campaigns | 43 |

**94 tools total** across all packages (counted by `node scripts/smoke-tools.mjs`).

> **Package names.** The packages go to npm under the **`@stufently/*` scope**
> (`@stufently/yandex-webmaster-mcp` and so on), starting with v2.2.0. The *unscoped* names — `yandex-search-mcp`,
> `yandex-wordstat-mcp`, `yandex-webmaster-mcp`, `yandex-metrika-mcp` — belong to a
> **different publisher** on the public registry (`altrr2`,
> [altrr2/yandex-tools-mcp](https://github.com/altrr2/yandex-tools-mcp), first published
> 2025-12-20, three months before this repo existed). Never `npx` an unscoped name with your
> Yandex tokens in the environment: that hands your credentials to unrelated code. Always type
> the `@stufently/` prefix.

## Install

Paste this into a terminal. It starts the Webmaster server on stdin; the same `npx` command is what every client block below runs.

```bash
YANDEX_WEBMASTER_TOKEN=your-oauth-token npx -y @stufently/yandex-webmaster-mcp
```

**Node.js >= 22** is the only prerequisite — no clone, no Bun, no build step. Swap the package and the variables for another server:

| Server | Command | Variables it needs |
|---|---|---|
| Search | `npx -y @stufently/yandex-search-mcp` | `YANDEX_SEARCH_API_KEY`, `YANDEX_FOLDER_ID` |
| Wordstat | `npx -y @stufently/yandex-wordstat-mcp` | `WORDSTAT_API_KEY`, `WORDSTAT_FOLDER_ID` |
| Webmaster | `npx -y @stufently/yandex-webmaster-mcp` | `YANDEX_WEBMASTER_TOKEN` |
| Metrika | `npx -y @stufently/yandex-metrika-mcp` | `YANDEX_METRIKA_TOKEN` |
| Direct | `npx -y @stufently/yandex-direct-mcp` | `YANDEX_DIRECT_TOKEN` (+ optional `YANDEX_DIRECT_CLIENT_LOGIN`, `YANDEX_DIRECT_SANDBOX`) |

All five servers start without their variables and fail only on the first tool call, with an
error that names the missing variable.

Append a version (`@stufently/yandex-webmaster-mcp@2.2.0`) to pin it instead of taking the
latest release on every start. The OAuth helper runs the same way:
`npx -y @stufently/yandex-webmaster-mcp auth` (and `@stufently/yandex-metrika-mcp auth`).

Do not `npx` the **unscoped** names — they belong to a different publisher (see the note
above).

## Verified clients and platforms

Only what has actually been run is listed as verified. Everything else in
[Client Configuration](#client-configuration) is a documented config that nobody has
checked end to end.

| Client / runtime | OS | How it runs | Status | Evidence |
|---|---|---|---|---|
| Claude Code 2.1 | Ubuntu 26.04 LTS, x86_64 | Node.js 24 from a checkout, stdio: `yandex-direct`, `yandex-wordstat`, `yandex-webmaster` | Verified | Daily use on the maintainer's host |
| Codex CLI 0.162 | Ubuntu 26.04 LTS, x86_64 | Node.js 24 from a checkout: `yandex-wordstat`, `yandex-webmaster` | Configured, not checked separately | Maintainer's host config |
| Node.js 22 + Bun | Ubuntu (`ubuntu-latest`) | Unit tests; all five servers answer `tools/list` over stdio | Verified in CI | `ci.yml` (`node --test`, `scripts/smoke-tools.mjs`) |
| `yandex-metrika`, `yandex-search` with a real token | any | — | Not verified in a client | CI smoke test only |
| `npx @stufently/*` from npm | Ubuntu, `node:24-alpine` | `npx -y @stufently/yandex-*-mcp`, stdio | Verified | All five answer `tools/list` from the published 2.2.0 packages (2026-10-15) |
| Claude Desktop (`.mcpb`), Claude Code plugin | any | Desktop Extension, plugin | Not verified | Bundles are built in CI, never installed in a real Desktop or as a plugin |
| Cursor, Windsurf, Zed | any | Configs below | Not verified | Documented only |
| macOS, Windows | — | Any client | Not verified | No CI runner on these systems |

## Client Configuration

Every block registers all five servers; drop the ones you do not need. Values are placeholders —
see [Environment Variables](#environment-variables) for where each one comes from. `command` is
the same `npx` as in the terminal snippet above.

### Claude Code

Project file: `.mcp.json` in the project root (Claude Code also reads a user-level config; this block is the project file).

```json
{
  "mcpServers": {
    "yandex-search": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-search-mcp"],
      "env": {
        "YANDEX_SEARCH_API_KEY": "your-cloud-api-key",
        "YANDEX_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-wordstat": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-wordstat-mcp"],
      "env": {
        "WORDSTAT_API_KEY": "your-cloud-api-key",
        "WORDSTAT_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-webmaster": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-webmaster-mcp"],
      "env": { "YANDEX_WEBMASTER_TOKEN": "your-oauth-token" }
    },
    "yandex-metrika": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-metrika-mcp"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    },
    "yandex-direct": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-direct-mcp"],
      "env": {
        "YANDEX_DIRECT_TOKEN": "your-oauth-token",
        "YANDEX_DIRECT_CLIENT_LOGIN": "",
        "YANDEX_DIRECT_SANDBOX": "true"
      }
    }
  }
}
```

Or from the terminal, one server at a time:

```bash
claude mcp add yandex-search --scope user \
  --env YANDEX_SEARCH_API_KEY=your-cloud-api-key --env YANDEX_FOLDER_ID=your-folder-id \
  -- npx -y @stufently/yandex-search-mcp
claude mcp add yandex-wordstat --scope user \
  --env WORDSTAT_API_KEY=your-cloud-api-key --env WORDSTAT_FOLDER_ID=your-folder-id \
  -- npx -y @stufently/yandex-wordstat-mcp
claude mcp add yandex-webmaster --scope user \
  --env YANDEX_WEBMASTER_TOKEN=your-oauth-token \
  -- npx -y @stufently/yandex-webmaster-mcp
claude mcp add yandex-metrika --scope user \
  --env YANDEX_METRIKA_TOKEN=your-oauth-token \
  -- npx -y @stufently/yandex-metrika-mcp
claude mcp add yandex-direct --scope user \
  --env YANDEX_DIRECT_TOKEN=your-oauth-token \
  -- npx -y @stufently/yandex-direct-mcp
```

Check with `claude mcp list`. The [plugin](#claude-code-plugin) does the same in two commands
and also installs the bundled skills.

### Claude Desktop

File: `claude_desktop_config.json` — macOS `~/Library/Application Support/Claude/claude_desktop_config.json`, Windows `%APPDATA%\Claude\claude_desktop_config.json`, Linux `~/.config/Claude/claude_desktop_config.json`.

```json
{
  "mcpServers": {
    "yandex-search": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-search-mcp"],
      "env": {
        "YANDEX_SEARCH_API_KEY": "your-cloud-api-key",
        "YANDEX_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-wordstat": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-wordstat-mcp"],
      "env": {
        "WORDSTAT_API_KEY": "your-cloud-api-key",
        "WORDSTAT_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-webmaster": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-webmaster-mcp"],
      "env": { "YANDEX_WEBMASTER_TOKEN": "your-oauth-token" }
    },
    "yandex-metrika": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-metrika-mcp"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    },
    "yandex-direct": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-direct-mcp"],
      "env": {
        "YANDEX_DIRECT_TOKEN": "your-oauth-token",
        "YANDEX_DIRECT_CLIENT_LOGIN": "",
        "YANDEX_DIRECT_SANDBOX": "true"
      }
    }
  }
}
```

One click, without editing JSON: download the `.mcpb` file for the server you want from
https://github.com/stufently/yandex-mcp/releases/latest and open it. Claude Desktop installs
the extension and asks for the token. Each server is its own file (`yandex-search-mcp`,
`yandex-wordstat-mcp`, `yandex-webmaster-mcp`, `yandex-metrika-mcp`, `yandex-direct-mcp`).
That path works before the npm packages are published. `YANDEX_DIRECT_SANDBOX` set to `true`
points Direct at the sandbox; leave it false for real campaigns. Restart the app after a manual edit.

Each release includes `checksums.txt` with the SHA256 checksum of every `.mcpb` bundle.
Download it alongside your bundle and verify the download before opening it:

```bash
sha256sum -c --ignore-missing checksums.txt   # run next to the downloaded .mcpb
```

Releases from the next tag on also carry a GitHub build provenance attestation for each
`.mcpb`, signed in the release workflow. Check that a bundle was built by this
repository's workflow and not swapped afterwards (needs the GitHub CLI):

```bash
gh attestation verify <bundle>.mcpb --repo stufently/yandex-mcp
```

### Cursor

[![Add yandex-search MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=yandex-search&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdHVmZW50bHkveWFuZGV4LXNlYXJjaC1tY3AiXSwiZW52Ijp7IllBTkRFWF9TRUFSQ0hfQVBJX0tFWSI6InlvdXItY2xvdWQtYXBpLWtleSIsIllBTkRFWF9GT0xERVJfSUQiOiJ5b3VyLWZvbGRlci1pZCJ9fQ%3D%3D) `yandex-search`

[![Add yandex-wordstat MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=yandex-wordstat&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdHVmZW50bHkveWFuZGV4LXdvcmRzdGF0LW1jcCJdLCJlbnYiOnsiV09SRFNUQVRfQVBJX0tFWSI6InlvdXItY2xvdWQtYXBpLWtleSIsIldPUkRTVEFUX0ZPTERFUl9JRCI6InlvdXItZm9sZGVyLWlkIn19) `yandex-wordstat`

[![Add yandex-webmaster MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=yandex-webmaster&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdHVmZW50bHkveWFuZGV4LXdlYm1hc3Rlci1tY3AiXSwiZW52Ijp7IllBTkRFWF9XRUJNQVNURVJfVE9LRU4iOiJ5b3VyLW9hdXRoLXRva2VuIn19) `yandex-webmaster`

[![Add yandex-metrika MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=yandex-metrika&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdHVmZW50bHkveWFuZGV4LW1ldHJpa2EtbWNwIl0sImVudiI6eyJZQU5ERVhfTUVUUklLQV9UT0tFTiI6InlvdXItb2F1dGgtdG9rZW4ifX0%3D) `yandex-metrika`

[![Add yandex-direct MCP server to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=yandex-direct&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBzdHVmZW50bHkveWFuZGV4LWRpcmVjdC1tY3AiXSwiZW52Ijp7IllBTkRFWF9ESVJFQ1RfVE9LRU4iOiJ5b3VyLW9hdXRoLXRva2VuIiwiWUFOREVYX0RJUkVDVF9DTElFTlRfTE9HSU4iOiIiLCJZQU5ERVhfRElSRUNUX1NBTkRCT1giOiJ0cnVlIn19) `yandex-direct`

Each button installs one server; replace the `your-…` placeholders in `~/.cursor/mcp.json` afterwards. Or add the block below by hand.

File: `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project).

```json
{
  "mcpServers": {
    "yandex-search": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-search-mcp"],
      "env": {
        "YANDEX_SEARCH_API_KEY": "your-cloud-api-key",
        "YANDEX_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-wordstat": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-wordstat-mcp"],
      "env": {
        "WORDSTAT_API_KEY": "your-cloud-api-key",
        "WORDSTAT_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-webmaster": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-webmaster-mcp"],
      "env": { "YANDEX_WEBMASTER_TOKEN": "your-oauth-token" }
    },
    "yandex-metrika": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-metrika-mcp"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    },
    "yandex-direct": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-direct-mcp"],
      "env": {
        "YANDEX_DIRECT_TOKEN": "your-oauth-token",
        "YANDEX_DIRECT_CLIENT_LOGIN": "",
        "YANDEX_DIRECT_SANDBOX": "true"
      }
    }
  }
}
```

### Windsurf

File: `mcp_config.json` — macOS and Linux `~/.config/devin/mcp_config.json` (or `$XDG_CONFIG_HOME/devin/mcp_config.json`), Windows `%APPDATA%\devin\mcp_config.json`. Older builds used `~/.codeium/windsurf/mcp_config.json`; that path is not in the current docs.

```json
{
  "mcpServers": {
    "yandex-search": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-search-mcp"],
      "env": {
        "YANDEX_SEARCH_API_KEY": "your-cloud-api-key",
        "YANDEX_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-wordstat": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-wordstat-mcp"],
      "env": {
        "WORDSTAT_API_KEY": "your-cloud-api-key",
        "WORDSTAT_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-webmaster": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-webmaster-mcp"],
      "env": { "YANDEX_WEBMASTER_TOKEN": "your-oauth-token" }
    },
    "yandex-metrika": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-metrika-mcp"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    },
    "yandex-direct": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-direct-mcp"],
      "env": {
        "YANDEX_DIRECT_TOKEN": "your-oauth-token",
        "YANDEX_DIRECT_CLIENT_LOGIN": "",
        "YANDEX_DIRECT_SANDBOX": "true"
      }
    }
  }
}
```

### Zed

File: `settings.json` (command palette: `zed: open settings file`). The key is `context_servers`, not `mcpServers`.

```json
{
  "context_servers": {
    "yandex-search": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-search-mcp"],
      "env": {
        "YANDEX_SEARCH_API_KEY": "your-cloud-api-key",
        "YANDEX_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-wordstat": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-wordstat-mcp"],
      "env": {
        "WORDSTAT_API_KEY": "your-cloud-api-key",
        "WORDSTAT_FOLDER_ID": "your-folder-id"
      }
    },
    "yandex-webmaster": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-webmaster-mcp"],
      "env": { "YANDEX_WEBMASTER_TOKEN": "your-oauth-token" }
    },
    "yandex-metrika": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-metrika-mcp"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    },
    "yandex-direct": {
      "command": "npx",
      "args": ["-y", "@stufently/yandex-direct-mcp"],
      "env": {
        "YANDEX_DIRECT_TOKEN": "your-oauth-token",
        "YANDEX_DIRECT_CLIENT_LOGIN": "",
        "YANDEX_DIRECT_SANDBOX": "true"
      }
    }
  }
}
```

### Codex

From the shell:

```bash
codex mcp add yandex-webmaster --env YANDEX_WEBMASTER_TOKEN=your-oauth-token \
  -- npx -y @stufently/yandex-webmaster-mcp
```

Or all five in `~/.codex/config.toml`:

```toml
[mcp_servers.yandex-search]
command = "npx"
args = ["-y", "@stufently/yandex-search-mcp"]
env = { YANDEX_SEARCH_API_KEY = "your-cloud-api-key", YANDEX_FOLDER_ID = "your-folder-id" }

[mcp_servers.yandex-wordstat]
command = "npx"
args = ["-y", "@stufently/yandex-wordstat-mcp"]
env = { WORDSTAT_API_KEY = "your-cloud-api-key", WORDSTAT_FOLDER_ID = "your-folder-id" }

[mcp_servers.yandex-webmaster]
command = "npx"
args = ["-y", "@stufently/yandex-webmaster-mcp"]
env = { YANDEX_WEBMASTER_TOKEN = "your-oauth-token" }

[mcp_servers.yandex-metrika]
command = "npx"
args = ["-y", "@stufently/yandex-metrika-mcp"]
env = { YANDEX_METRIKA_TOKEN = "your-oauth-token" }

[mcp_servers.yandex-direct]
command = "npx"
args = ["-y", "@stufently/yandex-direct-mcp"]
env = { YANDEX_DIRECT_TOKEN = "your-oauth-token" }
```

## Other ways to install

### Claude Code plugin

This repo ships as a Claude Code plugin, so you do not have to hand-write five MCP entries.
Add the repo as a marketplace and install:

```
/plugin marketplace add stufently/yandex-mcp
/plugin install yandex-mcp@stufently
```

Or without the interactive picker:

```bash
claude plugin marketplace add stufently/yandex-mcp
claude plugin install yandex-mcp@stufently --scope user
```

**What the plugin gives you over a hand-written MCP config:**

| | Plugin | Manual MCP config |
|---|---|---|
| Setup | two commands | edit JSON, restart client |
| All five servers | registered at once | five entries, one per server |
| Paths | resolved via `${CLAUDE_PLUGIN_ROOT}` | you hardcode absolute paths |
| Bundled skills | `yandex-keyword-research`, `yandex-competitive-analysis` installed too | not included — skills are Claude Code only |
| Updates | `/plugin update yandex-mcp@stufently` | `git pull` and re-check your paths |

The plugin installs the servers but **not the credentials** — Claude Code substitutes only
`${CLAUDE_PLUGIN_ROOT}`, `${CLAUDE_PLUGIN_DATA}` and `${CLAUDE_PROJECT_DIR}`. Supply the Yandex
tokens yourself, either by exporting them in your shell or by putting them in
`~/.claude/settings.json`:

```json
{
  "env": {
    "YANDEX_SEARCH_API_KEY": "...",
    "YANDEX_FOLDER_ID": "...",
    "WORDSTAT_API_KEY": "...",
    "WORDSTAT_FOLDER_ID": "...",
    "YANDEX_WEBMASTER_TOKEN": "...",
    "YANDEX_METRIKA_TOKEN": "...",
    "YANDEX_DIRECT_TOKEN": "..."
  }
}
```

Servers whose variables are missing simply fail on first use; the rest keep working, so you can
install the plugin and set up one API at a time.

### Docker

> The image `ghcr.io/stufently/yandex-mcp` is published with the next release tag; until then,
> build it from a clone with `docker build -t yandex-mcp .`.

One image holds all five servers. The first argument picks the server — `search`, `wordstat`,
`webmaster` (the default), `metrika` or `direct` — and anything after it goes to the server.
Keep `-i` (MCP talks over stdin) and pass the tokens with `-e`:

```bash
docker run -i --rm -e YANDEX_WEBMASTER_TOKEN ghcr.io/stufently/yandex-mcp webmaster
docker run -i --rm -e YANDEX_DIRECT_TOKEN ghcr.io/stufently/yandex-mcp direct
```

In a client config, Docker replaces `npx`. For example, Metrika in Claude Desktop:

```json
{
  "mcpServers": {
    "yandex-metrika": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "YANDEX_METRIKA_TOKEN", "ghcr.io/stufently/yandex-mcp", "metrika"],
      "env": { "YANDEX_METRIKA_TOKEN": "your-oauth-token" }
    }
  }
}
```

`-e NAME` without a value forwards the variable from the client's environment, so the token
stays out of `args`. The image runs as the unprivileged `node` user, and the entrypoint
`exec`s the server, so `docker stop` reaches Node directly. The Wordstat fallback file
`~/.config/yandex-cloud/wordstat.env` is not visible inside the container — pass
`WORDSTAT_API_KEY` and `WORDSTAT_FOLDER_ID` with `-e`. The OAuth helper prints a link and
reads the code from stdin, so it works too: `docker run -it --rm -e YANDEX_CLIENT_ID -e
YANDEX_CLIENT_SECRET ghcr.io/stufently/yandex-mcp webmaster auth`.

### From source

Needs Node.js >= 22 and [Bun](https://bun.sh/) on your `PATH` (the plugin needs Bun too: the
repo carries a `bun.lock`, so Claude Code installs the plugin's dependencies with Bun and does
not fall back to npm).

```bash
git clone https://github.com/stufently/yandex-mcp.git
cd yandex-mcp
bun install
cp .env.example .env   # then fill it in
```

For Webmaster and Metrika you can mint an OAuth token with the built-in flow (needs
`YANDEX_CLIENT_ID` / `YANDEX_CLIENT_SECRET` from your own Yandex OAuth app):

```bash
node packages/yandex-webmaster-mcp/src/index.mjs auth
node packages/yandex-metrika-mcp/src/index.mjs auth
```

Search and Wordstat do not use OAuth: both talk to Yandex Cloud and need a service-account API
key plus a folder ID.

The Metrika helper requests `metrika:read metrika:write`, because `create-counter` and
`delete-counter` are refused with HTTP 403 by a read-only token. To keep a token that cannot
modify anything, narrow the scope explicitly:

```bash
YANDEX_METRIKA_SCOPE="metrika:read" node packages/yandex-metrika-mcp/src/index.mjs auth
```

The two write tools then fail with a 403 that names the missing scope, instead of a bare
permission error.

A write-capable token does not mean an unguarded delete: `delete-counter` refuses unless the
call passes `confirm: true`, and the refusal happens before any request reaches Yandex. See
[Deleting a counter](packages/yandex-metrika-mcp/README.md#deleting-a-counter).

The same rule holds across the monorepo: an operation that cannot be undone through the API it
is called with asks for `confirm: true` first — `delete-counter` in Metrika,
[`delete-host`](packages/yandex-webmaster-mcp/README.md#deleting-a-host) in Webmaster, and the
[fourteen delete/update/bid tools](packages/yandex-direct-mcp/README.md#irreversible-tools-need-confirm-true)
in Direct. Additive and reversible writes (`create-counter`, `add-host`, `add_*`, archive and
suspend) run without one, so the confirmation stays a signal rather than a reflex. Search and
Wordstat are read-only and have nothing to guard.

To use a clone with any client block above, replace the `npx` command with `node` and the
script path, e.g. for Webmaster:

```json
"command": "node",
"args": ["/path/to/yandex-mcp/packages/yandex-webmaster-mcp/src/index.mjs"]
```

Inside a clone, the checked-in `.mcp.json` already registers all five servers for Claude Code
from `.env`, with paths relative to the repo root.

## Environment Variables

| Variable | Required by | Description |
|----------|-------------|-------------|
| `YANDEX_SEARCH_API_KEY` | yandex-search-mcp | API key from [Yandex Cloud](https://console.yandex.cloud/) |
| `YANDEX_FOLDER_ID` | yandex-search-mcp | Folder ID from Yandex Cloud |
| `WORDSTAT_API_KEY` | yandex-wordstat-mcp | Yandex Cloud API key (service account, role `search-api.webSearch.user`) |
| `WORDSTAT_FOLDER_ID` | yandex-wordstat-mcp | Yandex Cloud folder ID for Search API v2 |
| `YANDEX_WEBMASTER_TOKEN` | yandex-webmaster-mcp | OAuth token for Webmaster |
| `YANDEX_METRIKA_TOKEN` | yandex-metrika-mcp | OAuth token for Metrika (`metrika:read` to read, `metrika:write` also needed for counter tools) |
| `YANDEX_METRIKA_SCOPE` | OAuth flow (optional) | Override requested scopes; default `metrika:read metrika:write` |
| `YANDEX_DIRECT_TOKEN` | yandex-direct-mcp | OAuth token for Direct API v5 |
| `YANDEX_DIRECT_CLIENT_LOGIN` | yandex-direct-mcp (agencies) | Client login to act on behalf of |
| `YANDEX_DIRECT_SANDBOX` | yandex-direct-mcp (optional) | `true` to hit the Direct sandbox |
| `YANDEX_CLIENT_ID` | OAuth flow (optional) | Yandex OAuth app client ID |
| `YANDEX_CLIENT_SECRET` | OAuth flow (optional) | Yandex OAuth app client secret |

Copy `.env.example` to `.env` and fill in the values:

```bash
cp .env.example .env
```

You only need the variables for the servers you actually enable — each server reads its own
and ignores the rest.

## Example prompts

- Why did my site lose clicks last week?
- Which search phrases send the most visits to example.com?
- How many people search for this phrase on Yandex each month, and is that demand growing?
- Pause the Direct campaign that spent the most yesterday, then show me its new state.

## Common Prompts

Once a server is connected, these are the kinds of requests it can answer. Each example maps
onto real tools listed in the per-package READMEs.

### yandex-search (1 tool)

- "Search Yandex for «купить кондиционер» and show me the top 10 results with their domains."
- "Who ranks on Yandex for «доставка пиццы» in region 2 (Saint Petersburg)? Give positions and titles."
- "Search yandex.ru for «отзывы о клинике» with strict family mode and pull 20 results."
- "Get page 2 of Yandex results for «ремонт ноутбуков Москва»."

### yandex-wordstat (5 tools)

- "How many people search «купить ноутбук» on Yandex per month, and what related queries come up?"
- "Show the monthly trend for «горящие туры» over the last two years — is demand growing?"
- "Which Russian regions search «кондиционер» the most? Show volume and affinity index for the top 20."
- "Give me the Yandex region tree down to depth 2 so I can pick region IDs for filtering."
- "Compare desktop vs phone demand for «доставка еды»."

### yandex-webmaster (33 tools)

- "List my verified sites in Yandex Webmaster with their SQI."
- "Add https://example.com/sitemap.xml to Webmaster for example.com."
- "Which pages dropped out of search on example.com recently, and why? Show anything that came back too."
- "Why did pages drop out of search on example.com? Group the samples by exclusion reason."
- "What are my top 50 queries by clicks for example.com, and how did positions move?"
- "Show indexing history for example.com and flag any sudden drop in pages in search."
- "What site problems does Yandex report for example.com right now?"
- "Send https://example.com/new-page for recrawl and tell me my remaining daily quota."
- "Show broken internal links on example.com — and say which of them Yandex has not re-checked in months."

### yandex-metrika (12 tools)

- "Traffic summary for counter 12345678 last month — visits, users, bounce rate, average duration."
- "Where does my traffic come from? Break it down by source for counter 12345678."
- "Top 20 landing pages by pageviews for counter 12345678 last week."
- "Which countries and cities do my visitors come from?"
- "Custom report: `ym:s:visits` by `ym:s:trafficSource` and `ym:s:deviceCategory` for the last 7 days."

### yandex-direct (43 tools)

- "List my active Yandex Direct campaigns with their types and states."
- "Show the keywords in campaign 123 together with their current search bids."
- "Build a last-30-days campaign performance report with impressions, clicks, and cost."
- "Pause campaign 123, then confirm its new state."
- "Pull the GeoRegions dictionary so I can pick targeting IDs."

> **Direct writes are live.** Every write hits the production account — there is no dry-run.
> The fourteen that cannot be undone (`delete_*`, `update_*` and the three bid `set_*` tools)
> refuse unless the call passes `confirm: true`; `add_*`, `archive_*`/`unarchive_*`,
> `suspend_*`/`resume_*` and `moderate_ads` have an inverse tool and run straight away. Bids are
> expressed in micros (×10⁶). Set `YANDEX_DIRECT_SANDBOX=true` while experimenting. See
> [Irreversible tools](packages/yandex-direct-mcp/README.md#irreversible-tools-need-confirm-true).

## Skills

These are Claude Code skills, installed automatically with the [plugin](#claude-code-plugin). They are not
available to other MCP clients, which get the raw tools only.

### yandex-keyword-research

Research keywords and search trends for Russian/CIS markets. Combines Wordstat data (volumes, trends, regional distribution) with Yandex Search results for a complete keyword analysis.

### yandex-competitive-analysis

Analyze competitors and search landscape. Finds who ranks for target keywords, identifies content gaps, and discovers ranking opportunities across Russian/CIS markets.

## Development

Requires Node.js >= 22.0.0 and [Bun](https://bun.sh/) as the package manager.

```bash
# Install dependencies
bun install

# Lint
bun run lint

# Auto-fix lint issues
bun run lint:fix

# Format code
bun run format

# Unit tests (pure helpers: date windows, series extraction, XML parsing)
bun run test

# Smoke test: start every server over stdio and query tools/list
bun run smoke

# The same from the Docker image, with no credentials in the container
docker build -t yandex-mcp:local . && node scripts/docker-smoke.mjs yandex-mcp:local
```

### Tech Stack

- Pure ES Modules (`.mjs`, no TypeScript, no build step)
- `@modelcontextprotocol/sdk` (^1.27.1)
- `zod` (^4.3.6) for input validation
- [Biome](https://biomejs.dev/) for linting and formatting

### Project Structure

```
packages/
  yandex-search-mcp/       # 1 tool   - Yandex Search  (src/parse.mjs — XML parsing)
  yandex-wordstat-mcp/     # 5 tools  - Keyword research (src/dates.mjs — API window rules)
  yandex-webmaster-mcp/    # 33 tools - Site analytics  (SKILL.md — agent playbook;
                           #                            src/series.mjs — time series shapes;
                           #                            src/exclusions.mjs — exclusion reasons)
  yandex-metrika-mcp/      # 12 tools - Web analytics
  yandex-direct-mcp/       # 43 tools - Ad campaigns
scripts/smoke-tools.mjs    # Starts every server and checks tools/list
scripts/docker-smoke.mjs   # Same check against the Docker image, without credentials
Dockerfile                 # One image for all five servers (docker/entrypoint.sh picks one)
.claude/skills/            # Claude Code skills (shipped with the plugin)
.mcp.json                  # Local dev config
plugin.mcp.json            # Distribution config — the 5 servers the plugin registers
.claude-plugin/
  plugin.json              # Plugin manifest
  marketplace.json         # Marketplace catalog, so the repo can be added directly
```

Each package keeps its pure, testable helpers in separate modules next to `index.mjs`;
tests live in `packages/*/test/*.test.mjs` and run without network access.

## Contributing

Contributions are welcome. Please ensure:

1. Code passes `bun run lint` and `bun run test` with no errors.
2. All tools return both `content` (human-readable) and `structuredContent` (raw API data).
3. The `content` text must not contradict `structuredContent` — a summary that reports
   "0 data points" while the structured payload holds a full series is a bug, and was
   the most common one in this repo's history.
4. API requests use `fetchWithRetry` with exponential backoff for 429/5xx errors.
5. Dates are validated with strict calendar checking (no silent overflow) in UTC.
6. Tokens are never printed to stdout in full.

## License

MIT

TDQS

A4/5.0

Scored across 33 tools

Disambiguation4/5

Descriptions are unusually explicit with 'Do not use' cross-references, which strongly mitigates confusion. However, the set contains many near-identical history/sample/event tools (e.g., get-indexing-history vs get-insearch-history vs get-search-events-history; get-excluded-pages vs get-search-events-samples) that still require careful reading, so some misselection risk remains.

Naming Consistency5/5

All tool names use consistent kebab-case verb_noun patterns: get-*, add-*, list-*, delete-*, start-*, verify-host. The only minor variation is verify-host vs start-verification, but both are predictable and readable.

Tool Count2/5

With 33 tools, the server exceeds the 25+ heavy threshold. While Yandex Webmaster is a broad domain, the many read-only history/sample variants inflate the surface beyond what most agents need in a single call, making the set feel over-scoped.

Completeness4/5

The surface covers host lifecycle, verification, sitemaps, recrawl, important URLs, indexing/search/events, external links, and broken internal links. Some gaps remain (e.g., sitemap deletion, important-URL mutations), but core workflows are well supported.

Maintenance

ActivityActive
ResponsivenessResponsive