plausible-mcp
plausible-mcp
用于 Plausible Analytics 的 MCP 服务器 — 从任何支持 Model Context Protocol 的 AI 工具查询流量、转化率,并比较时间段。
专为想要提出以下问题的团队而构建:
“我们周二部署是否影响了 /pricing 的流量?”
“本月 /blog 的注册转化率是多少?”
“本周的跳出率与上周相比如何?”
工具
工具 | 描述 |
| 随时间变化的流量和转化指标(每日/每周/每月) |
| 按页面、来源、国家、设备、浏览器、操作系统、UTM 参数细分 |
| 目标转化率,可选按页面细分 |
| 并排比较两个日期范围,包含绝对值和百分比差异 |
所有查询工具均为只读,并标注了 readOnlyHint: true。
托管部署还公开了 send_feedback,它将关于服务器本身的反馈(令人困惑的错误、缺失的功能)提交到维护者的 Sentry User Feedback 收件箱。仅当服务器使用 Sentry 运行时(enableFeedbackTool)才会注册该工具。
Related MCP server: Yandex Metrica MCP
快速开始
远程(托管)
托管实例位于 https://plausible-mcp.sentry.dev。
使用您自己的 Plausible API 密钥(任何用户):
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 密钥。将其作为远程/自定义连接器添加到任何支持 OAuth 的 MCP 客户端(Cowork、Claude.ai 连接器、Claude Desktop):
https://plausible-mcp.sentry.dev/internal客户端会自动发现 OAuth 端点,将您引导通过 Sentry SSO(Cloudflare Access),并且只有 @sentry.io 身份被授予访问权限。查询使用共享的服务器端 Plausible API 密钥运行 — 您无需处理密钥。
位于
plausible-mcp.sentry.dev的托管/internal仅限 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 deploy该 Worker 公开两个端点:
/mcp— 自带密钥。每个用户通过Authorization: Bearer标头传递自己的 Plausible API 密钥。服务器上无需共享密钥。适用于任何支持标头的 MCP 客户端(Claude Code、Cursor、MCP Inspector)。/internal— 用于托管连接器(Cowork、Claude.ai)的受 Access 保护的 MCP 端点。一个带有 Managed OAuth 的 Cloudflare Access 应用程序位于整个 Worker 主机名之前(见下面的约束):Access 与客户端执行 OAuth 2.1 握手,并将每个请求转发给 Worker,并带有Cf-Access-Jwt-Assertion标头。Worker 验证该标头,并查询共享的服务器端 Plausible API 密钥。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。两个应用程序位于同一主机名上;无需单独的子域。
Beta / 客户端要求。 Cloudflare Access Managed OAuth 处于 Beta 阶段,要求 MCP 客户端支持 RFC 8707(资源指示符)。在依赖此路径之前,请确认您的连接器支持它。
设置 /internal 端点(Cloudflare Access Managed OAuth)
Worker 不运行 OAuth 服务器 — Cloudflare Access 是授权服务器。没有 OAUTH_KV,没有 cookie 密钥,也没有 OAuth 客户端 ID/密钥。您需要在同一主机名上创建两个 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(高级设置 → Managed OAuth),并将 Allowed redirect URIs 设置为您的连接器的实际回调 — 对于 Claude/Cowork,即
https://claude.ai/api/mcp/auth_callback。公共 HTTPS 回调必须列出,否则动态客户端注册会失败,并显示invalid_client_metadata: redirect_uri is not allowed by the account configuration;默认允许环回(http://localhost:*)回调。复制应用程序的 AUD 标签 → 这将成为
CF_ACCESS_AUD。
使用第二个、路径作用域的 Bypass 应用程序将
/mcp重新分离出来。 由于步骤 1 覆盖了整个主机,/mcp(自带密钥)现在也被限制了。创建另一个自托管应用程序,域为plausible-mcp.sentry.dev路径mcp,Managed OAuth 关闭,并设置一个策略,其操作为Bypass,选择器为Everyone。Bypass≠Allow:Allow策略仍会强制交互式登录(客户端会收到 HTML302重定向到登录页面,并以Unexpected content type: text/html失败)。只有Bypass允许请求无需身份验证即可通过,因此 Worker 自身的 Bearer 密钥检查生效。
设置 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不是密钥 — 它们是公共 JWKS URL 和应用程序标识符 — 因此它们放在步骤 4 的[vars]中。在
wrangler.toml中设置[vars]:CF_ACCESS_TEAM_DOMAIN—https://<team>.cloudflareaccess.com,不带尾部斜杠。验证Cf-Access-Jwt-AssertionJWKS 和签发者。CF_ACCESS_AUD— 您在步骤 1 中复制的 AUD 标签。ALLOWED_EMAIL_DOMAIN— 允许登录的电子邮件域,逗号分隔,@可选(默认sentry.io)。在代码中除了步骤 1 中的 Access 策略之外强制执行,因此请将其设置为您自己的域 — 否则每次登录都会被拒绝。MCP_ALLOWED_HOSTNAMES— MCP 端点接受的主机名,逗号分隔。将plausible-mcp.sentry.dev替换为您的 Worker 主机名;如果使用wrangler dev,请保留 localhost 条目。MCP_ALLOWED_ORIGIN_HOSTNAMES— 允许调用/internal的浏览器 Origin 主机名,逗号分隔。非浏览器客户端不会发送Origin标头。
部署(
npx wrangler deploy),然后将支持 RFC 8707 的 MCP 客户端指向https://<your-worker-host>/internal。
故障排除。 这些都是 Cloudflare Access 配置,而不是 Worker — 请求只有在 Access 转发后才会到达 Worker(及其 Sentry spans):
连接器中的症状 | 原因 | 修复 |
| 连接器回调不在 Allowed redirect URIs 中 | 添加确切的回调(步骤 1);从 Zero Trust → Logs → Access 中读取被拒绝的 |
| Managed OAuth 应用程序作用域为路径 | 将应用程序 1 重新限定为裸主机(步骤 1) |
|
| 将应用程序 2 的策略操作设置为 |
| 没有 | 创建应用程序 2(步骤 2) |
配置
环境变量 | 是否必需 | 默认值 | 描述 |
| 是(STDIO;Worker | — | 你的 Plausible API 密钥(在此获取)。在 Worker 上,这是 |
| 否 |
| 你的 Plausible 实例的 URL(用于自托管) |
| 否 | — | 默认站点域名,这样你就不必在每次调用时都传 |
| 是(Worker | — |
|
| 是(Worker | — | Access 应用程序的 Application Audience(AUD)标签 — 会与断言中的 |
| 否(Worker) | — | 用于 Worker 自身遥测的 Sentry DSN( |
| 否(Worker |
| 允许登录 |
| 是(Worker) | — | 用于校验 MCP |
| 否(Worker | — | 允许调用 |
在 Worker 上,/mcp 端点不需要服务端密钥 — 每个用户通过 Authorization: Bearer 传入自己的密钥。/internal 端点由 Cloudflare Access Managed OAuth 提供前置保护,并使用共享的服务端 PLAUSIBLE_API_KEY 密钥(参见自托管)。
Plausible API
此服务器封装了 Plausible Stats API v2(POST /api/v2/query)。它同时适用于 Plausible Cloud 和自托管实例。
支持的指标
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快捷参数也是如此。在同一调用中同时通过快捷参数和property_filters指定event:page/event:goal会被拒绝 — 请二选一。
例如,获取自然搜索流量的热门页面:使用 dimension: "event:page" 和 property_filters: [{ "property": "visit:channel", "values": ["Organic Search"] }] 调用 get_breakdown。
自定义属性
站点会发送自己的自定义事件属性,以 event:props:<name> 的形式寻址。这些属性因站点而异,因此没有固定的列表。
按自定义属性细分:向
get_breakdown传入event:props:<name>作为dimension(例如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(自带密钥) — 完全匿名。工具输入和输出不会被记录(这些数据属于调用者及其自己的密钥),不附加任何身份信息,并且会剥离由 ingest 推断出的客户端 IP(src/redaction.ts)。仅保留操作层面的遥测:工具名称、span 耗时和失败信息。/internal(SSO 门控) — 可归因。请求携带经过认证的@sentry.io电子邮件(Sentry.setUser),并且工具输入/输出会被记录(recordToolIO),以便在共享的服务端密钥上进行归因和滥用追踪。
Authorization / Cookie / Cf-Access-Jwt-Assertion 头在两条路径上都会从 span 中剥离。作为双保险,请在 Sentry 项目的 Security & Privacy 设置中启用 Prevent Storing of IP Addresses(阻止存储 IP 地址)。
许可证
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-