Skip to main content
Glama
getsentry

plausible-mcp

by getsentry

plausible-mcp

Servidor MCP para Plausible Analytics: consulta tráfico, conversiones y compara períodos de tiempo desde cualquier herramienta de IA compatible con Model Context Protocol.

Diseñado para equipos que quieren hacer preguntas como:

  • "¿Nuestro despliegue del martes afectó al tráfico de /pricing?"

  • "¿Cuál es la tasa de conversión de registro en /blog este mes?"

  • "¿Cómo se compara la tasa de rebote de esta semana con la de la semana pasada?"

Herramientas

Herramienta

Descripción

get_timeseries

Métricas de tráfico y conversión a lo largo del tiempo (diario/semanal/mensual)

get_breakdown

Desglose por página, fuente, país, dispositivo, navegador, SO, parámetros UTM

get_conversions

Tasas de conversión de objetivos, opcionalmente por página

compare_periods

Comparación lado a lado de dos rangos de fechas con deltas absolutos y porcentuales

Todas las herramientas de consulta son de solo lectura y están anotadas con readOnlyHint: true.

Los despliegues alojados exponen además send_feedback, que envía comentarios sobre el propio servidor (errores confusos, capacidades faltantes) a la bandeja de entrada de User Feedback de Sentry de los mantenedores. Solo se registra cuando el servidor se ejecuta con Sentry (enableFeedbackTool).

Related MCP server: Yandex Metrica MCP

Inicio rápido

Remoto (alojado)

Hay una instancia alojada disponible en https://plausible-mcp.sentry.dev.

Con tu propia clave de API de Plausible (cualquier usuario):

claude mcp add --transport http plausible https://plausible-mcp.sentry.dev/mcp --header "Authorization: Bearer YOUR_PLAUSIBLE_API_KEY"

Mantén la URL antes de --header. --header es variádico, así que si va al final, se traga la URL y la CLI falla con error: missing required argument 'commandOrUrl'.

O añádelo manualmente a la configuración de tu cliente MCP (Claude Desktop, Cursor, etc.):

{
  "mcpServers": {
    "plausible": {
      "url": "https://plausible-mcp.sentry.dev/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_PLAUSIBLE_API_KEY"
      }
    }
  }
}

Empleados de Sentry (a través de OAuth 2.1 + Cloudflare Access):

El endpoint /internal es un servidor OAuth 2.1: no se necesita clave de API. Añádelo como conector remoto/personalizado en cualquier cliente MCP compatible con OAuth (Cowork, conectores de Claude.ai, Claude Desktop):

https://plausible-mcp.sentry.dev/internal

El cliente descubre automáticamente los endpoints OAuth, te envía a través del SSO de Sentry (Cloudflare Access) y solo se concede acceso a identidades @sentry.io. Las consultas se ejecutan contra una clave de API de Plausible compartida del lado del servidor: nunca manejas una clave.

El /internal alojado en plausible-mcp.sentry.dev es solo para Sentry y no se puede usar fuera de la organización. Para ejecutar /internal para otra organización, auto-aloja y establece ALLOWED_EMAIL_DOMAIN a tu propio dominio. (El endpoint público /mcp de trae-tu-propia-clave no tiene esa restricción).

Local (STDIO)

Si prefieres ejecutarlo localmente, usa Node.js 20 o superior:

git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
pnpm build

Añádelo a Claude Code:

claude mcp add plausible -e PLAUSIBLE_API_KEY=your-key -- node /path/to/plausible-mcp/dist/index.js

O a Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "plausible": {
      "command": "node",
      "args": ["/path/to/plausible-mcp/dist/index.js"],
      "env": {
        "PLAUSIBLE_API_KEY": "your-key"
      }
    }
  }
}

Auto-alojamiento (Cloudflare Workers)

Despliega tu propia instancia:

git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
npx wrangler deploy

El worker expone dos endpoints:

  • /mcp — trae-tu-propia-clave. Cada usuario pasa su propia clave de API de Plausible mediante la cabecera Authorization: Bearer. No se necesitan secretos compartidos en el servidor. Funciona con cualquier cliente MCP compatible con cabeceras (Claude Code, Cursor, MCP Inspector).

  • /internal — endpoint MCP protegido por Access para conectores gestionados (Cowork, Claude.ai). Una aplicación de Cloudflare Access con OAuth gestionado protege todo el hostname del Worker (ver la restricción más abajo): Access realiza el protocolo OAuth 2.1 con el cliente y reenvía cada solicitud al Worker con una cabecera Cf-Access-Jwt-Assertion. El Worker verifica esa cabecera y consulta una clave de API de Plausible compartida del lado del servidor. Access está limitado a los dominios de correo en ALLOWED_EMAIL_DOMAIN (por defecto sentry.io) — no está vinculado a Sentry cuando te auto-alojas; establece tu propio dominio.

Debido a que la aplicación de OAuth gestionado debe cubrir el hostname desnudo sin ruta (Cloudflare rechaza una ruta cuando OAuth está habilitado — domain can not have a path if oauth is configured), también protege /mcp. Para mantener público el endpoint /mcp de trae-tu-propia-clave, añades una segunda aplicación Access más específica con ámbito en la ruta /mcp y una política Bypass. Cloudflare empareja primero el hostname+ruta más específico, así que las solicitudes a /mcp omiten Access por completo mientras que todo lo demás pasa por OAuth. Ambas aplicaciones viven en un solo hostname; no se requiere un subdominio separado.

Beta / requisito del cliente. El OAuth gestionado de Cloudflare Access está en Beta y requiere un cliente MCP que soporte RFC 8707 (indicadores de recurso). Confirma que tu conector lo soporta antes de depender de esta vía.

Configuración del endpoint /internal (OAuth gestionado de Cloudflare Access)

El Worker no ejecuta ningún servidor OAuth — Cloudflare Access es el servidor de autorización. No hay OAUTH_KV, ni clave de cookie, ni id/secreto de cliente OAuth. Creas dos aplicaciones Access en el mismo hostname.

  1. Crea la aplicación de OAuth gestionado sobre el hostname desnudo (Zero Trust → Access → Applications): una aplicación auto-alojada o servidor MCP cuyo dominio sea plausible-mcp.sentry.dev sin ruta.

    • ⚠️ No la limites a /internal. Una vez que OAuth gestionado está habilitado, Cloudflare rechaza cualquier ruta con access.api.error.invalid_request: domain can not have a path if oauth is configured. La aplicación debe ser todo el host; el Worker aplica la ruta /internal por sí mismo.

    • Añade una política de Access (Acción Allow) que restrinja a tu dominio de correo (p. ej. @acme.com) y proveedor de identidad.

    • Habilita OAuth gestionado (Configuración avanzada → Managed OAuth) y establece Allowed redirect URIs al callback real de tu conector — para Claude/Cowork es https://claude.ai/api/mcp/auth_callback. Los callbacks HTTPS públicos deben estar listados o el registro dinámico de clientes falla con invalid_client_metadata: redirect_uri is not allowed by the account configuration; los callbacks de loopback (http://localhost:*) están permitidos por defecto.

    • Copia la etiqueta AUD de la aplicación → esto se convierte en CF_ACCESS_AUD.

  2. Vuelve a separar /mcp con una segunda aplicación Bypass con ámbito de ruta. Como el paso 1 cubre todo el host, /mcp (trae-tu-propia-clave) ahora también está protegido. Crea otra aplicación auto-alojada, dominio plausible-mcp.sentry.dev ruta mcp, con OAuth gestionado DESACTIVADO, y una política cuya Acción sea Bypass con el selector Everyone.

    • Bypass ≠ Allow: una política Allow aún fuerza un inicio de sesión interactivo (el cliente recibe un 302 HTML a la página de inicio de sesión y falla con Unexpected content type: text/html). Solo Bypass deja pasar la solicitud sin autenticación, así que se aplica la verificación de clave Bearer del propio Worker.

  3. Establece los secretos del worker:

    npx wrangler secret put PLAUSIBLE_API_KEY          # shared key for /internal queries
    npx wrangler secret put SENTRY_DSN                 # optional — the Worker's own telemetry

    CF_ACCESS_TEAM_DOMAIN y CF_ACCESS_AUD no son secretos — una URL pública de JWKS y un identificador de aplicación — así que van en [vars] en el paso 4.

  4. Establece los [vars] en wrangler.toml:

    • CF_ACCESS_TEAM_DOMAIN — https://<team>.cloudflareaccess.com, sin barra final. Verifica el JWKS y el emisor de Cf-Access-Jwt-Assertion.

    • CF_ACCESS_AUD — la etiqueta AUD que copiaste en el paso 1.

    • ALLOWED_EMAIL_DOMAIN — el(los) dominio(s) de correo permitidos para iniciar sesión, separados por comas, @ opcional (por defecto sentry.io). Se aplica en el código además de la política de Access del paso 1, así que establécelo a tu propio dominio — de lo contrario, cada inicio de sesión es rechazado.

    • MCP_ALLOWED_HOSTNAMES — hostnames separados por comas aceptados por los endpoints MCP. Reemplaza plausible-mcp.sentry.dev con el hostname de tu worker; mantén las entradas de localhost si usas wrangler dev.

    • MCP_ALLOWED_ORIGIN_HOSTNAMES — hostnames de Origin del navegador separados por comas permitidos para llamar a /internal. Los clientes que no son navegador no envían una cabecera Origin.

  5. Despliega (npx wrangler deploy), y luego apunta un cliente MCP compatible con RFC 8707 a https://<tu-worker-host>/internal.

Solución de problemas. Todos estos son problemas de configuración de Cloudflare Access, no del Worker — una solicitud solo llega al Worker (y a sus spans de Sentry) una vez que Access la reenvía:

Síntoma (en el conector)

Causa

Solución

Couldn't register … / add an OAuth Client ID

El callback del conector no está en Allowed redirect URIs

Añade el callback exacto (paso 1); lee el redirect_uri rechazado desde Zero Trust → Logs → Access

domain can not have a path if oauth is configured

La aplicación de OAuth gestionado tiene ámbito en una ruta

Re-define el ámbito de la aplicación 1 al host desnudo (paso 1)

/mcp: Unexpected content type: text/html

La política de la aplicación /mcp es Allow, no Bypass

Establece la Acción de la política de la aplicación 2 a Bypass (paso 2)

/mcp: OAuth 401 invalid_token

No hay aplicación de bypass /mcp; la aplicación OAuth de todo el host la está protegiendo

Crea la aplicación 2 (paso 2)

Configuración

Variable de entorno

¿Requerida?

Valor por defecto

Descripción

PLAUSIBLE_API_KEY

Sí (STDIO; Worker /internal)

—

Tu clave de API de Plausible (consíguela aquí). En el Worker, esta es la clave compartida para /internal; /mcp usa la clave propia de cada usuario vía Bearer.

PLAUSIBLE_BASE_URL

No

https://plausible.io

URL de tu instancia de Plausible (para autoalojamiento)

PLAUSIBLE_DEFAULT_SITE_ID

No

—

Dominio del sitio por defecto para no tener que pasar site_id en cada llamada

CF_ACCESS_TEAM_DOMAIN

Sí (Worker /internal)

—

https://<team>.cloudflareaccess.com — verifica el JWKS y el emisor de Cf-Access-Jwt-Assertion. Sin barra final.

CF_ACCESS_AUD

Sí (Worker /internal)

—

La etiqueta de Audiencia de Aplicación (AUD) de la aplicación Access — se comprueba contra el aud de la aserción.

SENTRY_DSN

No (Worker)

—

DSN de Sentry para la telemetría propia del Worker (wrangler secret put SENTRY_DSN). Si no se define, Sentry queda desactivado — usa tu propio DSN si quieres telemetría en un despliegue autoalojado.

ALLOWED_EMAIL_DOMAIN

No (Worker /internal)

sentry.io

Dominio(s) de correo permitidos para iniciar sesión en /internal, separados por comas. Configúralo con tu propio dominio al autoalojar.

MCP_ALLOWED_HOSTNAMES

Sí (Worker)

—

Lista de hostnames permitidos, separados por comas, usada para validar las cabeceras Host de MCP.

MCP_ALLOWED_ORIGIN_HOSTNAMES

No (Worker /internal)

—

Hostnames de Origen del navegador permitidos para llamar a /internal, separados por comas. Si la lista está vacía, se rechaza cualquier Origin presente.

En el Worker, el endpoint /mcp no necesita clave del lado del servidor — cada usuario pasa la suya propia vía Authorization: Bearer. El endpoint /internal está protegido por Cloudflare Access Managed OAuth y usa un secreto compartido PLAUSIBLE_API_KEY del lado del servidor (consulta autoalojamiento).

API de Plausible

Este servidor envuelve la Plausible Stats API v2 (POST /api/v2/query). Funciona tanto con Plausible Cloud como con instancias autoalojadas.

Métricas admitidas

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

Dimensiones admitidas

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

Las dimensiones geográficas *_name devuelven nombres legibles (p. ej. «Canadá»); las simples visit:country/region/city devuelven códigos ISO/Geoname.

Filtrado

Cada herramienta de consulta acepta property_filters, que — pese al nombre — filtra tanto por dimensiones integradas como por propiedades de evento personalizadas. Cada entrada es { "property", "operator", "values" }:

  • property — una dimensión integrada (p. ej. visit:channel, visit:source, event:page) o una propiedad personalizada por su nombre simple ("plan" apunta a event:props:plan).

  • operator — is, is_not, contains, contains_not (por defecto is). event:goal solo admite is y contains.

  • Varias entradas se combinan con AND, al igual que los parámetros abreviados page/goal. Apuntar a event:page/event:goal a la vez desde un atajo y desde property_filters en la misma llamada se rechaza — usa uno u otro.

Por ejemplo, las páginas principales para tráfico de búsqueda orgánica: get_breakdown con dimension: "event:page" y property_filters: [{ "property": "visit:channel", "values": ["Organic Search"] }].

Propiedades personalizadas

Los sitios envían sus propias propiedades de evento personalizadas, referenciadas como event:props:<name>. Son específicas de cada sitio, por lo que no hay una lista fija.

  • Desglosar por una propiedad personalizada: pasa a get_breakdown una dimension de event:props:<name> (p. ej. event:props:plan).

  • Filtrar por una propiedad personalizada mediante property_filters con el nombre simple, p. ej. [{ "property": "plan", "operator": "is", "values": ["pro"] }].

Desarrollo

pnpm install
pnpm build         # TypeScript compilation
pnpm test          # Run unit + integration tests
pnpm test:watch    # Watch mode

Pruebas con MCP Inspector

pnpm build
PLAUSIBLE_API_KEY=your-key npx @modelcontextprotocol/inspector node dist/index.js

Evaluaciones LLM

Verifica que el modelo elige la herramienta correcta para preguntas analíticas en lenguaje natural. Se ejecuta a través de OpenRouter, por lo que cualquier modelo con llamada a herramientas funciona — el predeterminado es 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

Arquitectura

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.ts

PlausibleClient no tiene ninguna dependencia de MCP y puede usarse de forma independiente.

Observabilidad y recopilación de datos

El Worker informa a Sentry con una postura de privacidad que depende del endpoint:

  • /mcp (trae tu propia clave) — totalmente anónimo. Las entradas y salidas de las herramientas no se registran (esos datos pertenecen a quien llama y a su propia clave), no se adjunta ninguna identidad, y la IP del cliente inferida por el ingesta se elimina (src/redaction.ts). Solo queda la telemetría operativa: nombres de herramientas, tiempos de span y fallos.

  • /internal (protegido por SSO) — atribuido. Las solicitudes llevan el correo autenticado de @sentry.io (Sentry.setUser), y las entradas/salidas de las herramientas sí se registran (recordToolIO) para atribución y trazabilidad de abusos con la clave compartida del lado del servidor.

Las cabeceras Authorization / Cookie / Cf-Access-Jwt-Assertion se eliminan de los spans en ambas rutas. Como red de seguridad adicional, activa Prevent Storing of IP Addresses en los ajustes de Security & Privacy del proyecto de Sentry.

Licencia

MIT — consulta LICENSE.

Available Tools

4 tools
compare_periodsCompare PeriodsA
Read-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 %).

ParametersJSON Schema
NameRequiredDescriptionDefault
goalNoFilter by goal name (e.g. Signup, Purchase)
pageNoFilter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*)
metricsNoMetrics to return. Defaults vary by tool.
site_idYesPlausible site domain (e.g. example.com). Required.
period_aYesFirst date range, e.g. "2024-01-01,2024-01-07" or "7d"
period_bYesSecond date range, e.g. "2024-01-08,2024-01-14" or "7d"
property_filtersNoFilter 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

ParametersJSON Schema
NameRequiredDescription
deltasYesPer-metric change from period_a to period_b (absolute and percent)
period_aYes
period_bYes

TDQS

A4.2/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 BreakdownA
Read-onlyIdempotent

Break down metrics by a dimension: page, traffic source, country, device, etc. Use to find top pages, sources, or segment traffic.

ParametersJSON Schema
NameRequiredDescriptionDefault
pageNoFilter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*)
limitNoMax results to return
metricsNoMetrics to return. Defaults vary by tool.
site_idYesPlausible site domain (e.g. example.com). Required.
dimensionYesDimension 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_rangeYesDate range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD"
property_filtersNoFilter 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

ParametersJSON Schema
NameRequiredDescription
metricsYesMetric keys, in the order they appear in each row's `metrics` array
resultsYesOne row per dimension-value combination returned by Plausible
dimensionsYesDimension keys, in the order they appear in each row's `dimensions` array

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 ConversionsA
Read-onlyIdempotent

Get goal conversion rates and counts. Can break down by page to see which pages drive conversions.

ParametersJSON Schema
NameRequiredDescriptionDefault
goalNoFilter by goal name (e.g. Signup, Purchase)
pageNoFilter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*)
site_idYesPlausible site domain (e.g. example.com). Required.
date_rangeYesDate range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD"
property_filtersNoFilter 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_pageNoIf true, shows conversion rate per page

Output Schema

ParametersJSON Schema
NameRequiredDescription
metricsYesMetric keys, in the order they appear in each row's `metrics` array
resultsYesOne row per dimension-value combination returned by Plausible
dimensionsYesDimension keys, in the order they appear in each row's `dimensions` array

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness4/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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 TimeseriesA
Read-onlyIdempotent

Get traffic and conversion metrics over time for a site or specific page. Use to spot trends and changes around deploys.

ParametersJSON Schema
NameRequiredDescriptionDefault
goalNoFilter by goal name (e.g. Signup, Purchase)
pageNoFilter by page path. Exact match by default, use * as trailing wildcard (e.g. /blog*)
metricsNoMetrics to return. Defaults vary by tool.
site_idYesPlausible site domain (e.g. example.com). Required.
date_rangeYesDate range: "7d", "30d", "12mo", "month", "year", "all", or "YYYY-MM-DD,YYYY-MM-DD"
granularityNoTime bucket sizeday
property_filtersNoFilter 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

ParametersJSON Schema
NameRequiredDescription
metricsYesMetric keys, in the order they appear in each row's `metrics` array
resultsYesOne row per dimension-value combination returned by Plausible
dimensionsYesDimension keys, in the order they appear in each row's `dimensions` array

TDQS

A3.6/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

  1. 4 tool updatesv0.9.0
    • Changedcompare_periods8 fields changed
      • removedOutput schema / properties / deltas / additionalProperties / properties / absolute / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / deltas / additionalProperties / properties / absolute / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / deltas / additionalProperties / properties / percent / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / deltas / additionalProperties / properties / percent / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / period_a / properties / metrics / additionalProperties / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / period_a / properties / metrics / additionalProperties / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / period_b / properties / metrics / additionalProperties / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / period_b / properties / metrics / additionalProperties / type
        Added value: +[
        +  "number",
        +  "null"
        +]
    • Changedget_breakdown4 fields changed
      • removedOutput schema / properties / results / items / properties / dimensions / items / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "number"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / dimensions / items / type
        Added value: +[
        +  "string",
        +  "number"
        +]
      • removedOutput schema / properties / results / items / properties / metrics / items / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / metrics / items / type
        Added value: +[
        +  "number",
        +  "string",
        +  "null"
        +]
    • Changedget_conversions4 fields changed
      • removedOutput schema / properties / results / items / properties / dimensions / items / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "number"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / dimensions / items / type
        Added value: +[
        +  "string",
        +  "number"
        +]
      • removedOutput schema / properties / results / items / properties / metrics / items / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / metrics / items / type
        Added value: +[
        +  "number",
        +  "string",
        +  "null"
        +]
    • Changedget_timeseries4 fields changed
      • removedOutput schema / properties / results / items / properties / dimensions / items / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "number"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / dimensions / items / type
        Added value: +[
        +  "string",
        +  "number"
        +]
      • removedOutput schema / properties / results / items / properties / metrics / items / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / results / items / properties / metrics / items / type
        Added value: +[
        +  "number",
        +  "string",
        +  "null"
        +]
  2. 4 tool updatesv0.7.2
    • Changedcompare_periods1 field changed
      • addedInput schema / properties / property_filters / items / properties / values / items / maxLength
        Added value: +1024
    • Changedget_breakdown1 field changed
      • addedInput schema / properties / property_filters / items / properties / values / items / maxLength
        Added value: +1024
    • Changedget_conversions1 field changed
      • addedInput schema / properties / property_filters / items / properties / values / items / maxLength
        Added value: +1024
    • Changedget_timeseries1 field changed
      • addedInput schema / properties / property_filters / items / properties / values / items / maxLength
        Added value: +1024
  3. 4 tool updatesv0.7.1
    • Changedcompare_periods5 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / property_filters
        Added 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"
        +}
      • changedInput schema / properties / site_id / description
        Previous 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."
      • changedInput schema / required
        Previous value: -[
        -  "period_a",
        -  "period_b"
        -]New value: +[
        +  "site_id",
        +  "period_a",
        +  "period_b"
        +]
      • changedOutput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • Changedget_breakdown9 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / dimension / anyOf
        Added 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"
        +  }
        +]
      • changedInput schema / properties / dimension / description
        Previous 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)."
      • removedInput schema / properties / dimension / enum
        Removed 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"
        -]
      • removedInput schema / properties / dimension / type
        Removed value: -"string"
      • addedInput schema / properties / property_filters
        Added 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"
        +}
      • changedInput schema / properties / site_id / description
        Previous 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."
      • changedInput schema / required
        Previous value: -[
        -  "date_range",
        -  "dimension"
        -]New value: +[
        +  "site_id",
        +  "date_range",
        +  "dimension"
        +]
      • changedOutput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • Changedget_conversions5 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / property_filters
        Added 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"
        +}
      • changedInput schema / properties / site_id / description
        Previous 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."
      • changedInput schema / required
        Previous value: -[
        -  "date_range"
        -]New value: +[
        +  "site_id",
        +  "date_range"
        +]
      • changedOutput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • Changedget_timeseries5 fields changed
      • changedInput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
      • addedInput schema / properties / property_filters
        Added 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"
        +}
      • changedInput schema / properties / site_id / description
        Previous 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."
      • changedInput schema / required
        Previous value: -[
        -  "date_range"
        -]New value: +[
        +  "site_id",
        +  "date_range"
        +]
      • changedOutput schema / $schema
        Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
  4. 4 tool updatesv0.5.1
    • First observedcompare_periods
    • First observedget_breakdown
    • First observedget_conversions
    • First observedget_timeseries

TDQS

A3.7/5.0

Scored across 4 tools

Disambiguation4/5

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.

Naming Consistency5/5

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.

Tool Count4/5

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.

Completeness3/5

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

ActivityActive
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    MCP 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.
    8
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP 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.
    8
    390 npm
    4
    MIT
  • A
    license
    B
    quality
    F
    maintenance
    MCP server for Plausible Analytics, enabling querying of traffic, conversions, sources, and device breakdowns from any MCP-compatible AI assistant.
    12
    22 npm
    MIT
  • F
    license
    B
    quality
    C
    maintenance
    A 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
    -