# MCP server

> Connect Claude Code, Claude Desktop, Codex, Cursor or any MCP client to ClickClacks: the endpoint, read-only keys, every tool with example questions and limits.

- Canonical URL: https://clickclacks.io/docs/mcp
- Section: Integrations & AI
- Last updated: 2026-10-17

Ask your analytics from Claude, Codex, Cursor or any MCP client. ClickClacks runs a read-only, remote MCP server with twelve tools, scoped by a key, and every answer says where its numbers came from.

Want to see it first? The [MCP feature page](https://clickclacks.io/features/mcp) shows a conversation. This page covers setup and every detail. Sending events in, rather than reading them out? That’s the [server API](https://clickclacks.io/docs/server-side.md).

## Endpoint {#endpoint}

```http
POST https://app.clickclacks.io/api/v1/mcp
Authorization: Bearer cka_live_…
Content-Type: application/json
```

- Streamable HTTP, stateless and tools-only. Clients send JSON-RPC with `POST`. There are no sessions and no `Mcp-Session-Id`.
- `GET` and SSE streaming aren’t supported: a `GET` with a valid key gets `405`.
- Supports the `initialize`, `ping`, `tools/list` and `tools/call` methods, and protocol versions from `2024-11-05` to `2025-11-25`.
- Every tool is read-only. Nothing a client does can change your data.

## Keys {#keys}

MCP uses **MCP & analytics keys** (`cka_live_…`). Create one in **Settings › API access**: give it a name such as “Reporting agent”, and copy the secret, which is shown once. **Settings › MCP** shows the server URL, whether you have an active key, and the setup snippets.

- **The key decides the project.** Tool arguments never take an organization or project ID; a call that includes one is refused.
- **Read-only,** with the single scope `analytics:read`.
- **Revoke** a key in Settings › API access to disconnect every client that uses it. Missing, invalid or revoked keys get HTTP `401`.
- Creating and revoking keys needs permission to manage API credentials in your organization.

> **Keep the key out of sight**
>
> Don’t put a key in source control, prompts, logs, screenshots or error reports. Store it in your client’s secret settings, and give each client its own key so you can revoke one without the others.

## Connect a client {#connect}

Pick your client. Every configuration needs the same two things: the URL and the `Authorization` header.

**Claude Code**

```bash title="terminal"
claude mcp add --transport http clickclacks https://app.clickclacks.io/api/v1/mcp --header "Authorization: Bearer cka_..."
```

The command Settings › MCP shows. Replace cka\_... with your key.

**Claude Desktop**

```json title="claude_desktop_config.json"
{
  "mcpServers": {
    "clickclacks": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://app.clickclacks.io/api/v1/mcp",
        "--header",
        "Authorization:${CLICKCLACKS_AUTH}"
      ],
      "env": {
        "CLICKCLACKS_AUTH": "Bearer cka_..."
      }
    }
  }
}
```

Open Settings › Developer › Edit Config. Claude Desktop’s connector settings don’t take a custom Authorization header, so this uses the open-source mcp-remote bridge (it needs Node.js). Restart Claude Desktop afterwards.

**Codex**

```toml title="~/.codex/config.toml"
# ~/.codex/config.toml
[mcp_servers.clickclacks]
url = "https://app.clickclacks.io/api/v1/mcp"
bearer_token_env_var = "CLICKCLACKS_MCP_KEY"
```

Export CLICKCLACKS_MCP_KEY with your key in the environment Codex runs in. Needs a Codex version with Streamable HTTP servers.

**Cursor**

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "clickclacks": {
      "url": "https://app.clickclacks.io/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer cka_..."
      }
    }
  }
}
```

In your project’s .cursor/mcp.json, or ~/.cursor/mcp.json for every project.

**Other clients**

```json title="mcp config"
{
  "mcpServers": {
    "clickclacks": {
      "type": "http",
      "url": "https://app.clickclacks.io/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer cka_..."
      }
    }
  }
}
```

The JSON Settings › MCP shows. Key names vary a little between clients; the URL, the HTTP transport and the header are what matter.

To check it worked, ask the client to list its ClickClacks tools, or ask “Which ClickClacks sources are sending events?”, which calls `list_sources`.

## Tools {#tools}

Nine read-only tools for reports, and three more [for companies](https://clickclacks.io/docs/mcp.md#groups). Every one also takes optional `start` and `end` dates. Schemas are strict: an argument that isn’t listed is refused.

| Tool | Arguments besides dates | Returns |
| --- | --- | --- |
| `overview` | Report filters. | Overview KPIs and the visitors series. |
| `list_funnels` | Dates only. | Saved funnels: steps, ordering, count mode and window, with summary metrics when available. |
| `funnel_results` | `funnelId` (required); `breakdown`, an event property. | One saved funnel run in its saved ordered or unordered, people or events mode, optionally split by the property. |
| `retention` | Report filters; `interval`: `daily`, `weekly` (default) or `monthly`. | The saved retention definition and its cohort grid. |
| `list_events` | Report filters; `search`, an exact event name; `cursor`, for the next page. | Event rows, a page at a time: pass the returned cursor for the next page. |
| `event_breakdowns` | Report filters; `dimension`: `pages`, `referrers`, `countries` or `events`. | Top pages, referrers, countries and event names, or just one of them. |
| `list_segments` | Dates only. | Saved segments, with people counts when available (otherwise `null`). |
| `list_sources` | Dates only. | Sources with their event and people counts for the window, and health from each one’s last event. |
| `friction_findings` | `state`: `happening` (default), `stopped` or `muted`. | Friction findings in that state last seen in the window. `people7d` is the finding’s rolling seven-day count. |

## Tools for companies {#groups}

A group is an account your events are stamped with: a company, a workspace, a team. Each project names its own types, so start with `list_group_types`. [Companies and groups](https://clickclacks.io/docs/groups.md) explains the model.

| Tool | Arguments besides dates | Returns |
| --- | --- | --- |
| `list_group_types` | Dates only. | The group types the project shows, in order: `groupType` (the key, such as `company`), `labelSingular` and `labelPlural`. Hidden types are left out. Call it first. |
| `list_groups` | `groupType` (required); `search`, matched against the ID and the name; `sort`: `last_seen` (default), `events`, `members` or `name`; `cursor`, for the next page. | A page of 50 groups of that type: `groupId`, `name`, `firstSeen`, `lastSeen`, `events` and `activeMembers` in the window, and `totalEvents`. `total` counts every match, and `page.nextCursor` continues the list. |
| `group_profile` | `groupType` and `groupId` (both required). | One group: its name, first and last seen, total events, `traits`, `traitsWithheld` and `traitsUpdatedAt`; `metrics` and `previousMetrics` (`activeMembers`, `events`, `activeDays` for the window and the equally long window before it); and `members`. |
| `event_breakdowns` | Its usual arguments, plus `count_by`, a `groupType`. | The same four lists, counting unique groups of that type instead of people. `countBy` names the type. Each row has `label`, `count` and `unstampedShare`; event rows also have `eventCount`. |
| `funnel_results` | Its usual arguments, plus `count_by`, a `groupType`. | The saved funnel counting the groups of that type instead of people. `countBy` names the type and `counted` is its plural, such as `companies`. Every count that reads `people` on a people funnel reads `count`, the count mode reads `unique_companies`, and `results.unstampedShare` is the share of the funnel’s step events that carry no group of the type. |
| `retention` | Its usual arguments, plus `count_by`, a `groupType`. | The retention grid with cohorts of groups of that type instead of people. `countBy` and `counted` name the type, `totalPeople` reads `totalCount`, and `results.unstampedShare` is the share of the report’s events that carry no group of the type. |

What these tools return is about companies, never about a person:

- **Traits.** Secrets are dropped, as everywhere in ClickClacks. So is any trait that holds personal data: one named like `email`, `phone`, `ip`, `address` or `password`, or whose value reads as an email address or a phone number. Those traits are named in `traitsWithheld` and their values aren’t returned, even when the source is allowed to store them. A `name` that reads as an email address or a phone number comes back as `null`.
- **Members are never identified.** `members.active` is how many people had events for the group in the window. `members.top` lists up to ten of the most active as `rank`, `events` and `shareOfEvents`, with no person ID, name, email or session. No argument turns identifiers on.
- A `groupType` the project doesn’t show, because it is unknown or hidden in Lexicon, is refused with a pointer to `list_group_types`.

With `count_by` on `event_breakdowns`:

- Only events stamped with a group of that type count. `unstampedShare` is the share, from 0 to 1, of the row’s events that carry none and are left out.
- Pages, referrers and countries are read from pageviews. A group sits in every row it has a pageview for, so rows can add up to more than the number of groups.
- Each list holds the five values with the most events, ordered by unique groups.
- Report filters apply as usual, and can name a group: `g:company` is the group’s ID, and `g:company.plan` a trait of the event’s group, as in `{ "property": "g:company.plan", "values": ["pro"] }`.

With `count_by` on `funnel_results` and `retention`:

- A group enters a funnel at its first step-one event in the window, from any member, and completes each later step on the earliest matching event stamped with that group, again from any member, inside the conversion window. So when one person signs up and a colleague sends the first invite, the company converts.
- A retention cohort holds the groups whose first starting event falls in the period. A group is back in a later period when any of its members’ events is.
- A funnel counted by a group type is never counted by session: sessions belong to people, so a funnel saved in events mode is read as unique groups.
- Left out, the report counts what it was saved to count: a funnel or retention definition saved with Count by Companies answers in companies. `list_funnels` does the same for each funnel it lists, with `counted` on the definition and on its row.
- A saved Count by whose type the project no longer shows, because it is hidden in Lexicon, is read counting people.

- With an [agency key](https://clickclacks.io/docs/mcp.md#agency-keys), these tools take the `project` argument like every other tool.
- If the project’s group data can’t be read, the answer is `degraded` with a reason and `data: null`.

## Questions to ask {#ask}

You ask in plain language; the client picks the tools. Some that work well, and the tools they use:

| Ask | Tools |
| --- | --- |
| “Which step of our signup funnel loses the most people this month? Show the numbers.” | `list_funnels, funnel_results` |
| “Compare weekly retention for the last eight weeks. Is any cohort clearly weaker?” | `retention` |
| “What are our top referrers this month, and how does that compare with last month?” | `event_breakdowns` |
| “Is every source still sending? Which one went quiet?” | `list_sources` |
| “What friction is happening right now, and on which pages?” | `friction_findings, event_breakdowns` |
| “How many visitors did we have yesterday, only on the pro plan?” | `overview with a property filter` |
| “Which companies were most active this month, and which of them are on the Pro plan?” | `list_group_types, list_groups, group_profile` |
| “Which events do the most companies use, and how many events carry no company?” | `event_breakdowns with count_by` |
| “What share of companies that start a trial reach their first invite?” | `list_funnels, funnel_results with count_by` |

Adding “cite the provenance” or “stop if the data is degraded” to a prompt keeps an agent honest about what it actually read.

## Dates and filters {#dates-and-filters}

- `start` and `end` are `YYYY-MM-DD` dates. Send both or neither. The range is inclusive, `end` can’t be before `start`, and a window is at most 3,660 days.
- Without dates, a tool reads the last 30 days including today, in the project’s reporting time zone (Settings › General).

`overview`, `retention`, `list_events` and `event_breakdowns` also take report filters: a `sourceId`, a saved `segmentId`, and up to 20 `propertyFilters`, each with up to 100 values.

- `operator` is `is` (the default), `is_not`, `contains`, `not_contains`, `is_set`, `is_not_set`, `gt`, `lt` or `between`. `is_set` and `is_not_set` take no values, `gt` and `lt` one, and `between` two (inclusive). Bounds are numbers or `YYYY-MM-DD` dates. `contains` ignores case.
- `applyTo` is `matching-events` (only events that match count) or `people-ever-matched` (everything from people who ever matched).
- Every ID is resolved inside the key’s project.

```json title="arguments"
{
  "start": "2026-08-01",
  "end": "2026-08-31",
  "sourceId": "src_…",
  "segmentId": "seg_…",
  "propertyFilters": [
    { "property": "plan", "operator": "is", "values": ["pro"], "applyTo": "matching-events" },
    { "property": "amount_cents", "operator": "gt", "values": ["5000"], "applyTo": "matching-events" }
  ]
}
```

## Provenance {#provenance}

Every successful call returns its answer twice: `structuredContent` with `data` and `provenance` for programs, and a text version, with the same provenance, for the model reading it.

```json title="structuredContent"
{
  "data": { … },
  "provenance": {
    "window": { "start": "2026-08-25", "end": "2026-08-31", "timeZone": "Europe/London" },
    "filters": { "interval": "weekly" },
    "scope": {
      "organizationId": "org_…",
      "projectId": "prj_…",
      "label": "organization org_… / project prj_…"
    },
    "freshness": { "dataAsOf": "2026-08-31T21:14:03.000Z", "cache": "not-exposed" },
    "degraded": false,
    "reason": null,
    "definitionHash": "…"
  }
}
```

- `window` is the dates that were read, with the `timeZone` when the project doesn’t report in UTC.
- `filters` records every accepted argument other than the dates, including defaults such as `interval`.
- `scope` always comes from the verified key.
- `freshness.dataAsOf` is the newest timestamp in the answer. `cache` is `not-exposed`: ClickClacks doesn’t guess whether a result was cached.
- `definitionHash` identifies the saved definition behind `list_funnels`, `funnel_results`, `retention` and `list_segments`.

When a report can’t be read, `degraded` is `true`, `reason` says what’s missing, and the numbers come back as `null`. Saved definitions, such as a funnel’s steps, can still come back. Treat a degraded answer as unknown, never as zero.

## Agency keys {#agency-keys}

An agency that manages several client organizations can use one key for all of them. Agency owners and admins create **agency keys** (`cka_agency_…`) in **Agency settings › MCP keys**. Agency keys are part of the Pro plan.

- **It reads what its creator can open, right now.** The key stores no project list; access is checked on every call. Remove the creator from a project and the key stops reading it; remove them from the agency and the key stops working.
- **Every tool takes a `project` argument,** a slug or ID as `list_projects` returns it. It’s required unless the key can read exactly one project.
- **`list_projects`** lists the projects the key can read, with slug, ID, time zone, source count and events this month.
- **`compare_projects`** compares `visitors` (the default) or `events` across projects over the same calendar dates, each in its own time zone, sorted high to low. A total is given for events only, since visitors don’t add up across projects.
- **Every call is logged** per project it read, and the key list shows each key’s calls over the last 30 days. Any agency owner or admin can revoke any agency key.

## Rate limits and errors {#limits}

- Each key can make about **120 requests a minute**. The limit is approximate: it’s counted per server instance, not globally.
- Responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`.
- Over the limit, a call gets the JSON-RPC error `-32029` with `retryAfterSeconds`, and a `Retry-After` header.
- Bad arguments get `-32602`, an unknown method `-32601`, and a report that can’t run `-32603`. An agency key whose agency isn’t on a plan with agency keys gets `-32003`.
- Responses are sent with `Cache-Control: private, no-store`.

## Next steps {#next}

- [Glossary](https://clickclacks.io/docs/glossary.md): what funnels, segments and friction findings are.
- [MCP feature page](https://clickclacks.io/features/mcp): a conversation, start to finish.
- [Retention and deletion](https://clickclacks.io/docs/data.md): how long the data an agent reads is kept.
