Skip to main content
Glama
Defilan

Plausible Analytics MCP Server

by Defilan

get-timeseries

Retrieve website traffic trends over time, broken down by day, week, or month. Ideal for trend analysis and building charts.

Instructions

Get traffic trends over time. Returns data points broken down by time interval (day, week, or month). Use this for trend analysis and charts.

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
intervalNoTime granularity for the seriesdate
filtersNoFilters array using Plausible v2 syntax, e.g. [['is', 'event:page', ['/blog*']]]

Implementation Reference

  • The handler function for the 'get-timeseries' tool. It accepts site_id, metrics, date_range, interval (day/week/month), and optional filters. It maps the interval to a dimension key (time:day, time:week, or time:month), queries the Plausible API via client.query(), then formats results as an array of {date, ...metrics} objects and returns JSON.
      async ({ site_id, metrics, date_range, interval, filters }) => {
        const dimensionKey =
          interval === "date"
            ? "time:day"
            : interval === "week"
              ? "time:week"
              : "time:month";
    
        const result = await client.query({
          site_id,
          metrics,
          date_range,
          dimensions: [dimensionKey],
          filters: filters ?? undefined,
        });
    
        // Format as an array of { date, ...metrics }
        const rows = result.results.map((r) => {
          const row: Record<string, unknown> = { date: r.dimensions[0] };
          metrics.forEach((m, i) => {
            row[m] = r.metrics[i];
          });
          return row;
        });
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify({ site_id, date_range, interval, data: rows }, null, 2),
            },
          ],
        };
      }
    );
  • Input schema for get-timeseries: site_id (string), metrics (array using metricsSchema), date_range (using dateRangeSchema), interval (enum: date|week|month, defaults to 'date'), and filters (using filtersSchema).
    {
      site_id: z.string().describe("Domain of the site (e.g. 'example.com')"),
      metrics: metricsSchema,
      date_range: dateRangeSchema,
      interval: z
        .enum(["date", "week", "month"])
        .describe("Time granularity for the series")
        .default("date"),
      filters: filtersSchema,
    },
  • src/index.ts:144-191 (registration)
    Registration of the 'get-timeseries' tool via server.tool(), with description 'Get traffic trends over time...' and the accompanying schema and handler.
    server.tool(
      "get-timeseries",
      "Get traffic trends over time. Returns data points broken down by time interval (day, week, or month). Use this for trend analysis and charts.",
      {
        site_id: z.string().describe("Domain of the site (e.g. 'example.com')"),
        metrics: metricsSchema,
        date_range: dateRangeSchema,
        interval: z
          .enum(["date", "week", "month"])
          .describe("Time granularity for the series")
          .default("date"),
        filters: filtersSchema,
      },
      async ({ site_id, metrics, date_range, interval, filters }) => {
        const dimensionKey =
          interval === "date"
            ? "time:day"
            : interval === "week"
              ? "time:week"
              : "time:month";
    
        const result = await client.query({
          site_id,
          metrics,
          date_range,
          dimensions: [dimensionKey],
          filters: filters ?? undefined,
        });
    
        // Format as an array of { date, ...metrics }
        const rows = result.results.map((r) => {
          const row: Record<string, unknown> = { date: r.dimensions[0] };
          metrics.forEach((m, i) => {
            row[m] = r.metrics[i];
          });
          return row;
        });
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify({ site_id, date_range, interval, data: rows }, null, 2),
            },
          ],
        };
      }
    );
  • Shared schema definitions used by get-timeseries: dateRangeSchema, metricsSchema (enumerating possible metrics like visitors, pageviews, bounce_rate, etc.), and filtersSchema.
    // Shared schema fragments
    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");
    
    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"]);
    
    const filtersSchema = z
      .array(z.any())
      .optional()
      .describe(
        "Filters array using Plausible v2 syntax, e.g. [['is', 'event:page', ['/blog*']]]"
      );
  • The PlausibleClient.query() method used by the get-timeseries handler to make the API call to /api/v2/query.
    async query(params: PlausibleQueryParams): Promise<PlausibleQueryResult> {
      const response = await fetch(`${this.baseUrl}/api/v2/query`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${this.apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(params),
      });
    
      if (!response.ok) {
        const body = await response.text();
        throw new Error(
          `Plausible API error (${response.status}): ${body}`
        );
      }
    
      return response.json() as Promise<PlausibleQueryResult>;
    }
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 states the tool returns data points but does not disclose any behavioral traits like rate limits, authentication needs, or side effects. For a read-only tool, this is minimal but acceptable.

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 sentences, front-loaded with the core action and purpose. No unnecessary words, every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema is provided. The description says it returns data points broken by interval, but does not specify the structure (e.g., timestamps, value fields). For a tool that supports trend charts, this leaves ambiguity. Adequate but not complete.

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 the schema already documents all parameters. The description mentions 'time interval' which maps to the interval parameter but does not add significant meaning beyond the schema. Baseline of 3 is appropriate.

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 it retrieves traffic trends over time, broken by interval, and explicitly recommends it for trend analysis and charts, distinguishing it from sibling tools like get-aggregate-stats or get-breakdown.

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?

It says 'Use this for trend analysis and charts,' giving a clear use case. However, it does not explicitly mention when not to use it or provide alternatives, which would be helpful given sibling tools exist.

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