plausible-mcp
This MCP server lets AI tools query Plausible Analytics traffic and conversion data, break it down, and compare time periods.
Get timeseries metrics such as visitors, visits, pageviews, bounce rate, visit duration, events, revenue, and conversion rate with hourly/daily/weekly/monthly granularity.
Break down metrics by page, source, country, device, browser, OS, UTM parameters, goals, or custom event properties, with pagination.
Retrieve goal conversion rates and counts, optionally broken down per page.
Compare two date ranges side by side with absolute and percent deltas measured from a baseline period.
Filter queries by built-in dimensions or custom properties, using date presets, calendar ranges, or sub-day windows.
All query tools are read-only; hosted deployments also expose
send_feedbackfor reporting server issues, and supported MCP Apps clients can show interactive charts and comparisons.
Provides tools to query traffic and conversion metrics from Plausible Analytics, including timeseries, breakdowns, conversions, and period comparisons.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@plausible-mcp How did our traffic change this month compared to last month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
plausible-mcp
MCP server for Plausible Analytics — query traffic, conversions, and compare time periods from any AI tool that supports Model Context Protocol.
Built for teams that want to ask questions like:
"Did our deploy on Tuesday affect traffic to /pricing?"
"What's the signup conversion rate on /blog this month?"
"How does this week's bounce rate compare to last week?"
Tools
Tool | Description |
| Traffic and conversion metrics over time (daily/weekly/monthly) |
| Break down by page, source, country, device, browser, OS, UTM params |
| Goal conversion rates, optionally per-page |
| Side-by-side comparison of two date ranges with absolute and % deltas, measured from |
All query tools are read-only and annotated with readOnlyHint: true.
Hosted deployments additionally expose send_feedback, which files feedback about the server itself (confusing errors, missing capabilities) into the maintainers' Sentry User Feedback inbox. It is only registered when the server runs with Sentry (enableFeedbackTool). On those deployments, a rejected query or an unexpected failure ends with a pointer to send_feedback, so the agent can report an error message that did not help it.
Interactive results
MCP Apps clients show traffic as a line chart, breakdowns and conversions as bars, and period comparisons side by side. Select a metric or open Data to inspect the rows. For comparisons of explicit calendar ranges up to 366 days each, hosts that support app-initiated tool calls also offer Daily trend. Selecting it reads both ranges using the same site, metrics and filters and plots them by day of period. Select a day by clicking, tapping, or using the arrow keys. Ask the chart which pages changed, or type your own question if the host supports sharing selections. The question keeps the metric, both calendar dates, and any filters. If the host cannot send the message, the chart provides the question to paste into chat. Hosted feedback calls show a receipt with the feedback ID. Text-only clients receive the usual tool output.
To try all five views with sample data, follow the local preview instructions.
Related MCP server: Yandex Metrica MCP
Quick Start
Remote (Hosted)
A hosted instance is available at https://plausible-mcp.sentry.dev.
With your own Plausible API key (any user):
The /mcp endpoint authenticates with a Plausible Stats API key, sent as Authorization: Bearer <key>. Plausible has no OAuth sign-in, so the client must send that header. Without it, the server returns 401.
Claude Code:
claude mcp add --transport http plausible https://plausible-mcp.sentry.dev/mcp --header "Authorization: Bearer YOUR_PLAUSIBLE_API_KEY"Keep the URL before
--header.--headeris variadic, so if it comes last it swallows the URL and the CLI fails witherror: missing required argument 'commandOrUrl'.
Codex:
codex mcp add plausible --url https://plausible-mcp.sentry.dev/mcp --bearer-token-env-var PLAUSIBLE_API_KEYCodex reads the key from PLAUSIBLE_API_KEY when it starts, so export that variable in your shell profile, not only in the current shell.
Cursor (~/.cursor/mcp.json) and other clients that accept custom headers:
{
"mcpServers": {
"plausible": {
"url": "https://plausible-mcp.sentry.dev/mcp",
"headers": {
"Authorization": "Bearer YOUR_PLAUSIBLE_API_KEY"
}
}
}
}Claude (claude.ai and Claude Desktop):
Add a custom connector under Customize → Connectors with:
MCP server URL:
https://plausible-mcp.sentry.dev/mcpAuthentication: No sign-in
Request headers:
authorizationwith the valueBearer YOUR_PLAUSIBLE_API_KEY. IncludeBearerin the value, because Claude sends it exactly as entered.
Request headers are in beta for a limited set of organizations. On Team and Enterprise plans, an Owner adds the connector and everyone in the organization queries with that one key. See Claude's connector docs.
If the dialog has no Request headers section, Claude cannot send the key, so claude.ai cannot use /mcp. In Claude Desktop, bridge to the hosted server with mcp-remote in claude_desktop_config.json instead, or run the server locally:
{
"mcpServers": {
"plausible": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://plausible-mcp.sentry.dev/mcp",
"--header",
"Authorization:${PLAUSIBLE_AUTH}"
],
"env": {
"PLAUSIBLE_AUTH": "Bearer YOUR_PLAUSIBLE_API_KEY"
}
}
}
}Keep
Authorization:${PLAUSIBLE_AUTH}without spaces. Some clients do not escape spaces insideargs, so the space goes in the environment variable instead.
Sentry employees (via OAuth 2.1 + Cloudflare Access):
The /internal endpoint is an OAuth 2.1 server — no API key needed. Add it as a remote/custom connector in any OAuth-capable MCP client (Cowork, Claude.ai connectors, Claude Desktop):
https://plausible-mcp.sentry.dev/internalThe client discovers the OAuth endpoints automatically, sends you through Sentry SSO (Cloudflare Access), and only @sentry.io identities are granted access. Queries run against a shared, server-side Plausible API key — you never handle a key.
The hosted
/internalatplausible-mcp.sentry.devis Sentry-only and can't be used outside the org. To run/internalfor a different organization, self-host and setALLOWED_EMAIL_DOMAINto your own domain. (The public/mcpbring-your-own-key endpoint has no such restriction.)
Local (STDIO)
If you prefer to run it locally, use Node.js 20 or newer:
git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
pnpm buildAdd to Claude Code:
claude mcp add plausible -e PLAUSIBLE_API_KEY=your-key -- node /path/to/plausible-mcp/dist/index.jsOr Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"plausible": {
"command": "node",
"args": ["/path/to/plausible-mcp/dist/index.js"],
"env": {
"PLAUSIBLE_API_KEY": "your-key"
}
}
}
}Self-Hosting (Cloudflare Workers)
The Worker serves the installation page at the root of LANDING_HOSTNAME. Set this to the same hostname that serves /mcp, such as plausible-mcp.sentry.dev. The embedded demo uses example data and needs no key.
Deploy your own instance:
git clone https://github.com/getsentry/plausible-mcp.git
cd plausible-mcp
pnpm install
npx wrangler deployThe worker exposes two endpoints:
/mcp— bring-your-own-key. Each user passes their own Plausible API key via theAuthorization: Bearerheader. No shared secrets needed on the server. Works with any header-capable MCP client (Claude Code, Cursor, MCP Inspector)./internal— Access-protected MCP endpoint for managed connectors (Cowork, Claude.ai). A Cloudflare Access application with Managed OAuth fronts the whole Worker hostname (see the constraint below): Access runs the OAuth 2.1 handshake with the client and forwards each request to the Worker with aCf-Access-Jwt-Assertionheader. The Worker verifies that header and queries a shared, server-side Plausible API key. Access is gated to the email domain(s) inALLOWED_EMAIL_DOMAIN(defaults tosentry.io) — not tied to Sentry when you self-host; set it to your own domain.
Because the Managed OAuth application must cover the bare hostname with no path (Cloudflare rejects a path when OAuth is enabled — domain can not have a path if oauth is configured), it also gates /mcp. To keep the bring-your-own-key /mcp endpoint public you add a second, more-specific Access application scoped to the /mcp path with a Bypass policy. Cloudflare matches the most specific hostname+path first, so /mcp requests bypass Access entirely while the Managed OAuth application protects the other paths except for explicit public overrides. Both apps live on one hostname; no separate subdomain is required.
Beta / client requirement. Cloudflare Access Managed OAuth is in Beta and requires an MCP client that supports RFC 8707 (resource indicators). Confirm your connector supports it before relying on this path.
Setting up the /internal endpoint (Cloudflare Access Managed OAuth)
The Worker runs no OAuth server — Cloudflare Access is the authorization server. There is no OAUTH_KV, no cookie key, and no OAuth client id/secret. You create two Access applications on the same hostname.
Create the Managed OAuth application over the bare hostname (Zero Trust → Access → Applications): a self-hosted app or MCP server application whose domain is
plausible-mcp.sentry.devwith no path.⚠️ Do not scope it to
/internal. Once Managed OAuth is enabled, Cloudflare rejects any path withaccess.api.error.invalid_request: domain can not have a path if oauth is configured. The app must be the whole host; the Worker enforces the/internalroute itself.Add an Access policy (Action
Allow) restricting to your email domain (e.g.@acme.com) and identity provider.Enable Managed OAuth (Advanced settings → Managed OAuth) and set Allowed redirect URIs to your connector's actual callback — for Claude/Cowork that is
https://claude.ai/api/mcp/auth_callback. Public HTTPS callbacks must be listed or Dynamic Client Registration fails withinvalid_client_metadata: redirect_uri is not allowed by the account configuration; loopback (http://localhost:*) callbacks are allowed by default.Copy the application's AUD tag → this becomes
CF_ACCESS_AUD.To publish the landing page, add
{"path_pattern":"/","behavior":"public"}to the existing hostname destination’soverridesafter deploying the Worker. Keep its other settings. Cloudflare matches this override to the root only./internaland OAuth discovery retain their existing configuration. See Access application destinations.Verify from a signed-out client:
GET /returns the landing HTML,GET /internalreturns the OAuth challenge, andGET /mcpwithout a key returns the Plausible bearer challenge.
Carve
/mcpback out with a second, path-scoped Bypass application. Because step 1 covers the whole host,/mcp(bring-your-own-key) is now gated too. Create another self-hosted app, domainplausible-mcp.sentry.devpathmcp, with Managed OAuth OFF, and a policy whose Action isBypasswith the selectorEveryone.Bypass≠Allow: anAllowpolicy still forces an interactive login (the client gets an HTML302to the login page and fails withUnexpected content type: text/html). OnlyBypasslets the request through with no authentication, so the Worker's own Bearer-key check applies.
Set the worker secrets:
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_DOMAINandCF_ACCESS_AUDare not secrets — a public JWKS URL and an application identifier — so they go in[vars]in step 4.Set the
[vars]inwrangler.toml:CF_ACCESS_TEAM_DOMAIN—https://<team>.cloudflareaccess.com, no trailing slash. Verifies theCf-Access-Jwt-AssertionJWKS and issuer.CF_ACCESS_AUD— the AUD tag you copied in step 1.ALLOWED_EMAIL_DOMAIN— the email domain(s) allowed to sign in, comma-separated,@optional (defaultsentry.io). Enforced in code in addition to the Access policy in step 1, so set it to your own domain — otherwise every login is rejected.MCP_ALLOWED_HOSTNAMES— comma-separated hostnames accepted by the MCP endpoints. Replaceplausible-mcp.sentry.devwith your worker's hostname; keep the localhost entries if you usewrangler dev.MCP_ALLOWED_ORIGIN_HOSTNAMES— comma-separated browser Origin hostnames allowed to call/internal. Non-browser clients do not send anOriginheader.
Deploy (
npx wrangler deploy), then point an RFC 8707-capable MCP client athttps://<your-worker-host>/internal.
Troubleshooting. All of these are Cloudflare Access configuration, not the Worker — a request only reaches the Worker (and its Sentry spans) once Access forwards it:
Symptom (in the connector) | Cause | Fix |
| Connector callback isn't in Allowed redirect URIs | Add the exact callback (step 1); read the rejected |
| Managed OAuth app scoped to a path | Rescope app 1 to the bare host (step 1) |
|
| Set the app-2 policy Action to |
| No | Create app 2 (step 2) |
Configuration
Environment Variable | Required | Default | Description |
| Yes (STDIO; Worker | — | Your Plausible API key (get one here). On the Worker this is the shared key for |
| No |
| URL of your Plausible instance (for self-hosted) |
| No | — | Default site domain so you don't have to pass |
| Yes (Worker | — |
|
| Yes (Worker | — | The Access application's Application Audience (AUD) tag — checked against the assertion's |
| No (Worker) | — | Sentry DSN for the Worker's own telemetry ( |
| No (Worker |
| Comma-separated email domain(s) allowed to sign in to |
| Yes (Worker) | — | Comma-separated hostname allowlist used to validate MCP |
| No (Worker | — | Comma-separated browser Origin hostnames allowed to call |
On the Worker, the /mcp endpoint needs no server-side key — each user passes their own via Authorization: Bearer. The /internal endpoint is fronted by Cloudflare Access Managed OAuth and uses a shared server-side PLAUSIBLE_API_KEY secret (see self-hosting).
Plausible API
This server wraps the Plausible Stats API v2 (POST /api/v2/query). It works with both Plausible Cloud and self-hosted instances.
Supported Metrics
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
Supported Dimensions
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
The *_name geography dimensions return human-readable names (e.g. "Canada"); the plain visit:country/region/city return ISO/Geoname codes.
Filtering
Every query tool accepts property_filters, which — despite the name — filters by built-in dimensions as well as custom event properties. Each entry is { "property", "operator", "values" }:
property— a built-in dimension (e.g.visit:channel,visit:source,event:page) or a custom property as its bare name ("plan"targetsevent:props:plan).operator—is,is_not,contains,contains_not(defaultis).event:goalsupports onlyisandcontains.Multiple entries combine with AND, as do the
page/goalshortcut parameters. Targetingevent:page/event:goalfrom both a shortcut andproperty_filtersin the same call is rejected — use one or the other.
For example, top pages for organic search traffic: get_breakdown with dimension: "event:page" and property_filters: [{ "property": "visit:channel", "values": ["Organic Search"] }].
Custom Properties
Sites send their own custom event properties, addressed as event:props:<name>. These are site-specific, so there's no fixed list.
Break down by a custom property: pass
get_breakdownadimensionofevent:props:<name>(e.g.event:props:plan).Filter by a custom property via
property_filterswith the bare name, e.g.[{ "property": "plan", "operator": "is", "values": ["pro"] }].
Development
pnpm install
pnpm build # TypeScript compilation
pnpm test # Run unit + integration tests
pnpm test:watch # Watch modeTesting with MCP Inspector
pnpm build
PLAUSIBLE_API_KEY=your-key npx @modelcontextprotocol/inspector node dist/index.jsLLM Evals
Verifies the model picks the right tool for natural language analytics questions. Runs through
OpenRouter, so any tool-calling model works — the default is 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 modelArchitecture
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 has zero MCP dependency and can be used standalone.
Observability & data collection
The Worker reports to Sentry with an endpoint-dependent privacy posture:
/mcp(bring-your-own-key) — fully anonymous. Tool inputs and outputs are not recorded (that data belongs to the caller and their own key), no identity is attached, and the ingest-inferred client IP is stripped (src/redaction.ts). Only operational telemetry remains: tool names, span timings, and failures./internal(SSO-gated) — attributed. Requests carry the authenticated@sentry.ioemail (Sentry.setUser), and tool inputs/outputs are recorded (recordToolIO) for attribution and abuse-tracing on the shared server-side key.
Authorization / Cookie / Cf-Access-Jwt-Assertion headers are stripped from spans on both paths. As a belt-and-suspenders backstop, enable Prevent Storing of IP Addresses in the Sentry project's Security & Privacy settings.
License
MIT — see LICENSE.
Available Tools
4 toolscompare_periodsCompare PeriodsARead-onlyIdempotent
Compare metrics between two date ranges side by side. Ideal for before/after deploy analysis. period_a is the baseline and period_b is measured against it: deltas are period_b minus period_a, and percent is relative to period_a. To see how the current period changed from a prior one, pass the prior range as period_a and the current range as period_b.
Inputs: site_id (required), period_a (required), period_b (required), goal, +3 more. Returns: deltas, period_a, period_b
get_breakdownGet BreakdownBRead-onlyIdempotent
Break down metrics by a dimension: page, traffic source, country, device, etc. Use to find top pages, sources, or segment traffic.
Inputs: site_id (required), dimension (required), date_range (required), goal, +5 more. Returns: metrics, results, dimensions, pagination
get_conversionsGet ConversionsBRead-onlyIdempotent
Get goal conversion rates and counts. Can break down by page to see which pages drive conversions.
Inputs: site_id (required), date_range (required), goal, page, +2 more. Returns: metrics, results, dimensions, pagination
get_timeseriesGet TimeseriesBRead-onlyIdempotent
Get traffic and conversion metrics over time for a site or specific page. Use to spot trends and changes around deploys.
Inputs: site_id (required), date_range (required), goal, page, +3 more. Returns: metrics, results, dimensions, pagination
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
Each tool has a distinct analytics purpose: dimensional breakdown, conversion goals, time series, and period comparison. There is some conceptual overlap because conversions can be segmented and trended, but the descriptions make the intended boundaries clear.
All tool names use snake_case verb_noun phrasing: get_breakdown, get_conversions, get_timeseries, and compare_periods. The get_/compare_ variation is still predictable and consistent with the action being performed.
Four tools are well scoped for a focused analytics server, with each covering a meaningful query pattern. The compact surface avoids redundancy while still supporting the core reporting workflows.
The core analytics surface is covered: breakdowns, conversions, trends over time, and period comparisons. Minor gaps remain around site/property discovery or metadata, but the main analysis workflows are supported.
Maintenance
Related MCP Connectors
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Hosted MCP server for marketing agencies: connect Google Ads, GA4, Search Console, Meta, LinkedIn and more to Claude or ChatGPT. Pull reports and make changes via OAuth login, no setup.
Clamp Analytics MCP server: traffic, revenue, funnels, cohorts, errors, and search, for AI agents.
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
- AlicenseAqualityBmaintenanceMCP 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.8708 npm4MIT
- AlicenseBqualityFmaintenanceMCP server for Plausible Analytics, enabling querying of traffic, conversions, sources, and device breakdowns from any MCP-compatible AI assistant.1263 npmMIT
- FlicenseBqualityDmaintenanceA 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-