---
title: "MCP server"
description: "CanaryLytics ships a hosted Model Context Protocol server at /mcp, so any MCP client can query your analytics with the same numbers the dashboard shows."
last_updated: "2026-07-30"
---

# MCP server

CanaryLytics exposes a [Model Context Protocol](https://modelcontextprotocol.io) server at `https://api.analytics.canarycoders.es/mcp`, speaking streamable HTTP. Point an MCP client at it and the client's model can read your traffic directly: every tool wraps the same query layer as the dashboard and [`/v1/stats`](/docs/api/rest/), so the numbers can never diverge.

All tools are read-only. A read-only key (`cly_read_…`) gets the full surface, which makes it the right key to hand to an agent.

## Connect a client

Any client that supports the streamable HTTP transport with custom headers works. Authentication is the same Bearer header as the REST API. Most JSON-configured clients accept this shape:

```json
{
  "mcpServers": {
    "canarylytics": {
      "url": "https://api.analytics.canarycoders.es/mcp",
      "headers": { "Authorization": "Bearer cly_read_..." }
    }
  }
}
```

<Aside type="note">
  Clients that only support OAuth for remote servers can't send a static
  Bearer header. Use a client with header support, or bridge with a local
  proxy such as `mcp-remote`.
</Aside>

## Authentication and limits

The `Authorization: Bearer <key>` header is required; without a valid key every request answers `401 unauthorized`. Requests share the REST API's read budget of 120 requests per minute per key, so a busy agent can hit `429 rate_limited` with a `Retry-After` header. See [Keys](/docs/keys/) for how live and read-only keys relate.

The server is stateless: no session ids, no server-side conversation state, one self-contained JSON-RPC exchange per request.

## Tools

Stats tools take `window` (`24h`, `7d`, `30d`, `90d`; default `7d`) and `includeBots` (default `false`, bots excluded).

| Tool | Arguments | Returns |
|---|---|---|
| `get_project` | none | Name, trusted domains, retention, caps |
| `get_overview` | window, includeBots | Visitors, pageviews, bounce rate, visit duration |
| `get_timeseries` | window, includeBots | Visitors and pageviews over time |
| `get_top_pages` | window, includeBots | Most-visited paths |
| `get_traffic_sources` | window, includeBots | Search, social, direct, referral breakdown |
| `get_countries` | window, includeBots | Visitors by country |
| `get_devices` | window, includeBots | Browser, OS, device-type breakdowns |
| `get_utm` | window, includeBots | Campaign performance by UTM parameters |
| `get_custom_events` | window, includeBots | Counts per custom event name |
| `get_goals` | window, includeBots | Goal conversions and rates |
| `get_realtime` | none | Visitors active in the last 5 minutes |
| `get_recent_sessions` | window, includeBots, limit, offset | Paginated session list, newest first |
| `get_session` | sessionId | Full event trail of one session |

Results come back as JSON in the tool's text content, in the same shapes as the [REST API](/docs/api/rest/) responses.

## Raw protocol example

The endpoint is plain JSON-RPC 2.0 over HTTP POST, so it is easy to probe without a client:

```bash
curl https://api.analytics.canarycoders.es/mcp \
  -H "Authorization: Bearer cly_read_..." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_overview","arguments":{"window":"30d"}}}'
```

## Related

<CardGrid>
  <LinkCard title="REST API" description="The /v1 endpoints these tools wrap." href="/docs/api/rest/" />
  <LinkCard title="Keys" description="Live vs read-only keys, rotation, and what each may do." href="/docs/keys/" />
</CardGrid>

## Sitemap

- [Docs sitemap](https://analytics.canarycoders.es/docs/sitemap.md)
- [Full documentation as a single file](https://analytics.canarycoders.es/llms-full.txt)
