yandex-metrica-mcp
# yandex-metrica-mcp
MCP server for the Yandex Metrica Reporting API, Logs API and counter management, designed for use with [Claude Code](https://claude.ai/claude-code) and [Codex CLI](https://openai.com/codex). Eight of its 13 tools only read; five change your Metrica account — create and delete a counter, and create, cancel and clean a Logs API export. Each tool says which it is in its MCP annotations (`readOnlyHint`, `destructiveHint`).
Runs locally over **stdio**. No web server, no persistent storage, no background daemons.
---
## Why this project exists
Yandex Metrica has two distinct APIs that serve different purposes:
- **Reporting API** (`/stat/v1/data`) — aggregated, sampled statistics. Use this to build reports, query traffic sources, behavior metrics, goal conversions, etc.
- **Logs API** (`/management/v1/counter/{id}/logrequests`) — raw, non-aggregated event export. Use this when you need row-level session or hit data for custom analytics pipelines.
This MCP server exposes both APIs as named tools that an LLM can call directly.
---
## Features
- 13 MCP tools covering counter management, goals, reporting, and full Logs API lifecycle
- Authentication via environment variable only (no interactive OAuth)
- Minimal transformation — responses stay close to the Yandex API format
- Safe download behavior: logs parts default to 50KB preview + metadata
- Strict TypeScript, zod validation, clean error messages
- Multi-stage Docker image (node:24-alpine)
- Unit + mock integration tests, live test scaffolding
- GitHub Actions CI and Docker workflows
---
## Supported MCP Tools
### Counter Management
| Tool | Description |
| ------------------------------- | --------------------------------- |
| `yandex_metrica_list_counters` | List accessible counters (tags) |
| `yandex_metrica_get_counter` | Get counter details by ID |
| `yandex_metrica_list_goals` | List goals for a counter |
| `yandex_metrica_create_counter` | Create a new counter (add a site) |
| `yandex_metrica_delete_counter` | Delete a counter permanently |
### Reporting API (aggregated)
| Tool | Description |
| --------------------------- | ------------------------- |
| `yandex_metrica_get_report` | Run a Reporting API query |
> For period comparison, pass `date1_b`/`date2_b` to `yandex_metrica_get_report`. The tool automatically routes to `/stat/v1/data/comparison`; `date1`/`date2` become segment A, `date1_b`/`date2_b` become segment B. Each result row returns `metrics: { a, b }`.
### Logs API (raw export)
| Tool | Description |
| -------------------------------------- | ----------------------------------- |
| `yandex_metrica_evaluate_logs_request` | Check if export is possible |
| `yandex_metrica_create_logs_request` | Create an async export request |
| `yandex_metrica_list_logs_requests` | List all requests for a counter |
| `yandex_metrica_get_logs_request` | Poll status of a request |
| `yandex_metrica_download_logs_part` | Download a processed part (TSV) |
| `yandex_metrica_cancel_logs_request` | Cancel a pending request |
| `yandex_metrica_clean_logs_request` | Clean processed files to free quota |
---
## Logs API Workflow
```
evaluate → create → [poll get until status=processed] → download parts → clean
```
The Logs API is asynchronous. After creating a request, poll `yandex_metrica_get_logs_request` until `status = "processed"`, then download each part listed in `log_request.parts` (by its `part_number`).
---
## Download Behavior
Raw log parts can be hundreds of MB. By default `yandex_metrica_download_logs_part` returns:
```json
{
"mode": "preview",
"truncated": true,
"original_byte_size": 52428800,
"preview_byte_size": 49980,
"content": "ym:s:visitID\tym:s:date\t...\n..."
}
```
Set `mode: "full"` to get the entire content — only do this for small parts.
---
## Requirements
- Node.js 24+
- Yandex OAuth token with `metrika:read` scope
### How to get YANDEX_METRICA_TOKEN
1. Go to https://oauth.yandex.com/ → **Create app**
2. Fill in any name, select **For API access**
3. Under **Access** → enable `metrika:read`
4. Click **Create app**, copy the **ClientID**
5. Open in browser (replace `<ClientID>` with yours):
```
https://oauth.yandex.com/authorize?response_type=token&client_id=<ClientID>
```
6. Authorize → copy the `access_token` from the URL fragment (`#access_token=...`)
### How to get YANDEX_METRICA_COUNTER_ID
The counter ID is the numeric ID of your Yandex Metrica tag (счётчик):
- Open https://metrika.yandex.ru — the counter ID is shown next to each tag
- Or look at the URL: `metrika.yandex.ru/dashboard?id=**12345678**`
- Or use the tool itself: call `yandex_metrica_list_counters` — it returns all counters with their IDs
> `YANDEX_METRICA_COUNTER_ID` is only needed for live tests (`npm run test:live`), not for normal server operation.
---
## Installation
```bash
docker run -i --rm -e YANDEX_METRICA_TOKEN ghcr.io/stufently/yandex-metrica-mcp:latest
```
**One path that works today: Docker.** Nothing to clone, no Node.js. Every MCP client
below starts the server with this same command.
`-e YANDEX_METRICA_TOKEN` without a value forwards the variable from the client's
environment into the container, so the token lives in the client's `env` block and never
appears in the `docker` command line or `ps` output. The client pulls the image on
first start; `docker pull ghcr.io/stufently/yandex-metrica-mcp:latest` up front just
makes that first start faster.
Smoke test without a token or network — the server must answer `initialize` and list
13 tools. The image ships a placeholder `YANDEX_METRICA_TOKEN` so it starts for
introspection; tool calls with it return a "YANDEX_METRICA_TOKEN is not set" error
instead of reaching Yandex:
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| docker run -i --rm --network none ghcr.io/stufently/yandex-metrica-mcp:latest
```
Image tags on GHCR: `latest` and `main` (every push to `main`), `sha-<short>`, and
`X.Y.Z` / `X.Y` / `X` on version tags. Pin a version tag (`:0.2.3`) for reproducible setups.
### npm (not published yet)
The package is **scoped** — both unscoped names (`yandex-metrica-mcp` and the project's
former name `mcp-yandex-metrica`) belong to unrelated projects on npm — so it ships as
`@stufently/yandex-metrica-mcp` (command `yandex-metrica-mcp`). The first npm release
has not been cut, so `npx -y @stufently/yandex-metrica-mcp` does **not** work today. Use
Docker.
### From source
```bash
git clone https://github.com/stufently/yandex-metrica-mcp.git
cd yandex-metrica-mcp
npm install && npm run build
```
See [Running from a local build](#running-from-a-local-build-instead-of-docker) for the
client config.
---
## Configuration
Copy `.env.example` to `.env` and set your token:
```bash
cp .env.example .env
```
| Variable | Required | Default | Description |
| --------------------------- | -------- | -------------------------------- | ------------------------------------------- |
| `YANDEX_METRICA_TOKEN` | **Yes** | — | OAuth token with `metrika:read` |
| `YANDEX_METRICA_TIMEOUT_MS` | No | `30000` | Request timeout in ms |
| `LOG_LEVEL` | No | `info` | Log level: debug, info, warn, error, silent |
| `YANDEX_METRICA_USER_AGENT` | No | `yandex-metrica-mcp/0.2.3` | Custom User-Agent |
| `YANDEX_METRICA_BASE_URL` | No | `https://api-metrika.yandex.net` | API base URL (tests only) |
---
## Local Development
```bash
# Development with hot reload
YANDEX_METRICA_TOKEN=your_token npm run dev
# Build
npm run build
# Run production build
YANDEX_METRICA_TOKEN=your_token npm start
# Lint
npm run lint
# Type check
npm run typecheck
# Tests (unit + mock integration, no live API calls)
npm test
# Live tests (requires real token and counter ID)
LIVE_TESTS=1 YANDEX_METRICA_TOKEN=your_token YANDEX_METRICA_COUNTER_ID=12345678 npm run test:live
# Format
npm run format
```
---
## Docker
```bash
# Use pre-built image from GHCR (updated on every push to main)
docker run -i --rm \
-e YANDEX_METRICA_TOKEN=your_token \
ghcr.io/stufently/yandex-metrica-mcp:latest
# Or build locally
docker build -t yandex-metrica-mcp:local .
docker run -i --rm \
-e YANDEX_METRICA_TOKEN=your_token \
yandex-metrica-mcp:local
```
## Docker Compose
```bash
# Create .env with your token first
cp .env.example .env
# Run
docker-compose run --rm yandex-metrica-mcp
```
---
## 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 | Docker image built locally from this repo, stdio | Verified | Daily use on the maintainer's host (counters, reports, goals) |
| Node.js 24 | Ubuntu (`ubuntu-latest`) | `npm ci`, lint, typecheck, unit + mock integration tests, build | Verified in CI | `ci.yml` |
| Docker image | Ubuntu (`ubuntu-latest`) | Image build on every pull request and push to `main` | Built in CI, not started there | `docker.yml` |
| Claude Desktop (`.mcpb`) | macOS, Windows | Desktop Extension | Not verified | Bundle is built in CI, never installed in a real Desktop |
| Cursor, Windsurf, Codex CLI, Zed | any | Configs below | Not verified | Documented only |
| macOS, Windows | — | Any client | Not verified | No CI runner on these systems |
## Client Configuration
All configs below are copy-paste ready and use the Docker command from
[Installation](#installation). Replace `your_oauth_token_here` with your Yandex OAuth
token and nothing else.
### Claude Code
Project file: `.mcp.json` in the project root.
```bash
claude mcp add yandex-metrica \
-e YANDEX_METRICA_TOKEN=your_oauth_token_here \
-- docker run -i --rm -e YANDEX_METRICA_TOKEN ghcr.io/stufently/yandex-metrica-mcp:latest
```
Add `--scope user` to make it available in every project, or `--scope project` to write
`.mcp.json`. Check with `claude mcp list` / `claude mcp get yandex-metrica`.
The same server as JSON in `.mcp.json`:
```json
{
"mcpServers": {
"yandex-metrica": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"YANDEX_METRICA_TOKEN",
"ghcr.io/stufently/yandex-metrica-mcp:latest"
],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
### Claude Desktop
Config file `claude_desktop_config.json`: macOS
`~/Library/Application Support/Claude/`, Windows `%APPDATA%\Claude\`, Linux
`~/.config/Claude/`. Restart the app after saving.
```json
{
"mcpServers": {
"yandex-metrica": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"YANDEX_METRICA_TOKEN",
"ghcr.io/stufently/yandex-metrica-mcp:latest"
],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
Claude Desktop can also install this server in one click from a `.mcpb` file. Releases
up to v0.2.0 do not include one yet; the bundle is attached starting with the next
tagged release at https://github.com/stufently/yandex-metrica-mcp/releases/latest.
Until then, build it locally with `bash scripts/build-mcpb.sh` (needs Docker) and open
`dist-mcpb/yandex-metrica-mcp-<version>.mcpb`.
Releases include `checksums.txt` with the SHA256 checksum of each `.mcpb` bundle.
Download it alongside the bundle and verify before installing:
```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-metrica-mcp
```
### Cursor
[](https://cursor.com/en/install-mcp?name=yandex-metrica&config=eyJjb21tYW5kIjoiZG9ja2VyIiwiYXJncyI6WyJydW4iLCItaSIsIi0tcm0iLCItZSIsIllBTkRFWF9NRVRSSUNBX1RPS0VOIiwiZ2hjci5pby9zdHVmZW50bHkveWFuZGV4LW1ldHJpY2EtbWNwOmxhdGVzdCJdLCJlbnYiOnsiWUFOREVYX01FVFJJQ0FfVE9LRU4iOiJ5b3VyX29hdXRoX3Rva2VuX2hlcmUifX0%3D)
After installing, put your token in place of `your_oauth_token_here` in `~/.cursor/mcp.json`. Or add the block below by hand.
`~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one project.
```json
{
"mcpServers": {
"yandex-metrica": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"YANDEX_METRICA_TOKEN",
"ghcr.io/stufently/yandex-metrica-mcp:latest"
],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
### Windsurf
File `mcp_config.json`. On macOS and Linux:
`~/.config/devin/mcp_config.json` (or `$XDG_CONFIG_HOME/devin/mcp_config.json`). On
Windows: `%APPDATA%\devin\mcp_config.json`. Older builds used
`~/.codeium/windsurf/mcp_config.json`.
```json
{
"mcpServers": {
"yandex-metrica": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"YANDEX_METRICA_TOKEN",
"ghcr.io/stufently/yandex-metrica-mcp:latest"
],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
### Codex CLI
```bash
codex mcp add yandex-metrica \
--env YANDEX_METRICA_TOKEN=your_oauth_token_here \
-- docker run -i --rm -e YANDEX_METRICA_TOKEN ghcr.io/stufently/yandex-metrica-mcp:latest
```
Or by hand in `~/.codex/config.toml` (shared by the Codex CLI, IDE extension and desktop app):
```toml
[mcp_servers.yandex-metrica]
command = "docker"
args = ["run", "-i", "--rm", "-e", "YANDEX_METRICA_TOKEN", "ghcr.io/stufently/yandex-metrica-mcp:latest"]
env = { YANDEX_METRICA_TOKEN = "your_oauth_token_here" }
```
### Zed
Zed's `settings.json` (`zed: open settings file`), under `context_servers`:
```json
{
"context_servers": {
"yandex-metrica": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"YANDEX_METRICA_TOKEN",
"ghcr.io/stufently/yandex-metrica-mcp:latest"
],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
### Running from a local build instead of Docker
Works in any client above — swap `command`/`args` for:
```json
{
"mcpServers": {
"yandex-metrica": {
"command": "node",
"args": ["/absolute/path/to/yandex-metrica-mcp/dist/index.js"],
"env": {
"YANDEX_METRICA_TOKEN": "your_oauth_token_here"
}
}
}
}
```
In Claude Code the same thing via CLI:
```bash
claude mcp add yandex-metrica \
-e YANDEX_METRICA_TOKEN=your_oauth_token_here \
-- node /absolute/path/to/yandex-metrica-mcp/dist/index.js
```
---
## Example prompts
- Why did my site lose visits last week?
- Which traffic sources sent the most visits last month?
- What goals are configured on my main counter?
- Compare this month's visits with the same month last year.
- Export raw visits for the first week of January and show a preview.
## Common prompts
Once the server is registered, ask in plain language — the client picks the tool.
**List what you have access to** → `yandex_metrica_list_counters`
```
List all my Yandex Metrica counters with their IDs and site names.
```
**Traffic report over a period** → `yandex_metrica_get_report`
```
Show visits, users and bounce rate for counter 12345678 for the last 7 days,
broken down by date.
```
**Top traffic sources** → `yandex_metrica_get_report`
```
What were the top 10 traffic sources for counter 12345678 last month,
by visits and conversion rate?
```
**Compare two periods** → `yandex_metrica_get_report` with `date1_b`/`date2_b`
```
Compare visits and average session duration for counter 12345678
between this month and the same month last year.
```
**Goal conversions** → `yandex_metrica_list_goals` + `yandex_metrica_get_report`
```
List the goals configured on counter 12345678, then show conversions
for each of them over the last 30 days.
```
**Raw event export** → the Logs API chain (`evaluate` → `create` → `get` → `download_part` → `clean`)
```
Export raw visits for counter 12345678 from 2024-01-01 to 2024-01-07 with fields
visitID, date, clientID, startURL, trafficSource. Poll until it is ready,
then show me a preview of the first part.
```
---
## Architecture
```
src/
index.ts Entry point, loads config, connects stdio transport
config/env.ts Zod-based environment config validation
server/
createServer.ts Creates MCP Server instance
registerTools.ts Registers all tools with the server
tools/ One file per MCP tool
client/
http.ts Fetch-based HTTP client with auth, timeout, error handling
management.ts Counters and goals endpoints
reporting.ts Reporting API (/stat/v1/data)
logs.ts Logs API lifecycle endpoints
schemas/ Zod schemas for tool input validation
errors/ YandexApiError + error mapping
types/api.ts TypeScript types for Yandex API responses
utils/
arrays.ts Array → comma-separated conversion
text.ts Text preview/truncation for large downloads
```
---
## Limitations
- **Not read-only** — 8 tools read; 5 write: counter create/delete and the Logs API export lifecycle (create, cancel, clean)
- **No comparison report tool** — use `yandex_metrica_get_report` with `date1_b`/`date2_b` params
- **No drilldown/pivot/bytime tools** in v1 — planned for v0.2
- **No caching** — every tool call hits the Yandex API
- **Large log parts** — use `mode: "full"` carefully; defaults to 50KB preview
- Retry with exponential backoff is built-in (3 retries, statuses 429/500/502/503/504)
---
## Testing
```bash
# Unit + mock integration (CI-safe, no real API calls)
npm test
# Live tests (hit real Yandex API)
LIVE_TESTS=1 \
YANDEX_METRICA_TOKEN=your_token \
YANDEX_METRICA_COUNTER_ID=12345678 \
npm run test:live
```
---
## CI / Docker Image
GitHub Actions workflows are in `.github/workflows/`:
- `ci.yml` — lint, typecheck, test, build on every push/PR
- `docker.yml` — build Docker image; push to GHCR on pushes to `main` and version tags;
on a `vX.Y.Z` tag, also publish `server.json` to the
[official MCP Registry](https://registry.modelcontextprotocol.io) as
`io.github.stufently/yandex-metrica-mcp` (GitHub OIDC, no stored secret)
- `mcpb.yml` — on a `vX.Y.Z` tag, build the `.mcpb` Desktop extension and attach it
to that GitHub Release. It does not publish to GHCR, npm, PyPI, or the MCP registry.
Image tags produced by `docker.yml`:
| Tag | When |
| ------------------- | ------------------------------------------ |
| `latest` | every push to `main` |
| `main` | every push to `main` |
| `sha-<short>` | every push to `main` and every version tag |
| `X.Y.Z`, `X.Y`, `X` | pushing a `vX.Y.Z` git tag |
`REGISTRY` and `IMAGE_NAME` default to `ghcr.io` and `${{ github.repository }}`.
---
## Security
- Token is passed via environment variable only — never hardcoded
- Token never appears in logs
- Counter create/delete requires `metrika:write` scope; all other tools use read-only access
- Docker image runs as non-root (`node` user)
---
## License
MIT — see [LICENSE](./LICENSE)
TDQS
Scored across 13 tools
Each tool has a clearly distinct purpose, with explicit cross-references in descriptions (e.g., 'Do not use this... call X instead') that prevent misselection. The split between aggregated reports, logs export lifecycle, and counter management is unambiguous.
All tools follow a consistent yandex_metrica_ prefix and verb_noun pattern (list_counters, get_counter, create_logs_request, download_logs_part, etc.). No mixing of conventions or styles.
13 tools is well within the ideal 3-15 range and each tool maps to a distinct API operation or step in a workflow. The logs export lifecycle is fully broken out without redundancy.
Core workflows (counter listing/retrieval/creation/deletion, reporting, full logs export lifecycle) are covered. Minor gaps exist, such as no counter update/settings modification and only goal listing without goal management, but these are not critical for typical agent tasks.