plausible-mcp
plausible-mcp
MCP-сервер для Plausible Analytics — запрашивайте трафик, конверсии и сравнивайте временные периоды из любого ИИ-инструмента, поддерживающего Model Context Protocol.
Создан для команд, которые хотят задавать вопросы вроде:
«Повлиял ли наш деплой во вторник на трафик на /pricing?»
«Какой коэффициент конверсии в регистрацию на /blog в этом месяце?»
«Как показатель отказов на этой неделе соотносится с прошлой?»
Инструменты
Инструмент | Описание |
| Метрики трафика и конверсий во времени (ежедневно/еженедельно/ежемесячно) |
| Разбивка по странице, источнику, стране, устройству, браузеру, ОС, UTM-параметрам |
| Коэффициенты конверсии по целям, опционально по страницам |
| Сравнение двух диапазонов дат бок о бок с абсолютными и процентными дельтами |
Все инструменты запросов только для чтения и аннотированы readOnlyHint: true.
Хостируемые развёртывания дополнительно предоставляют send_feedback, который отправляет отзывы о самом сервере (запутанные ошибки, отсутствующие возможности) в почтовый ящик Sentry User Feedback мейнтейнеров. Он регистрируется только тогда, когда сервер работает с Sentry (enableFeedbackTool).
Related MCP server: Yandex Metrica MCP
Быстрый старт
Удалённо (хостинг)
Хостируемый экземпляр доступен по адресу https://plausible-mcp.sentry.dev.
С собственным ключом API Plausible (любой пользователь):
claude mcp add --transport http plausible https://plausible-mcp.sentry.dev/mcp --header "Authorization: Bearer YOUR_PLAUSIBLE_API_KEY"Держите URL перед
--header.--headerвариадичен, поэтому если он идёт последним, он поглощает URL, и CLI завершается с ошибкойerror: missing required argument 'commandOrUrl'.
Или добавьте вручную в конфигурацию вашего MCP-клиента (Claude Desktop, Cursor и т. д.):
{
"mcpServers": {
"plausible": {
"url": "https://plausible-mcp.sentry.dev/mcp",
"headers": {
"Authorization": "Bearer YOUR_PLAUSIBLE_API_KEY"
}
}
}
}Сотрудники Sentry (через OAuth 2.1 + Cloudflare Access):
Эндпоинт /internal — это сервер OAuth 2.1 — ключ API не нужен. Добавьте его как удалённый/пользовательский коннектор в любой MCP-клиент с поддержкой OAuth (Cowork, коннекторы Claude.ai, Claude Desktop):
https://plausible-mcp.sentry.dev/internalКлиент автоматически обнаруживает эндпоинты OAuth, проводит вас через Sentry SSO (Cloudflare Access), и доступ предоставляется только идентичностям @sentry.io. Запросы выполняются с использованием общего серверного ключа API Plausible — вам никогда не нужно работать с ключом.
Хостируемый
/internalнаplausible-mcp.sentry.devдоступен только для Sentry и не может использоваться вне организации. Чтобы запустить/internalдля другой организации, разверните самостоятельно и установитеALLOWED_EMAIL_DOMAINна свой домен. (Публичный эндпоинт/mcpс собственным ключом не имеет таких ограничений.)
Локально (STDIO)
Если вы предпочитаете запускать локально, используйте Node.js 20 или новее:
git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
pnpm buildДобавьте в Claude Code:
claude mcp add plausible -e PLAUSIBLE_API_KEY=your-key -- node /path/to/plausible-mcp/dist/index.jsИли в Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"plausible": {
"command": "node",
"args": ["/path/to/plausible-mcp/dist/index.js"],
"env": {
"PLAUSIBLE_API_KEY": "your-key"
}
}
}
}Самостоятельное развёртывание (Cloudflare Workers)
Разверните собственный экземпляр:
git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
npx wrangler deployWorker предоставляет два эндпоинта:
/mcp— принесите свой ключ. Каждый пользователь передаёт свой собственный ключ API Plausible через заголовокAuthorization: Bearer. На сервере не нужны общие секреты. Работает с любым MCP-клиентом, поддерживающим заголовки (Claude Code, Cursor, MCP Inspector)./internal— защищённый Access MCP-эндпоинт для управляемых коннекторов (Cowork, Claude.ai). Приложение Cloudflare Access с Managed OAuth стоит перед всем хостом Worker (см. ограничение ниже): Access выполняет рукопожатие OAuth 2.1 с клиентом и пересылает каждый запрос Worker с заголовкомCf-Access-Jwt-Assertion. Worker проверяет этот заголовок и запрашивает общий серверный ключ API Plausible. Access ограничен доменом(ами) электронной почты изALLOWED_EMAIL_DOMAIN(по умолчаниюsentry.io) — не привязан к Sentry при самостоятельном развёртывании; установите свой домен.
Поскольку приложение Managed OAuth должно покрывать голый хост без пути (Cloudflare отклоняет путь, когда OAuth включён — domain can not have a path if oauth is configured), оно также закрывает /mcp. Чтобы оставить публичный эндпоинт /mcp с собственным ключом, вы добавляете второе, более специфичное приложение Access, ограниченное путём /mcp с политикой Bypass. Cloudflare сопоставляет наиболее специфичный хост+путь первым, поэтому запросы /mcp полностью обходят Access, а всё остальное проходит через OAuth. Оба приложения живут на одном хосте; отдельный поддомен не требуется.
Бета / требование к клиенту. Cloudflare Access Managed OAuth находится в бете и требует MCP-клиент, поддерживающий RFC 8707 (индикаторы ресурсов). Прежде чем полагаться на этот путь, убедитесь, что ваш коннектор его поддерживает.
Настройка эндпоинта /internal (Cloudflare Access Managed OAuth)
Worker не запускает OAuth-сервер — Cloudflare Access является сервером авторизации. Нет OAUTH_KV, нет ключа cookie и нет идентификатора/секрета OAuth-клиента. Вы создаёте два приложения Access на одном хосте.
Создайте приложение Managed OAuth на голом хосте (Zero Trust → Access → Applications): самостоятельно размещённое приложение или приложение MCP-сервера, чей домен —
plausible-mcp.sentry.devбез пути.⚠️ Не ограничивайте его
/internal. Как только Managed OAuth включён, Cloudflare отклоняет любой путь сaccess.api.error.invalid_request: domain can not have a path if oauth is configured. Приложение должно охватывать весь хост; Worker сам обеспечивает маршрут/internal.Добавьте политику Access (действие
Allow), ограничивающую ваш домен электронной почты (например,@acme.com) и поставщика удостоверений.Включите Managed OAuth (Advanced settings → Managed OAuth) и установите Allowed redirect URIs на фактический callback вашего коннектора — для Claude/Cowork это
https://claude.ai/api/mcp/auth_callback. Публичные HTTPS-callback обязательно должны быть указаны, иначе Dynamic Client Registration завершится с ошибкойinvalid_client_metadata: redirect_uri is not allowed by the account configuration; loopback (http://localhost:*) callback разрешены по умолчанию.Скопируйте AUD-тег приложения → он станет
CF_ACCESS_AUD.
Выделите
/mcpобратно с помощью второго приложения Bypass, ограниченного путём. Поскольку шаг 1 покрывает весь хост,/mcp(принесите свой ключ) теперь тоже закрыт. Создайте ещё одно самостоятельно размещённое приложение, доменplausible-mcp.sentry.devпутьmcp, с Managed OAuth OFF и политикой, чьё действие —Bypassс селекторомEveryone.Bypass≠Allow: политикаAllowвсё равно принуждает к интерактивному входу (клиент получает HTML302на страницу входа и завершается с ошибкойUnexpected content type: text/html). ТолькоBypassпропускает запрос без аутентификации, поэтому применяется собственная проверка Bearer-ключа Worker.
Установите секреты Worker:
npx wrangler secret put PLAUSIBLE_API_KEY # shared key for /internal queries npx wrangler secret put SENTRY_DSN # optional — the Worker's own telemetryCF_ACCESS_TEAM_DOMAINиCF_ACCESS_AUD— не секреты — публичный URL JWKS и идентификатор приложения — поэтому они помещаются в[vars]на шаге 4.Установите
[vars]вwrangler.toml:CF_ACCESS_TEAM_DOMAIN—https://<team>.cloudflareaccess.com, без завершающего слэша. Проверяет JWKS и издателяCf-Access-Jwt-Assertion.CF_ACCESS_AUD— AUD-тег, скопированный на шаге 1.ALLOWED_EMAIL_DOMAIN— домен(ы) электронной почты, которым разрешён вход, через запятую,@необязателен (по умолчаниюsentry.io). Применяется в коде в дополнение к политике Access на шаге 1, поэтому установите свой домен — иначе каждый вход будет отклонён.MCP_ALLOWED_HOSTNAMES— разделённые запятыми имена хостов, принимаемые MCP-эндпоинтами. Заменитеplausible-mcp.sentry.devна хост вашего Worker; сохраните записи localhost, если используетеwrangler dev.MCP_ALLOWED_ORIGIN_HOSTNAMES— разделённые запятыми имена хостов браузерного Origin, которым разрешено вызывать/internal. Небраузерные клиенты не отправляют заголовокOrigin.
Разверните (
npx wrangler deploy), затем укажите MCP-клиенту с поддержкой RFC 8707 наhttps://<your-worker-host>/internal.
Устранение неполадок. Всё это — конфигурация Cloudflare Access, а не Worker — запрос достигает Worker (и его Sentry-спанов) только после того, как Access перешлёт его:
Симптом (в коннекторе) | Причина | Исправление |
| Callback коннектора отсутствует в Allowed redirect URIs | Добавьте точный callback (шаг 1); прочитайте отклонённый |
| Приложение Managed OAuth ограничено путём | Переопределите область приложения 1 на голый хост (шаг 1) |
| Политика приложения | Установите действие политики приложения 2 на |
| Нет приложения bypass для | Создайте приложение 2 (шаг 2) |
Конфигурация
Переменная окружения | Обязательная | По умолчанию | Описание |
| Да (STDIO; Worker | — | Ваш API-ключ Plausible (получить здесь). На Worker это общий ключ для |
| Нет |
| URL вашего экземпляра Plausible (для self-hosted) |
| Нет | — | Домен сайта по умолчанию, чтобы не передавать |
| Да (Worker | — |
|
| Да (Worker | — | Тег Application Audience (AUD) приложения Access — проверяется по |
| Нет (Worker) | — | Sentry DSN для собственной телеметрии Worker ( |
| Нет (Worker |
| Разрешённые домены email (через запятую) для входа в |
| Да (Worker) | — | Разрешённый список hostname (через запятую) для проверки заголовков MCP |
| Нет (Worker | — | Разрешённые hostname браузерного Origin (через запятую) для вызова |
На Worker конечной точке /mcp не нужен серверный ключ — каждый пользователь передаёт свой через Authorization: Bearer. Конечная точка /internal защищена Cloudflare Access Managed OAuth и использует общий серверный секрет PLAUSIBLE_API_KEY (см. self-hosting).
Plausible API
Этот сервер оборачивает Plausible Stats API v2 (POST /api/v2/query). Он работает как с Plausible Cloud, так и с self-hosted экземплярами.
Поддерживаемые метрики
visitors, visits, pageviews, views_per_visit, bounce_rate, visit_duration, events, scroll_depth, percentage, conversion_rate, group_conversion_rate, average_revenue, total_revenue, time_on_page
Поддерживаемые измерения
event:page, event:goal, event:hostname, visit:entry_page, visit:exit_page, visit:source, visit:referrer, visit:channel, visit:utm_medium, visit:utm_source, visit:utm_campaign, visit:utm_content, visit:utm_term, visit:device, visit:browser, visit:browser_version, visit:os, visit:os_version, visit:country, visit:region, visit:city, visit:country_name, visit:region_name, visit:city_name
Географические измерения *_name возвращают человекочитаемые названия (например, «Канада»); обычные visit:country/region/city возвращают коды ISO/Geoname.
Фильтрация
Каждый инструмент запроса принимает property_filters, который — несмотря на название — фильтрует как по встроенным измерениям, так и по пользовательским свойствам событий. Каждая запись — { "property", "operator", "values" }:
property— встроенное измерение (например,visit:channel,visit:source,event:page) или пользовательское свойство по его имени без префикса ("plan"обращается кevent:props:plan).operator—is,is_not,contains,contains_not(по умолчаниюis).event:goalподдерживает толькоisиcontains.Несколько записей объединяются по AND, как и параметры-сокращения
page/goal. Обращение кevent:page/event:goalодновременно через сокращение иproperty_filtersв одном вызове отклоняется — используйте что-то одно.
Например, топ страниц по органическому поисковому трафику: get_breakdown с dimension: "event:page" и property_filters: [{ "property": "visit:channel", "values": ["Organic Search"] }].
Пользовательские свойства
Сайты отправляют свои пользовательские свойства событий, адресуемые как event:props:<name>. Они специфичны для сайта, поэтому фиксированного списка нет.
Разбивка по пользовательскому свойству: передайте
get_breakdowndimensionвидаevent:props:<name>(например,event:props:plan).Фильтрация по пользовательскому свойству через
property_filtersс именем без префикса, например[{ "property": "plan", "operator": "is", "values": ["pro"] }].
Разработка
pnpm install
pnpm build # TypeScript compilation
pnpm test # Run unit + integration tests
pnpm test:watch # Watch modeТестирование с MCP Inspector
pnpm build
PLAUSIBLE_API_KEY=your-key npx @modelcontextprotocol/inspector node dist/index.jsLLM-оценки
Проверяет, что модель выбирает правильный инструмент для вопросов по аналитике на естественном языке. Запускается через OpenRouter, поэтому подходит любая модель с поддержкой вызова инструментов — по умолчанию anthropic/claude-sonnet-5:
OPENROUTER_API_KEY=sk-or-... pnpm eval
OPENROUTER_MODEL=openai/gpt-5 OPENROUTER_API_KEY=sk-or-... pnpm eval # try another modelАрхитектура
src/
├── index.ts # STDIO entry point (local use)
├── worker.ts # Cloudflare Worker entry point (remote)
├── env.ts # Worker environment bindings
├── cf-access.ts # Verifies the Cloudflare Access assertion on /internal
├── server.ts # Creates McpServer, registers all tools
├── plausible.ts # PlausibleClient — standalone API client
├── schemas.ts # Shared Zod schemas and filter helpers
├── errors.ts # UserFacingError and tool-error reporting
├── telemetry.ts # Pure classifiers — route, MCP request kind, client family
├── mcp-telemetry.ts # Records MCP client info onto the active span
├── redaction.ts # Strips PII from Sentry events on the BYOK path
└── tools/
├── get-timeseries.ts
├── get-breakdown.ts
├── get-conversions.ts
├── compare-periods.ts
└── send-feedback.tsPlausibleClient не имеет зависимостей от MCP и может использоваться автономно.
Наблюдаемость и сбор данных
Worker отправляет отчёты в Sentry с режимом конфиденциальности, зависящим от конечной точки:
/mcp(bring-your-own-key) — полностью анонимный. Входные и выходные данные инструментов не записываются (эти данные принадлежат вызывающему и его собственному ключу), личность не привязывается, а определённый при приёме IP клиента удаляется (src/redaction.ts). Остаётся только операционная телеметрия: имена инструментов, тайминги спанов и сбои./internal(за SSO) — атрибутированный. Запросы несут аутентифицированный email@sentry.io(Sentry.setUser), а входные/выходные данные инструментов записываются (recordToolIO) для атрибуции и отслеживания злоупотреблений на общем серверном ключе.
Заголовки Authorization / Cookie / Cf-Access-Jwt-Assertion удаляются из спанов на обоих путях. В качестве дополнительной страховки включите Prevent Storing of IP Addresses в настройках Security & Privacy проекта Sentry.
Лицензия
MIT — см. LICENSE.
Available Tools
4 toolscompare_periodsCompare PeriodsARead-onlyIdempotent
Compare metrics between two date ranges side by side. Ideal for before/after deploy analysis. Returns aggregate values for each period plus the delta (absolute and %).
| Name | Required | Description | Default |
|---|---|---|---|
| goal | No | Filter by goal name (e.g. Signup, Purchase) | |
| page | No | Filter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*) | |
| metrics | No | Metrics to return. Defaults vary by tool. | |
| site_id | Yes | Plausible site domain (e.g. example.com). Required. | |
| period_a | Yes | First date range, e.g. "2024-01-01,2024-01-07" or "7d" | |
| period_b | Yes | Second date range, e.g. "2024-01-08,2024-01-14" or "7d" | |
| property_filters | No | Filter results by built-in dimensions or custom event properties, e.g. [{ "property": "visit:channel", "operator": "is", "values": ["Organic Search"] }] or [{ "property": "plan", "values": ["pro"] }]. Entries are combined with AND. |
Output Schema
| Name | Required | Description |
|---|---|---|
| deltas | Yes | Per-metric change from period_a to period_b (absolute and percent) |
| period_a | Yes | |
| period_b | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so the safety profile is covered. The description adds that the result contains aggregate values for each period plus absolute and percentage deltas, which is genuinely useful behavioral context beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences with zero filler, front-loaded with the core comparison purpose and ending with the return shape. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a full schema, an output schema, and annotations present, the description only needs to convey purpose, usage context and return nature, all of which it does. Nothing essential for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the description adds no parameter-level detail (no date-format syntax, no metric defaults, no filter semantics). Baseline 3 is appropriate when the schema fully documents the seven parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Compare metrics between two date ranges side by side.' The comparison semantics clearly distinguish it from siblings like get_timeseries and get_breakdown, which retrieve rather than diff two periods.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Ideal for before/after deploy analysis' gives a concrete usage context that tells the agent when this tool is the right pick. It stops short of naming exclusions or the sibling tools to prefer for single-period retrieval, so it is clear context without routing rules.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_breakdownGet BreakdownARead-onlyIdempotent
Break down metrics by a dimension: page, traffic source, country, device, etc. Use to find top pages, sources, or segment traffic.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | Filter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*) | |
| limit | No | Max results to return | |
| metrics | No | Metrics to return. Defaults vary by tool. | |
| site_id | Yes | Plausible site domain (e.g. example.com). Required. | |
| dimension | Yes | Dimension to group results by: a standard dimension (e.g. event:page, visit:source), or a custom event property as "event:props:<name>" (e.g. event:props:plan). | |
| date_range | Yes | Date range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD" | |
| property_filters | No | Filter results by built-in dimensions or custom event properties, e.g. [{ "property": "visit:channel", "operator": "is", "values": ["Organic Search"] }] or [{ "property": "plan", "values": ["pro"] }]. Entries are combined with AND. |
Output Schema
| Name | Required | Description |
|---|---|---|
| metrics | Yes | Metric keys, in the order they appear in each row's `metrics` array |
| results | Yes | One row per dimension-value combination returned by Plausible |
| dimensions | Yes | Dimension keys, in the order they appear in each row's `dimensions` array |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description adds no behavioral context beyond that — nothing about result limits, default metric selection, ordering, or how entries are combined.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core action and followed by the use case. No filler, no repetition of the title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists and all 7 parameters are fully described in the schema, so the description need not cover return values. It covers purpose and a use case adequately; only route-to-sibling guidance is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so every parameter including dimension enums and format examples is already documented in the schema. The description's dimension examples (page, traffic source, country, device) loosely map to schema values but add no syntax or format detail beyond it. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (break down metrics by a dimension) and gives concrete example dimensions (page, traffic source, country, device) plus example goals (top pages, sources). It never names or contrasts its siblings (get_timeseries, get_conversions, compare_periods), so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use to find top pages, sources, or segment traffic' implies the use case but gives no when-not conditions and points to no alternative tool. An agent must infer that breakdown-by-dimension is the right choice over timeseries or comparison, which the description does not address.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_conversionsGet ConversionsARead-onlyIdempotent
Get goal conversion rates and counts. Can break down by page to see which pages drive conversions.
| Name | Required | Description | Default |
|---|---|---|---|
| goal | No | Filter by goal name (e.g. Signup, Purchase) | |
| page | No | Filter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*) | |
| site_id | Yes | Plausible site domain (e.g. example.com). Required. | |
| date_range | Yes | Date range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD" | |
| property_filters | No | Filter results by built-in dimensions or custom event properties, e.g. [{ "property": "visit:channel", "operator": "is", "values": ["Organic Search"] }] or [{ "property": "plan", "values": ["pro"] }]. Entries are combined with AND. | |
| breakdown_by_page | No | If true, shows conversion rate per page |
Output Schema
| Name | Required | Description |
|---|---|---|
| metrics | Yes | Metric keys, in the order they appear in each row's `metrics` array |
| results | Yes | One row per dimension-value combination returned by Plausible |
| dimensions | Yes | Dimension keys, in the order they appear in each row's `dimensions` array |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so the safety profile is covered and the bar is lower. The description adds only the page-breakdown capability and says nothing about result shape, pagination, or rate limits beyond what structured fields provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, capability front-loaded with the optional breakdown as a secondary clause. No waste, though it is quite terse for a six-parameter analytics tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With annotations, a full output schema, and 100% schema description coverage, the description only needs to convey the tool's scope, which it does. Minor gaps around filtering/segmentation guidance are acceptable given the rich structured fields.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all six parameters (including goal, page, property_filters, breakdown_by_page) are already documented in the schema. The description adds no syntax or format detail beyond that, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: goal conversion rates and counts, plus a page-breakdown capability. It distinguishes itself reasonably from siblings like get_timeseries and get_breakdown by scoping to conversions, though it never names an alternative directly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: use this when you need conversion metrics, optionally per page. There is no explicit when-to-use versus when-to-prefer get_breakdown or compare_periods, and no exclusions or prerequisites are stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_timeseriesGet TimeseriesARead-onlyIdempotent
Get traffic and conversion metrics over time for a site or specific page. Use to spot trends and changes around deploys.
| Name | Required | Description | Default |
|---|---|---|---|
| goal | No | Filter by goal name (e.g. Signup, Purchase) | |
| page | No | Filter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*) | |
| metrics | No | Metrics to return. Defaults vary by tool. | |
| site_id | Yes | Plausible site domain (e.g. example.com). Required. | |
| date_range | Yes | Date range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD" | |
| granularity | No | Time bucket size | day |
| property_filters | No | Filter results by built-in dimensions or custom event properties, e.g. [{ "property": "visit:channel", "operator": "is", "values": ["Organic Search"] }] or [{ "property": "plan", "values": ["pro"] }]. Entries are combined with AND. |
Output Schema
| Name | Required | Description |
|---|---|---|
| metrics | Yes | Metric keys, in the order they appear in each row's `metrics` array |
| results | Yes | One row per dimension-value combination returned by Plausible |
| dimensions | Yes | Dimension keys, in the order they appear in each row's `dimensions` array |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and openWorldHint, so safety and idempotency are covered. The description adds no behavioral context beyond that (no mention of pagination, granularity defaults, or result volume), and the output schema handles the return shape, so a baseline 3 is appropriate.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the primary purpose and followed by the usage cue. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only timeseries tool with a full output schema and 100% parameter coverage, the description covers purpose and one use case adequately. It is slightly thin on when to prefer it over compare_periods or get_breakdown, which keeps it from a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% with rich inline docs for goal, page, metrics, date_range, granularity and property_filters. The description adds no parameter meaning beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (get traffic/conversion metrics over time) plus the scope (site or specific page), which clearly separates it from get_breakdown and compare_periods. It does not explicitly name the sibling tools, so the differentiation is implied rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use to spot trends and changes around deploys' gives one concrete use case, implying time-series analysis rather than aggregation. But there is no when-not guidance and no routing to siblings like compare_periods for period-over-period analysis, so usage is only partially covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.9.0- Changed
compare_periods8 fields changed- removed
Output schema / properties / deltas / additionalProperties / properties / absolute / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / deltas / additionalProperties / properties / absolute / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / deltas / additionalProperties / properties / percent / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / deltas / additionalProperties / properties / percent / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / period_a / properties / metrics / additionalProperties / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / period_a / properties / metrics / additionalProperties / typeAdded value: +[ + "number", + "null" +] - removed
Output schema / properties / period_b / properties / metrics / additionalProperties / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "null" - } -] - added
Output schema / properties / period_b / properties / metrics / additionalProperties / typeAdded value: +[ + "number", + "null" +]
- Changed
get_breakdown4 fields changed- removed
Output schema / properties / results / items / properties / dimensions / items / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "number" - } -] - added
Output schema / properties / results / items / properties / dimensions / items / typeAdded value: +[ + "string", + "number" +] - removed
Output schema / properties / results / items / properties / metrics / items / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / results / items / properties / metrics / items / typeAdded value: +[ + "number", + "string", + "null" +]
- Changed
get_conversions4 fields changed- removed
Output schema / properties / results / items / properties / dimensions / items / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "number" - } -] - added
Output schema / properties / results / items / properties / dimensions / items / typeAdded value: +[ + "string", + "number" +] - removed
Output schema / properties / results / items / properties / metrics / items / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / results / items / properties / metrics / items / typeAdded value: +[ + "number", + "string", + "null" +]
- Changed
get_timeseries4 fields changed- removed
Output schema / properties / results / items / properties / dimensions / items / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "number" - } -] - added
Output schema / properties / results / items / properties / dimensions / items / typeAdded value: +[ + "string", + "number" +] - removed
Output schema / properties / results / items / properties / metrics / items / anyOfRemoved value: -[ - { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "null" - } -] - added
Output schema / properties / results / items / properties / metrics / items / typeAdded value: +[ + "number", + "string", + "null" +]
4 tool updates
v0.7.2- Changed
compare_periods1 field changed- added
Input schema / properties / property_filters / items / properties / values / items / maxLengthAdded value: +1024
- Changed
get_breakdown1 field changed- added
Input schema / properties / property_filters / items / properties / values / items / maxLengthAdded value: +1024
- Changed
get_conversions1 field changed- added
Input schema / properties / property_filters / items / properties / values / items / maxLengthAdded value: +1024
- Changed
get_timeseries1 field changed- added
Input schema / properties / property_filters / items / properties / values / items / maxLengthAdded value: +1024
4 tool updates
v0.7.1- Changed
compare_periods5 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / properties / property_filtersAdded value: +{ + "description": "Filter results by built-in dimensions or custom event properties, e.g. [{ \"property\": \"visit:channel\", \"operator\": \"is\", \"values\": [\"Organic Search\"] }] or [{ \"property\": \"plan\", \"values\": [\"pro\"] }]. Entries are combined with AND.", + "items": { + "properties": { + "operator": { + "default": "is", + "description": "Match operator: is, is_not, contains, contains_not (default: is)", + "enum": [ + "is", + "is_not", + "contains", + "contains_not" + ], + "type": "string" + }, + "property": { + "description": "What to filter on: a built-in dimension (e.g. \"visit:channel\", \"visit:source\", \"event:page\") or a custom event property as its bare name (e.g. \"plan\" targets event:props:plan)", + "maxLength": 312, + "minLength": 1, + "type": "string" + }, + "values": { + "description": "One or more values to match the property against", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "property", + "values" + ], + "type": "object" + }, + "type": "array" +} - changed
Input schema / properties / site_id / descriptionPrevious value: -"Plausible site domain (e.g. example.com). Uses PLAUSIBLE_DEFAULT_SITE_ID if omitted."New value: +"Plausible site domain (e.g. example.com). Required." - changed
Input schema / requiredPrevious value: -[ - "period_a", - "period_b" -]New value: +[ + "site_id", + "period_a", + "period_b" +] - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
get_breakdown9 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / properties / dimension / anyOfAdded value: +[ + { + "enum": [ + "event:page", + "event:goal", + "event:hostname", + "visit:entry_page", + "visit:exit_page", + "visit:source", + "visit:referrer", + "visit:channel", + "visit:utm_medium", + "visit:utm_source", + "visit:utm_campaign", + "visit:utm_content", + "visit:utm_term", + "visit:device", + "visit:browser", + "visit:browser_version", + "visit:os", + "visit:os_version", + "visit:country", + "visit:region", + "visit:city", + "visit:country_name", + "visit:region_name", + "visit:city_name" + ], + "type": "string" + }, + { + "maxLength": 312, + "minLength": 13, + "pattern": "^event:props:", + "type": "string" + } +] - changed
Input schema / properties / dimension / descriptionPrevious value: -"Dimension to group results by"New value: +"Dimension to group results by: a standard dimension (e.g. event:page, visit:source), or a custom event property as \"event:props:<name>\" (e.g. event:props:plan)." - removed
Input schema / properties / dimension / enumRemoved value: -[ - "event:page", - "event:goal", - "event:hostname", - "visit:entry_page", - "visit:exit_page", - "visit:source", - "visit:referrer", - "visit:channel", - "visit:utm_medium", - "visit:utm_source", - "visit:utm_campaign", - "visit:utm_content", - "visit:utm_term", - "visit:device", - "visit:browser", - "visit:browser_version", - "visit:os", - "visit:os_version", - "visit:country", - "visit:region", - "visit:city", - "visit:country_name", - "visit:region_name", - "visit:city_name" -] - removed
Input schema / properties / dimension / typeRemoved value: -"string" - added
Input schema / properties / property_filtersAdded value: +{ + "description": "Filter results by built-in dimensions or custom event properties, e.g. [{ \"property\": \"visit:channel\", \"operator\": \"is\", \"values\": [\"Organic Search\"] }] or [{ \"property\": \"plan\", \"values\": [\"pro\"] }]. Entries are combined with AND.", + "items": { + "properties": { + "operator": { + "default": "is", + "description": "Match operator: is, is_not, contains, contains_not (default: is)", + "enum": [ + "is", + "is_not", + "contains", + "contains_not" + ], + "type": "string" + }, + "property": { + "description": "What to filter on: a built-in dimension (e.g. \"visit:channel\", \"visit:source\", \"event:page\") or a custom event property as its bare name (e.g. \"plan\" targets event:props:plan)", + "maxLength": 312, + "minLength": 1, + "type": "string" + }, + "values": { + "description": "One or more values to match the property against", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "property", + "values" + ], + "type": "object" + }, + "type": "array" +} - changed
Input schema / properties / site_id / descriptionPrevious value: -"Plausible site domain (e.g. example.com). Uses PLAUSIBLE_DEFAULT_SITE_ID if omitted."New value: +"Plausible site domain (e.g. example.com). Required." - changed
Input schema / requiredPrevious value: -[ - "date_range", - "dimension" -]New value: +[ + "site_id", + "date_range", + "dimension" +] - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
get_conversions5 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / properties / property_filtersAdded value: +{ + "description": "Filter results by built-in dimensions or custom event properties, e.g. [{ \"property\": \"visit:channel\", \"operator\": \"is\", \"values\": [\"Organic Search\"] }] or [{ \"property\": \"plan\", \"values\": [\"pro\"] }]. Entries are combined with AND.", + "items": { + "properties": { + "operator": { + "default": "is", + "description": "Match operator: is, is_not, contains, contains_not (default: is)", + "enum": [ + "is", + "is_not", + "contains", + "contains_not" + ], + "type": "string" + }, + "property": { + "description": "What to filter on: a built-in dimension (e.g. \"visit:channel\", \"visit:source\", \"event:page\") or a custom event property as its bare name (e.g. \"plan\" targets event:props:plan)", + "maxLength": 312, + "minLength": 1, + "type": "string" + }, + "values": { + "description": "One or more values to match the property against", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "property", + "values" + ], + "type": "object" + }, + "type": "array" +} - changed
Input schema / properties / site_id / descriptionPrevious value: -"Plausible site domain (e.g. example.com). Uses PLAUSIBLE_DEFAULT_SITE_ID if omitted."New value: +"Plausible site domain (e.g. example.com). Required." - changed
Input schema / requiredPrevious value: -[ - "date_range" -]New value: +[ + "site_id", + "date_range" +] - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
get_timeseries5 fields changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema" - added
Input schema / properties / property_filtersAdded value: +{ + "description": "Filter results by built-in dimensions or custom event properties, e.g. [{ \"property\": \"visit:channel\", \"operator\": \"is\", \"values\": [\"Organic Search\"] }] or [{ \"property\": \"plan\", \"values\": [\"pro\"] }]. Entries are combined with AND.", + "items": { + "properties": { + "operator": { + "default": "is", + "description": "Match operator: is, is_not, contains, contains_not (default: is)", + "enum": [ + "is", + "is_not", + "contains", + "contains_not" + ], + "type": "string" + }, + "property": { + "description": "What to filter on: a built-in dimension (e.g. \"visit:channel\", \"visit:source\", \"event:page\") or a custom event property as its bare name (e.g. \"plan\" targets event:props:plan)", + "maxLength": 312, + "minLength": 1, + "type": "string" + }, + "values": { + "description": "One or more values to match the property against", + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "property", + "values" + ], + "type": "object" + }, + "type": "array" +} - changed
Input schema / properties / site_id / descriptionPrevious value: -"Plausible site domain (e.g. example.com). Uses PLAUSIBLE_DEFAULT_SITE_ID if omitted."New value: +"Plausible site domain (e.g. example.com). Required." - changed
Input schema / requiredPrevious value: -[ - "date_range" -]New value: +[ + "site_id", + "date_range" +] - changed
Output schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
4 tool updates
v0.5.1- First observed
compare_periods - First observed
get_breakdown - First observed
get_conversions - First observed
get_timeseries
TDQS
Scored across 4 tools
The four tools have largely distinct purposes: time series trends, dimension breakdowns, goal conversions, and period comparisons. However, get_conversions can break down by page, which overlaps slightly with get_breakdown, though descriptions clarify the focus on goals.
All tool names follow a clear verb_noun pattern in snake_case: get_timeseries, get_breakdown, get_conversions, compare_periods. Minor deviation in verb (get_ vs compare_) is appropriate and predictable.
Four tools are well within the typical 3-15 range and each covers a distinct analytical need. It is slightly thin for a full analytics surface (e.g., no site listing or realtime), but not problematic for a focused metrics server.
Core analytical queries are covered, but there are notable gaps such as listing available sites, retrieving site metadata, or accessing realtime/current visitors. For a read-only analytics server, these omissions could force agents to work around missing context.
Maintenance
Related MCP Connectors
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Clamp Analytics MCP server: traffic, revenue, funnels, cohorts, errors, and search, for AI agents.
Hosted MCP server for GA4, Google Ads and Search Console. Google OAuth, nothing to install.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server that provides read access to Plausible Analytics data with natural-language date resolution, enabling users to query analytics like 'yesterday' or 'last week' without needing to know exact date formats.8MIT
- AlicenseAqualityAmaintenanceMCP server for Yandex Metrica analytics: query web analytics metrics, goals, conversions, and raw API data using natural language from AI clients like Claude and Cursor.8390 npm4MIT
- AlicenseBqualityFmaintenanceMCP server for Plausible Analytics, enabling querying of traffic, conversions, sources, and device breakdowns from any MCP-compatible AI assistant.1222 npmMIT
- FlicenseBqualityCmaintenanceA read-only stdio-based MCP server that provides tools to query Plausible Analytics data, including listing sites, fetching statistics with metrics/dimensions/filters, and getting realtime visitor counts.3-