Skip to main content
Glama
Defilan

Plausible Analytics MCP Server

by Defilan

get-breakdown

Break down website metrics by dimensions like page, source, or country. Get top pages, traffic sources, and visitor locations with filters and custom date ranges.

Instructions

Break down stats by a dimension (e.g. page, source, country, device, browser, OS, UTM tags). Use this for 'top pages', 'traffic sources', 'visitor countries', etc.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
site_idYesDomain of the site (e.g. 'example.com')
metricsNoMetrics to retrieve
date_rangeNoTime period. Use a preset like '30d' or a custom range ['2024-01-01', '2024-01-31']30d
dimensionsYesDimensions to group by. Common: 'event:page' for top pages, 'visit:source' for traffic sources, 'visit:country_name' for geography
filtersNoFilters array using Plausible v2 syntax, e.g. [['is', 'event:page', ['/blog*']]]
limitNoMax number of results to return (default 10)

Implementation Reference

  • src/index.ts:195-273 (registration)
    Registration of the 'get-breakdown' tool via server.tool(), defining its name, description, schema, and handler.
    server.tool(
      "get-breakdown",
      "Break down stats by a dimension (e.g. page, source, country, device, browser, OS, UTM tags). Use this for 'top pages', 'traffic sources', 'visitor countries', etc.",
      {
        site_id: z.string().describe("Domain of the site (e.g. 'example.com')"),
        metrics: metricsSchema,
        date_range: dateRangeSchema,
        dimensions: z
          .array(
            z.enum([
              "event:page",
              "event:hostname",
              "event:goal",
              "visit:source",
              "visit:referrer",
              "visit:utm_source",
              "visit:utm_medium",
              "visit:utm_campaign",
              "visit:utm_content",
              "visit:utm_term",
              "visit:device",
              "visit:browser",
              "visit:browser_version",
              "visit:os",
              "visit:os_version",
              "visit:country",
              "visit:country_name",
              "visit:region",
              "visit:city",
              "visit:entry_page",
              "visit:exit_page",
            ])
          )
          .describe(
            "Dimensions to group by. Common: 'event:page' for top pages, 'visit:source' for traffic sources, 'visit:country_name' for geography"
          ),
        filters: filtersSchema,
        limit: z
          .number()
          .optional()
          .describe("Max number of results to return (default 10)")
          .default(10),
      },
      async ({ site_id, metrics, date_range, dimensions, filters, limit }) => {
        const result = await client.query({
          site_id,
          metrics,
          date_range,
          dimensions,
          filters: filters ?? undefined,
          pagination: { limit },
        });
    
        // Format as rows with dimension labels + metrics
        const rows = result.results.map((r) => {
          const row: Record<string, unknown> = {};
          dimensions.forEach((d, i) => {
            row[d] = r.dimensions[i];
          });
          metrics.forEach((m, i) => {
            row[m] = r.metrics[i];
          });
          return row;
        });
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(
                { site_id, date_range, dimensions, data: rows },
                null,
                2
              ),
            },
          ],
        };
      }
    );
  • Handler function for get-breakdown that calls client.query() with dimensions, transforms results into rows keyed by dimension/metric names, and returns them as JSON text.
    async ({ site_id, metrics, date_range, dimensions, filters, limit }) => {
      const result = await client.query({
        site_id,
        metrics,
        date_range,
        dimensions,
        filters: filters ?? undefined,
        pagination: { limit },
      });
    
      // Format as rows with dimension labels + metrics
      const rows = result.results.map((r) => {
        const row: Record<string, unknown> = {};
        dimensions.forEach((d, i) => {
          row[d] = r.dimensions[i];
        });
        metrics.forEach((m, i) => {
          row[m] = r.metrics[i];
        });
        return row;
      });
    
      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(
              { site_id, date_range, dimensions, data: rows },
              null,
              2
            ),
          },
        ],
      };
    }
  • Input schema for get-breakdown: site_id, metrics (reusing metricsSchema), date_range (reusing dateRangeSchema), dimensions array of allowed dimension values, filters (reusing filtersSchema), and optional limit (default 10).
    {
      site_id: z.string().describe("Domain of the site (e.g. 'example.com')"),
      metrics: metricsSchema,
      date_range: dateRangeSchema,
      dimensions: z
        .array(
          z.enum([
            "event:page",
            "event:hostname",
            "event:goal",
            "visit:source",
            "visit:referrer",
            "visit:utm_source",
            "visit:utm_medium",
            "visit:utm_campaign",
            "visit:utm_content",
            "visit:utm_term",
            "visit:device",
            "visit:browser",
            "visit:browser_version",
            "visit:os",
            "visit:os_version",
            "visit:country",
            "visit:country_name",
            "visit:region",
            "visit:city",
            "visit:entry_page",
            "visit:exit_page",
          ])
        )
        .describe(
          "Dimensions to group by. Common: 'event:page' for top pages, 'visit:source' for traffic sources, 'visit:country_name' for geography"
        ),
      filters: filtersSchema,
      limit: z
        .number()
        .optional()
        .describe("Max number of results to return (default 10)")
        .default(10),
    },
  • Shared dateRangeSchema used by get-breakdown (and other tools) for specifying a time period.
    const dateRangeSchema = z
      .union([
        z.enum(["day", "7d", "30d", "month", "6mo", "12mo", "year", "all"]),
        z.tuple([z.string(), z.string()]).describe("Custom range: [start, end] in YYYY-MM-DD format"),
      ])
      .describe("Time period. Use a preset like '30d' or a custom range ['2024-01-01', '2024-01-31']")
      .default("30d");
  • Shared metricsSchema used by get-breakdown (and other tools) defining valid metrics like visitors, pageviews, bounce_rate, etc.
    const metricsSchema = z
      .array(
        z.enum([
          "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",
        ])
      )
      .describe("Metrics to retrieve")
      .default(["visitors", "pageviews", "bounce_rate", "visit_duration"]);
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It implies a read-only operation (breaking down stats) with no side effects, but does not explicitly state safety or permission requirements. The description adequately conveys the tool is non-destructive but lacks depth.

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?

The description is exceptionally concise: two sentences, no redundant phrases, and front-loaded with the core action. Every word 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?

Given the tool's simplicity and the absence of an output schema, the description is sufficiently complete. It defines the purpose, inputs, and typical use cases. Subtle omissions (e.g., return structure) are minor; the tool is easily understood.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with descriptions for all parameters. The description adds value by naming common dimensions and use cases (e.g., 'event:page' for top pages), which helps users select appropriate parameters beyond what the schema alone provides.

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?

The description clearly states the tool breaks down stats by a dimension, giving concrete examples like page, source, country, device, browser, OS, UTM tags. It explicitly lists use cases ('top pages', 'traffic sources', 'visitor countries'), effectively distinguishing it from siblings like get-aggregate-stats or get-timeseries.

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?

The description provides explicit guidance on when to use the tool ('Use this for...'), with practical examples. However, it does not mention when not to use or explicitly name alternative tools, though sibling names imply different purposes.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Defilan/plausible-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server