Skip to main content
Glama
getsentry

plausible-mcp

by getsentry

plausible-mcp

Plausible Analytics 用の MCP サーバー — Model Context Protocol をサポートする任意の AI ツールから、トラフィック、コンバージョンの照会、期間の比較ができます。

次のような質問をしたいチーム向けに作られています:

  • 「火曜日のデプロイは /pricing へのトラフィックに影響しましたか?」

  • 「今月の /blog のサインアップコンバージョン率は?」

  • 「今週の直帰率は先週と比べてどうですか?」

ツール

ツール

説明

get_timeseries

期間ごとのトラフィックとコンバージョンメトリクス(日次/週次/月次)

get_breakdown

ページ、ソース、国、デバイス、ブラウザ、OS、UTM パラメータで内訳を表示

get_conversions

目標コンバージョン率(ページごとの指定も可能)

compare_periods

2 つの日付範囲を絶対値と % の差分で並べて比較

すべてのクエリツールは読み取り専用で、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 の ID のみがアクセスを許可されます。クエリは共有のサーバーサイド Plausible API キーに対して実行されるため、キーを扱う必要はありません。

ホスト型の /internal(plausible-mcp.sentry.dev)は Sentry 専用で、組織外では使用できません。別の組織で /internal を実行するには、セルフホスト して ALLOWED_EMAIL_DOMAIN を自分のドメインに設定してください。(公開の /mcp 持ち込みキーエンドポイントにはそのような制限はありません。)

ローカル(STDIO)

ローカルで実行したい場合は、Node.js 20 以降を使用してください:

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

Claude Code に追加:

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

または Claude Desktop(claude_desktop_config.json):

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

セルフホスティング(Cloudflare Workers)

独自のインスタンスをデプロイ:

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

ワーカーは 2 つのエンドポイントを公開します:

  • /mcp — 持ち込みキー。各ユーザーは独自の Plausible API キーを Authorization: Bearer ヘッダーで渡します。サーバー側で共有シークレットは不要です。ヘッダー対応の MCP クライアント(Claude Code、Cursor、MCP Inspector)で動作します。

  • /internal — 管理コネクタ(Cowork、Claude.ai)用の Access 保護 MCP エンドポイント。Cloudflare Access アプリケーション(Managed OAuth)がワーカーのホスト名全体を前面に置きます(下記の制約を参照)。Access はクライアントとの OAuth 2.1 ハンドシェイクを実行し、各リクエストを Cf-Access-Jwt-Assertion ヘッダー付きでワーカーに転送します。ワーカーはそのヘッダーを検証し、共有のサーバーサイド Plausible API キーを使用してクエリを実行します。Access は ALLOWED_EMAIL_DOMAIN(デフォルトは sentry.io)のメールドメインに制限されます — セルフホスト時は Sentry に関連付けられません。自分のドメインに設定してください。

Managed OAuth アプリケーションはパスなしのベアホスト名をカバーする必要があるため(OAuth を有効にすると Cloudflare はパスを拒否します — domain can not have a path if oauth is configured)、/mcp もゲートされます。持ち込みキーの /mcp エンドポイントを公開のままにするには、/mcp パスにスコープされた 2 つ目のより具体的な Access アプリケーションを Bypass ポリシーで追加します。Cloudflare は最も具体的なホスト名+パスを最初に一致させるため、/mcp リクエストは Access を完全にバイパスし、それ以外はすべて OAuth を通過します。両方のアプリは 1 つのホスト名に存在します。別のサブドメインは不要です。

ベータ版 / クライアント要件。 Cloudflare Access Managed OAuth はベータ版であり、RFC 8707(リソースインジケータ)をサポートする MCP クライアントが必要です。このパスに依存する前に、コネクタがサポートしていることを確認してください。

/internal エンドポイントの設定(Cloudflare Access Managed OAuth)

ワーカーはOAuth サーバーを実行しません — Cloudflare Access が認可サーバーです。OAUTH_KV も、クッキーキーも、OAuth クライアント ID/シークレットもありません。同じホスト名に2 つの Access アプリケーションを作成します。

  1. ベアホスト名で Managed OAuth アプリケーションを作成(Zero Trust → Access → Applications): ドメインが plausible-mcp.sentry.dev でパスなしのセルフホストアプリまたはMCP サーバーアプリケーション。

    • ⚠️ /internal にスコープしないでください。 Managed OAuth を有効にすると、Cloudflare はパスを access.api.error.invalid_request: domain can not have a path if oauth is configured で拒否します。アプリはホスト全体である必要があります。ワーカーが /internal ルートを自身で強制します。

    • メールドメイン(例: @acme.com)と ID プロバイダーに制限する Access ポリシー(アクション Allow)を追加します。

    • Managed OAuth を有効にし(詳細設定 → Managed OAuth)、許可されたリダイレクト URI をコネクタの実際のコールバックに設定します — Claude/Cowork の場合は https://claude.ai/api/mcp/auth_callback。公開 HTTPS コールバックは必ずリストに含める必要があります。含めないと Dynamic Client Registration が invalid_client_metadata: redirect_uri is not allowed by the account configuration で失敗します。ループバック(http://localhost:*)コールバックはデフォルトで許可されます。

    • アプリケーションの AUD タグをコピー → これが CF_ACCESS_AUD になります。

  2. 2 つ目のパススコープの Bypass アプリケーションで /mcp を切り出します。 ステップ 1 がホスト全体をカバーするため、/mcp(持ち込みキー)もゲートされます。別のセルフホストアプリを作成し、ドメイン plausible-mcp.sentry.dev パス mcp、Managed OAuth オフ、ポリシーのアクションは Bypass、セレクターは Everyone にします。

    • Bypass ≠ Allow: Allow ポリシーは対話型ログインを強制します(クライアントはログインページへの HTML 302 を受け取り、Unexpected content type: text/html で失敗します)。Bypass のみが認証なしでリクエストを通過させ、ワーカー自身の Bearer キーチェックが適用されます。

  3. ワーカーシークレットを設定:

    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 と CF_ACCESS_AUD はシークレットではありません — 公開 JWKS URL とアプリケーション識別子です。そのためステップ 4 の [vars] に入ります。

  4. wrangler.toml の [vars] を設定:

    • CF_ACCESS_TEAM_DOMAIN — https://<team>.cloudflareaccess.com、末尾スラッシュなし。Cf-Access-Jwt-Assertion の JWKS と発行者を検証します。

    • CF_ACCESS_AUD — ステップ 1 でコピーした AUD タグ。

    • ALLOWED_EMAIL_DOMAIN — サインインを許可するメールドメイン(カンマ区切り、@ は省略可能、デフォルト sentry.io)。ステップ 1 の Access ポリシーに加えてコードで強制されるため、自分のドメインに設定してください — そうしないとすべてのログインが拒否されます。

    • MCP_ALLOWED_HOSTNAMES — MCP エンドポイントが受け入れるホスト名(カンマ区切り)。plausible-mcp.sentry.dev をワーカーのホスト名に置き換え、wrangler dev を使用する場合は localhost エントリを維持します。

    • MCP_ALLOWED_ORIGIN_HOSTNAMES — /internal を呼び出せるブラウザ Origin ホスト名(カンマ区切り)。非ブラウザクライアントは Origin ヘッダーを送信しません。

  5. デプロイ(npx wrangler deploy)し、RFC 8707 対応の MCP クライアントを https://<your-worker-host>/internal にポイントします。

トラブルシューティング。 これらはすべて Cloudflare Access の設定であり、ワーカーではありません — リクエストがワーカー(およびその Sentry スパン)に到達するのは、Access が転送した後だけです:

症状(コネクタ内)

原因

修正

Couldn't register … / add an OAuth Client ID

コネクタのコールバックが許可されたリダイレクト URI にない

正確なコールバックを追加(ステップ 1)。拒否された redirect_uri は Zero Trust → Logs → Access で確認

domain can not have a path if oauth is configured

Managed OAuth アプリがパスにスコープされている

アプリ 1 をベアホストに再スコープ(ステップ 1)

/mcp: Unexpected content type: text/html

/mcp アプリのポリシーが Allow で Bypass ではない

アプリ 2 のポリシーアクションを Bypass に設定(ステップ 2)

/mcp: OAuth 401 invalid_token

/mcp バイパスアプリがない。ホスト全体の OAuth アプリがゲートしている

アプリ 2 を作成(ステップ 2)

設定

環境変数

必須

デフォルト

説明

PLAUSIBLE_API_KEY

はい(STDIO、Worker /internal)

—

あなたの Plausible API キー(こちらで取得)。Worker では /internal 用の共有キーです。/mcp は各ユーザーが自分のキーを Bearer で渡します。

PLAUSIBLE_BASE_URL

いいえ

https://plausible.io

Plausible インスタンスの URL(セルフホスト用)

PLAUSIBLE_DEFAULT_SITE_ID

いいえ

—

デフォルトのサイトドメイン。毎回 site_id を渡す必要がなくなります

CF_ACCESS_TEAM_DOMAIN

はい(Worker /internal)

—

https://<team>.cloudflareaccess.com — Cf-Access-Jwt-Assertion の JWKS と issuer を検証します。末尾スラッシュなし。

CF_ACCESS_AUD

はい(Worker /internal)

—

Access アプリケーションの Application Audience(AUD)タグ — アサーションの aud と照合されます。

SENTRY_DSN

いいえ(Worker)

—

Worker 自身のテレメトリ用 Sentry DSN(wrangler secret put SENTRY_DSN)。未設定の場合は Sentry が無効になります — セルフホスト環境でテレメトリが必要な場合は独自の DSN を使用してください。

ALLOWED_EMAIL_DOMAIN

いいえ(Worker /internal)

sentry.io

/internal へのサインインを許可するメールドメイン(カンマ区切り)。セルフホスト時は自分のドメインに設定してください。

MCP_ALLOWED_HOSTNAMES

はい(Worker)

—

MCP の Host ヘッダーを検証するためのホスト名許可リスト(カンマ区切り)。

MCP_ALLOWED_ORIGIN_HOSTNAMES

いいえ(Worker /internal)

—

/internal を呼び出せるブラウザの Origin ホスト名(カンマ区切り)。リストが空の場合、Origin が存在すると拒否されます。

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 を対象にすることは拒否されます — どちらか一方を使用してください。

例えば、オーガニック検索トラフィックの上位ページを取得する場合:get_breakdown に dimension: "event:page" と property_filters: [{ "property": "visit:channel", "values": ["Organic Search"] }] を渡します。

カスタムプロパティ

サイトは独自のカスタムイベントプロパティを送信します。これらは 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.js

LLM 評価

自然言語の分析クエリに対してモデルが正しいツールを選択するかを検証します。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.ts

PlausibleClient は MCP への依存がゼロで、単体で使用できます。

可観測性とデータ収集

Worker はエンドポイントに応じたプライバシーポリシーで Sentry にレポートします:

  • /mcp(キー持ち込み) — 完全に匿名。ツールの入力と出力は記録されません(そのデータは呼び出し元とそのキーに属します)。ID は付与されず、取り込み時に推測されたクライアント IP は削除されます(src/redaction.ts)。運用テレメトリのみが残ります:ツール名、スパンのタイミング、失敗。

  • /internal(SSO ゲート付き) — 属性付き。リクエストには認証済みの @sentry.io メールアドレスが付与され(Sentry.setUser)、ツールの入力/出力は共有サーバー側キーの属性追跡と不正使用トレースのために記録されます(recordToolIO)。

Authorization / Cookie / Cf-Access-Jwt-Assertion ヘッダーは、両方のパスでスパンから削除されます。念のためのバックストップとして、Sentry プロジェクトの Security & Privacy 設定で Prevent Storing of IP Addresses を有効にしてください。

ライセンス

MIT — 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
    -