---
url: 'https://docs.kitbase.dev/sdks/mcp.md'
description: >-
  Connect your AI assistant to Kitbase analytics over the hosted MCP server —
  nothing to install. Query visitors, events, users, sessions, funnels,
  retention, AI visibility, and bot traffic — and manage AI-visibility tracking,
  site audits, webhooks, workflows, and your team — from claude.ai, Claude Code,
  Cursor, or any MCP-compatible client.
---

# MCP Server

Kitbase speaks [MCP (Model Context Protocol)](https://modelcontextprotocol.io). Connect it to claude.ai, Claude Code, Cursor, or any MCP-compatible client and ask questions about your traffic, events, users, sessions, AI visibility, and bot traffic in natural language. Over an OAuth connection it also exposes [management tools](#management-tools) — add AI-visibility competitors and prompts, start scans and site audits, build workflows, manage webhooks and team members.

The server is **built into the Kitbase API** — there is nothing to install or run.

```
https://api.kitbase.dev/mcp
```

Self-hosted deployments serve the same endpoint at `https://<your-host>/api/mcp`.

::: tip No Dashboard Required
Once connected, your AI assistant can answer questions like "How many visitors are on the site right now?" or "Which domains does ChatGPT cite instead of us?" by querying Kitbase directly — and, on an OAuth connection, act on requests like "Add Vercel as a competitor we track in AI answers."
:::

## Authentication

The server accepts two kinds of credentials. Both are scoped to exactly **one project** — tools default to it, so no organization parameter is ever needed (project-scoped tools do accept an optional `projectId` to target another project in the same organization).

| Method | Best for | How |
|--------|----------|-----|
| **OAuth** | claude.ai / Claude Desktop connectors | Add the server URL as a custom connector; a browser window asks you to sign in and pick the org + project to grant access to |
| **Private API key** | Cursor, headless clients | Send `Authorization: Bearer sk_kitbase_...` (or `X-API-Key`) with each request |

Read tools work with either credential. **[Management tools](#management-tools) work only over OAuth**: each one checks the signed-in user's live permission (the same permission the dashboard enforces, capped by what the connection was granted). A private API key carries no user to check, so key-authenticated calls to management tools are rejected.

::: warning
Private API keys (`sk_kitbase_`) are secret keys — not the public SDK token used in browser SDKs. Generate one in your Kitbase dashboard under **Project Settings > API Keys**.
:::

### Revoking a connection

Revoke an OAuth connection from the dashboard under **Account Settings → MCP Connections**. Revocation takes effect on the connection's next token refresh, and the connection cannot renew itself afterwards.

Refresh tokens rotate on every use and are single-use. If a rotated token is ever presented again — the signature of a copied credential — the whole connection is revoked immediately rather than renewed, so a leaked token buys at most one refresh before both parties are cut off. Nothing is required of you for that to happen.

## Client Setup

### claude.ai (web / desktop)

1. Go to **Settings > Connectors > Add custom connector**
2. Enter `https://api.kitbase.dev/mcp`
3. Complete the sign-in flow and choose the organization and project to connect

### Claude Code

```bash
claude mcp add --transport http kitbase https://api.kitbase.dev/mcp \
  --header "Authorization: Bearer sk_kitbase_your_key_here"
```

### Cursor

Add to your Cursor MCP settings (`.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "kitbase": {
      "url": "https://api.kitbase.dev/mcp",
      "headers": {
        "Authorization": "Bearer sk_kitbase_your_key_here"
      }
    }
  }
}
```

### Other MCP Clients

The server speaks the standard MCP Streamable HTTP transport. Any client that supports remote MCP servers works — point it at the URL and supply either credential.

## Discovery

Clients and agents that look for MCP servers automatically, rather than being handed a URL, find Kitbase through the standard discovery documents. None of them require authentication.

| Document | URL | Media type |
|----------|-----|------------|
| **Server card** — name, version, description, transport, how to authenticate ([SEP-2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127)) | `https://api.kitbase.dev/mcp/server-card` | `application/mcp-server-card+json` |
| **AI catalog** — the domain-level index that points at the server card | `https://kitbase.dev/.well-known/ai-catalog.json` and `https://api.kitbase.dev/.well-known/ai-catalog.json` | `application/ai-catalog+json` |
| **Protected-resource metadata** — which authorization server protects `/mcp` ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728)) | `https://api.kitbase.dev/.well-known/oauth-protected-resource/mcp` | `application/json` |
| **Authorization-server metadata** — authorize/token/revocation endpoints, PKCE, `scopes_supported` ([RFC 8414](https://www.rfc-editor.org/rfc/rfc8414)) | `https://api.kitbase.dev/.well-known/oauth-authorization-server` | `application/json` |

An unauthenticated request to `/mcp` is answered with `401` and a `WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp"` header, which is how an OAuth-aware client finds its way to the consent flow without any configuration. The server card carries the same facts in one place:

```bash
curl -s -H "Accept: application/mcp-server-card+json" https://api.kitbase.dev/mcp/server-card | jq '.remotes[0]'
```

The card deliberately does not list tools — they depend on what the connection was granted, so clients read them at runtime with `tools/list`. The REST side of the same platform is described by the [OpenAPI spec](/api-reference#openapi-specification), which also documents `POST /mcp` with its OAuth security scheme.

## Available Tools

The server exposes **180 tools**. Most are reads for querying analytics, reports and configuration; the rest are [management tools](#management-tools) that change configuration — AI-visibility setup and runs, site audits, webhooks, projects, team, and [workflows](#workflows). Analytics data itself (events, sessions, visitors) can never be modified or deleted through MCP.

The tables below cover the tools you are most likely to reach for. Your client's own tool list is the complete one — ask your assistant to list the Kitbase tools if you want to see everything it has.

Unless noted otherwise, every tool accepts the standard date parameters described in [Date Filtering](#date-filtering): `preset`, `from` and `to`.

### Web Analytics

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_web_summary` | Summary KPIs — visitors, pageviews, bounce rate, average duration | `filters` |
| `get_web_timeline` | Visitors/pageviews as a time series | `interval` (`hour`/`day`/`week`/`month`), `filters` |
| `get_web_breakdown` | Break down by dimension (`COUNTRY`, `BROWSER`, `OS`, `DEVICE`, `PATH`, `REFERRER`, …) | `dimension` (required), `page`, `size`, `filters` |

All three also take the [period comparison](#period-comparison) parameters, so "how does this week compare to last?" is one call rather than two.

`dimension` is a closed set, and the tool schema lists it, so an assistant picks from
it rather than guessing. There is no `page` dimension — pages are `PATH`, `ENTRY_PAGE`,
`EXIT_PAGE` and `TOP_PAGE`. The full set:

`DEVICE`, `BROWSER`, `BROWSER_VERSION`, `OS`, `OS_VERSION`, `BRAND`, `MODEL`,
`COUNTRY`, `REGION`, `CITY`, `UTM_SOURCE`, `UTM_MEDIUM`, `UTM_CAMPAIGN`, `PATH`,
`ENTRY_PAGE`, `EXIT_PAGE`, `TOP_PAGE`, `REFERRER`, `TOP_REFERRER`, `OUTBOUND_LINK`,
`CUSTOM_EVENTS`, `EVENT_NAME`, `USER`.

The REST API takes the same dimensions in lower case (`top_page`); over MCP either
spelling is accepted.

### Custom Events

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_events` | List raw tracked events | `event`, `channel`, `userId`, `filters`, `page`, `size`, `sort` (`desc` default, `asc`) |
| `get_event_stats` | Grouped event counts | `groupBy` (`event` default, `channel`, `user`), `channel` |
| `get_event_timeline` | Event counts over time | `interval`, `event` |
| `get_event_breakdown` | Break events down by dimension (`EVENT_NAME`, `USER`, `COUNTRY`, …) | `dimension` (required), `limit` |

### Users

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_users` | List/search aggregated user analytics | `type` (`all`/`identified`/`anonymous`), `search`, `filters`, `page`, `size` |
| `get_user_summary` | Comprehensive single-user summary | `userId` (required) |
| `get_user_activity` | Daily activity heatmap for one user | `userId` (required), `months` (default 4); no date params |
| `get_user_events` | Paginated event timeline for one user | `userId` (required), `page`, `size`; no date params |

### Sessions

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_sessions` | List aggregated sessions | `search`, `filters`, `page`, `size` |
| `get_session_detail` | Single session details (metadata only) | `sessionId` (required); no date params |
| `get_session_events` | Events inside one session | `sessionId` (required), `page`, `size`; no date params |

### Funnels & Journeys

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `analyze_funnel` | Ad-hoc funnel: conversion + drop-off per step | `steps` (required — ordered list of `{type, value, name?, filters?}`), `analysisMode` (`session`/`user`) |
| `analyze_journey` | Most common multi-step event sequences | `steps` (count, default 3), `limit` (default 100) |

### Frustration Signals

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_frustration_signals` | Rage clicks and dead clicks, with top pages/elements | `limit` |

### Retention

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_retention` | Cohort retention: visitors grouped by the week/month of their first visit, with the share still active in each following period | `interval` (`week` default, or `month`) |

Cohorts are anchored to each visitor's **first visit ever**, not the first visit inside the requested window, so long-standing visitors are never counted as new. Offset 0 is the cohort itself (100%). The date range selects which cohorts are returned.

### Realtime

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_realtime_visitors` | Distinct visitors active right now | `windowMinutes` (1–60, default 5); no date params |

### AI Visibility

How often your brand appears in AI search answers (ChatGPT, Gemini, Perplexity, Claude), which domains those answers cite, and how you stack up against competitors.

Most of these tools accept a `providers` filter — a list of platforms such as `["CHATGPT", "GEMINI"]`, where omitting it means every platform — and a `jobs` parameter, how many of the most recent scan jobs to aggregate when no date window is given (default 10). Six of them also take the [period comparison](#period-comparison) parameters, which need a date window rather than a `jobs` count.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_ai_brands` | Configured brands (yours + competitors) | none |
| `list_ai_prompts` | Prompts tracked against AI platforms | none |
| `list_ai_topics` | Prompt topics (thematic groupings of prompts) | `includeArchived` (default false); no date params |
| `get_ai_visibility_series` | Visibility score over time | `providers`, `limit` (default 30) |
| `get_ai_visibility_domains` | Domains AI answers cite, ranked by citation share | `providers`, `jobs`, `limit` (default 25) |
| `get_ai_cited_pages` | The specific pages (URLs, with titles) AI answers cite, ranked by citation count | `providers`, `mentioningBrand`, `topicIds`, `jobs`, `limit` (default 25) |
| `get_ai_competitors` | Your brand vs competitors (mentions, sentiment) | `providers`, `jobs`, `limit` |
| `get_ai_share_of_voice` | Your share of all brand mentions over time | `providers`, `jobs` |
| `get_ai_prompts_breakdown` | Per-prompt: did the brand appear in each provider's answer | `jobs` |

### Bot & AI-Crawler Traffic

Which bots and AI crawlers (GPTBot, ClaudeBot, PerplexityBot, Googlebot, …) hit your site, from where, and what they read.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_bot_timeline` | Bot requests over time, grouped by vendor | `interval` |
| `get_bot_countries` | Per-country bot traffic (requests + distinct bots) | `page`, `size` (default 8) |
| `get_top_bots` | Top bots ranked by request count | `size` (default 10) |
| `get_bot_top_paths` | Pages most requested by bots | `size` (default 10), `vendor` (e.g. `GPTBot`) |

All four also take the [period comparison](#period-comparison) parameters, which is how "is ChatGPT crawling us more than last month?" gets answered in one call.

### Backlinks

Referring domains detected from the project's real traffic — search engines, social networks, AI assistants, and self-referrals are excluded. These tools use a `days` window instead of the standard date parameters.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_backlinks` | Referring domains with referred-visit counts | `status` (`all` default, `active`, `ignored`, `lost`, `untapped`), `sort` (`first_seen` default, `sessions`), `q` (domain search), `days` (default 30), `page`, `size` |
| `get_backlink_detail` | One referring domain: daily visit timeline, landing pages, and the linking-page URLs | `domain` (required), `days` (default 30) |
| `get_backlink_reclamation` | Dead pages that still receive referral traffic — backlinks recoverable with a redirect | `days` (default 90) |
| `get_backlink_opportunities` | Domains AI answers cite where your brand is absent — sites worth earning a link from; requires AI Visibility to be configured | `jobs`, `limit` (default 25) |

### Content Recommendations

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `get_content_recommendations` | AI-generated create/update-page recommendations, each with the analytics, AI-visibility, and SERP facts it is grounded in | `status` (`SUGGESTED`/`ACCEPTED`/`DONE`/`DISMISSED`, default all), `page`, `size`; no date params |

### Workflow Reads

Workflows are the automations built on the dashboard's workflow canvas. These reads require `workflow.view`; the tools that change a workflow are under [Workflows](#workflows). None accept date parameters.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_workflows` | The project's workflows with their status (`DRAFT`, `ACTIVE`, `PAUSED`, `ARCHIVED`); graphs omitted | `page`, `size` (default 20) |
| `get_workflow` | One workflow with its full graph — every step, its config, and the connections between them | `workflowId` (required) |
| `list_workflow_step_types` | The catalog of step types: what each does, the payload types its inputs accept and its outputs emit, whether it costs AI credits, and every config key it takes | none |
| `list_workflow_runs` | A workflow's runs, most recent first, with status, trigger, cost and any error | `workflowId` (required), `page`, `size` |
| `get_workflow_run` | One run in detail: every step's status, what arrived on its inputs, what it emitted, and why it failed or was skipped | `workflowId`, `runId` (required) |

### Site Audit Reads

Everything a [site audit](/site-audit) produced. All of them require `siteaudit.view`, and none accept date parameters.

Each report tool takes an optional `auditId` and falls back to the project's most recent audit **of its own website**, so an assistant that has just been told about an audit does not need a second call to name it. An audit can be pointed at any address, and a competitor's report is never what an unqualified question means — so reading one means naming its id, which `list_site_audits` returns. There is no living page inventory behind these — every answer comes from one run's snapshot, so two figures read together were measured by the same crawl.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `list_site_audits` | Recent audits, most recent first | `limit` (default 10) |
| `get_site_audit_worklist` | The whole audit as one table to work through, worst first: every failed rule with why it matters, how to fix it, how many pages carry it, and the first few of those URLs. `list_site_audit_issues` plus the first page of `list_site_audit_issue_pages` for every rule, in one call — the place to start when the task is "fix what the audit found" | `auditId`, `limit` (default 20, max 100), `examplesPerIssue` (default 3, max 10) |
| `get_latest_site_audit` | The most recent audit of the project's **own** website: weighted AI-readiness score, the 16-item scorecard, headline crawl statistics | `includeExternal` (default false) |
| `get_site_audit` | One audit in full, by id: which site it ran against and whether that is your own, the score and its category scores, the scorecard's verdicts, which checks the run was narrowed to, headline statistics | `auditId` |
| `get_site_audit_summary` | One audit's headline statistics: pages crawled, issues by severity, average response time, and how many pages came back blocked, broken or erroring | `auditId` |
| `list_site_audit_issues` | What the audit found, one row per rule, worst first — why it matters, how to fix it, how many pages it affects | `auditId` |
| `list_site_audit_checks` | Every check the audit graded, worst first, **including the ones that hold up** — each with what this run measured, how to fix it, and the pages it names. The issue tools are issues-only, so this is the only way to tell "passed" from "never ran", and the only place that reports how many checks ran at all | `auditId` |
| `list_site_audit_performance` | Lighthouse measurements, one row per page and device: performance, accessibility, SEO and best-practices scores plus LCP, FCP, CLS, TBT, TTFB and Speed Index | `auditId`, `device`, `band`, `failuresOnly`, `sort`, `q`, `page`, `size` |
| `get_site_audit_performance_page` | One measurement with the audits it failed, worst first — what Lighthouse objected to (render-blocking resources, unsized images, long main-thread work), not just the score it ended on | `resultId` (required), `auditId` |
| `list_site_audit_duplicates` | Pages that say the same thing, clustered by meaning and tightest first. `NEAR_DUPLICATE` is two pages that are nearly the same page — the ones worth merging or canonicalising; `RELATED` is normal for a site about one subject | `auditId` |
| `list_site_audit_issue_pages` | The pages behind one issue. Pass **both** `source` and `findingId` exactly as `list_site_audit_issues` returned them — a rule id is only unique within its source | `source` and `findingId` (both required), `auditId`, `page`, `size` |
| `list_site_audit_crawled_pages` | The page-by-page snapshot of one audit's crawl: status, title, word count, images missing alt text, response time, crawl depth, issue count | `auditId`, `status`, `sort`, `q`, `page`, `size` |
| `list_site_pages` | The same page list for the project's **latest completed audit of its own website** — the shortcut for "what does our site consist of right now" | `page`, `size`, `sort`, `filter`, `q` |
| `get_site_audit_page` | Everything one audit knows about one page: what the crawl measured, which checks it failed and what each found, how a model read it, and the nearest pages by meaning. Address it by `url` or by the `pageId` from `list_site_audit_crawled_pages` | `url` **or** `pageId` (one required), `auditId` |
| `get_page_content` | The same page detail for the project's own site, addressed the way a person names it — by path | `path` (required), e.g. `/pricing` |

`sort` takes `URL`, `STATUS`, `SPEED`, `WORDS` or `DEPTH` (default `URL`). The status filter — `status` on `list_site_audit_crawled_pages`, `filter` on `list_site_pages` — takes `ALL`, `OK`, `REDIRECT`, `BROKEN`, `SERVER_ERROR`, `BLOCKED` or `MISSING` (default `ALL`). `q` is a case-insensitive substring match on the URL.

On `list_site_audit_performance`, `device` takes `ALL`, `MOBILE` or `DESKTOP`, `band` takes `ALL`, `GOOD`, `NEEDS_WORK` or `POOR`, and `sort` takes `PERFORMANCE`, `URL`, `LCP`, `CLS` or `TTFB` (default `PERFORMANCE`, worst first).

Two empty answers mean "did not run" rather than "nothing found": no Lighthouse rows means no measurement happened — no provider configured, or the audit predates it — not that the pages are fast, and `list_site_audit_duplicates` reporting `pagesCompared: 0` means the similarity pass never ran, not that no page repeats another.

::: warning A project with no finished audit has no pages
`list_site_pages` and `get_page_content` read stored rows only. If the project has never completed an audit of its own website there is nothing to read — start one with `start_site_audit` first. A path nobody crawled is reported as not found rather than as an empty page, because "we looked and there is nothing there" and "we never looked" are different answers.
:::

### Reviewing a Draft Before You Publish

Every tool above reads a crawl, so the earliest they can see an article is after it is live — and by then the expensive mistake is already quotable. `audit_draft` takes the file instead: your agent reads the draft off disk, passes the text, and gets back the checks the site audit runs on a published page, plus the one only a pre-publish review can make — whether the draft contradicts something your site already says.

| Tool | Description | Extra parameters |
|------|-------------|------------------|
| `audit_draft` | Review an unpublished article: the prose checks the page report runs, plus the figures, structure words and titles it would contradict on the live site | `content` (required), `format` (`markdown`/`html`, detected when omitted), `title`, `metaDescription`, `path`, `auditId` |

Requires `siteaudit.view`. Nothing is stored: the draft is read, graded and forgotten. The dashboard runs the same review on the same endpoint — Website Scan → [Review a draft](/site-audit#reviewing-an-article-before-it-is-published) — for the times nobody is at a terminal.

```
"Audit blog/ai-visibility.md before I publish it"
```

**Pass the text, not a path.** The server cannot read your disk — the client opens the file. Markdown front matter is read for `title` and `description`, so a normal blog file needs nothing else; `path` is worth passing when you know where the piece will live, because it enables the check against the page already at that address.

The response answers "publish or not" first and gives the reasons under it:

| Field | What it says |
|-------|--------------|
| `verdict` | `BLOCK` only when the draft **contradicts** the published site. A thin description or a missing opening answer is a fix, not a contradiction, and comes back `WARN`; `CLEAR` means nothing this review can see would be contradicted |
| `summary` | One line: contradictions, what to fix, what is worth a look, and how many checks hold up |
| `checks[]` | Every graded line — passes included — with `status` (`FAIL`/`WARNING`/`PASS`/`INFO`), what was measured, and what to do about it |
| `comparedSite` | The audit the draft was read against and how many pages that comparison saw. `null` when the project has no completed audit of its own site |
| `comparisonNote` | Why the comparison is missing or partial, when it is |
| `onlyAfterPublishing` | The checks this review deliberately does not answer |

**What it grades depends on what you send.** A markdown file is the prose you own, so it is graded on the opening answer, question headings, statistics, heading order, lists and tables, length, title, description and image alt text. Structured data, social tags and the byline belong to the template that renders it — asked only of an HTML draft, which is the whole page. Internal versus outbound links need to know which host is yours, so they are graded only when there is an audit supplying the domain.

::: warning A CLEAR draft is not a clean audit
`onlyAfterPublishing` exists because a file cannot answer everything: HTTP status, the canonical tag, sitemap membership, whether AI crawlers are allowed in, and whether anyone has cited the page are all properties of a live URL. Review the draft here, then audit the page once it ships.
:::

Without a completed audit of your own website the contradiction half cannot run — the prose checks still do, and `comparisonNote` says exactly that. `start_site_audit` fixes it.

### Configuration Reads

Unlike the analytics reads above, these expose org/project configuration, so each requires the same view permission its dashboard page enforces. None accept date parameters.

| Tool | Description | Permission | Extra parameters |
|------|-------------|------------|------------------|
| `list_projects` | All projects in the connected organization | `project.read` | none |
| `list_webhooks` | The organization's webhooks | `webhook.view` | `page`, `size` (default 50) |

## Management Tools

These tools change configuration — never analytics data. They work **only over an OAuth connection** (see [Authentication](#authentication)): each checks the listed permission on the signed-in user and fails without it; private-API-key calls are rejected.

### AI Visibility Setup & Runs

All of these require the `aivisibility.manage` permission.

| Tool | Description | Parameters |
|------|-------------|------------|
| `add_ai_brand` | Add a brand — your own (`isSelf: true`, only one allowed) or a competitor | `name` (required), `primaryDomain`, `isSelf`, `aliases`, `excludedTopicIds` |
| `add_ai_competitor` | Add a competitor brand — shorthand for `add_ai_brand` without `isSelf` | `name` (required), `primaryDomain`, `aliases`, `excludedTopicIds` |
| `update_ai_brand` | Edit a brand's name, domain, aliases or topic scope, or (de)activate it | `brandId`, `name` (required), `primaryDomain`, `active`, `aliases`, `excludedTopicIds` |
| `delete_ai_brand` | Delete a brand | `brandId` (required) |

**Scoping a competitor to certain topics.** `excludedTopicIds` takes topic ids from `list_ai_topics` and mutes that competitor for them: it is treated as absent from every answer on those topics, so they leave its visibility rate and its share of voice, and it drops out of the leaderboard and charts whenever results are filtered to one. Resolve the topic name to an id with `list_ai_topics` first, and read a brand's current scope from `list_ai_brands`.

The set is replaced wholesale, so send the full list. On `update_ai_brand`, **omitting the parameter leaves the current scope untouched** — unlike `aliases`, which clears when omitted — so editing a name can't wipe a scope set in the dashboard. Send an empty array to deliberately track the competitor everywhere again. It is rejected for your own brand.
| `add_ai_prompt` | Add a prompt tracked across answer platforms | `text` (required), `locale` (default `en-US`), `topicId`, `personaId` |
| `update_ai_prompt` | Edit a prompt's text or locale, or (de)activate it. It cannot change the topic or persona — use the two tools below | `promptId`, `text` (required), `locale`, `active` |
| `move_ai_prompts_to_topic` | File prompts under one topic, or take them out of every topic by omitting `topicId`. Pass one id to move one prompt; all or nothing across the list | `promptIds` (required, ≤500), `topicId` |
| `assign_ai_prompt_persona` | Assign one persona to prompts, or unassign them by omitting `personaId`. Never touches their topic or text | `promptIds` (required, ≤500), `personaId` |
| `delete_ai_prompt` | Deactivate (remove) a prompt | `promptId` (required) |
| `add_ai_topic` | Create a prompt topic (thematic grouping) | `name` (required) |
| `update_ai_topic` | Rename a topic | `topicId`, `name` (required) |
| `delete_ai_topic` | Archive a topic — its prompts become uncategorized; historical runs keep the topic | `topicId` (required) |
| `suggest_ai_prompts` | Generate prompt ideas for a brand — nothing is saved; pass the keepers to `add_ai_prompt` | `brandName` (required), `primaryDomain`, `count` (1–15, default 8) |
| `list_ai_exploration_engines` | The platforms Prompt Explorer can run right now. Call it before `explore_ai_prompt`: this list is **not** the tracked-analysis platform list, and anything outside it is rejected | none |
| `explore_ai_prompt` | Ask one question of up to 4 platforms at once and read every answer, the brands each named, and the sources it cited. **Spends the organization's AI budget on every call** — ask the user first, and don't loop over reworded variations: an identical question is free for 7 days, a reworded one is a fresh purchase, and a daily cap will start refusing calls. Nothing is tracked; use `add_ai_prompt` for a question worth monitoring | `promptText`, `providers` (required, 1–4), `highlightBrand`, `region` (`US`/`EU`), `analyze` (default true), `runFresh` |
| `pause_run`, `resume_run`, `cancel_run` | Control an in-flight analysis run | `jobId` (required) |

### Site, SEO & Content

| Tool | Description | Permission | Parameters |
|------|-------------|------------|------------|
| `start_site_audit` | Start a link-following crawl and technical/SEO/AI-readiness audit of the connected project's website | `siteaudit.manage` | `maxPages` (defaults to the service setting), `followLinks` (default true) |
| `update_backlink_status` | Set a backlink source to `active` (shown) or `ignored` (dismissed as noise) | `backlinks.manage` | `backlinkId`, `status` (required) |
| `update_recommendation_status` | Move a content recommendation to `ACCEPTED`, `DONE`, or `DISMISSED` | `contentrecs.manage` | `recommendationId`, `status` (required), `dismissedReason` |

`start_site_audit` always audits the connected project's own website, and runs every check the server can run. Auditing somebody else's site, or narrowing a run to a subset of the checks, is a [dashboard](/site-audit#running-an-audit) decision — an assistant cannot spend your daily allowance for audits of other websites on its own.

### Projects & Organization

| Tool | Description | Permission | Parameters |
|------|-------------|------------|------------|
| `create_project` | Create a project in the connected organization | `project.create` | `name`, `projectType` (required), `description`, `websiteDomain` |
| `update_project` | Update the project's name, description, or website domain | `project.update` | `name` (required), `description`, `websiteDomain` (empty string clears it) |
| `update_organization` | Organization settings: name, logo, 2FA requirement, data-retention notification emails | `organization.update` | `name`, `logoUrl`, `require2fa`, `dataRetentionNotificationsEnabled` (all optional) |

### Webhooks

Webhooks are organization-scoped, not per-project.

| Tool | Description | Permission | Parameters |
|------|-------------|------------|------------|
| `create_webhook` | Create a webhook that POSTs subscribed events to a URL | `webhook.create` | `name`, `url`, `events` (required), `secret`, `enabled` (default true) |
| `update_webhook` | Update name, URL, events, secret, or enabled state | `webhook.update` | `webhookId` (required); the rest optional — `events` replaces the whole set, empty `secret` clears it |
| `delete_webhook` | Delete a webhook | `webhook.delete` | `webhookId` (required) |

### Workflows

Everything the workflow canvas can do: author a graph, validate it, publish it, run it. Authoring requires `workflow.manage` and running requires `workflow.run` — the same permissions the dashboard enforces — and both are held to the plan's workflow entitlements (feature enabled, active-workflow cap, monthly run allowance, AI budget).

Start with [`list_workflow_step_types`](#workflow-reads): it returns each step type's ports (which decide what can connect to what) and its config keys, so a graph can be built without guessing. Every authoring tool returns the workflow with its updated graph.

| Tool | Description | Permission | Parameters |
|------|-------------|------------|------------|
| `create_workflow` | Create a workflow, empty or with its steps and connections in one call. It starts as a `DRAFT` | `workflow.manage` | `name` (required), `description`, `steps` (`{type, key?, config?}`), `connections` (`{fromKey, toKey, fromHandle?, toHandle?}`) |
| `update_workflow` | Rename a workflow or change its description; omitted fields are left alone | `workflow.manage` | `workflowId` (required), `name`, `description` |
| `duplicate_workflow` | Copy a workflow's steps, connections and settings into a new `DRAFT` | `workflow.manage` | `workflowId` (required), `name` (defaults to `"<name> (copy)"`) |
| `delete_workflow` | Delete a workflow and its run history; can report that it is pending schedule teardown — retry shortly | `workflow.manage` | `workflowId` (required) |
| `add_workflow_step` | Add a step, optionally wiring it to an existing one in the same call | `workflow.manage` | `workflowId`, `type` (required), `config`, `key` (defaults to the lowercased type), `afterKey` |
| `update_workflow_step` | Change a step's settings; the given keys are merged into its config, and a null clears one | `workflow.manage` | `workflowId`, `stepKey`, `config` (required) |
| `remove_workflow_step` | Remove a step and every connection that touched it | `workflow.manage` | `workflowId`, `stepKey` (required) |
| `connect_workflow_steps` | Feed one step's output into another's input | `workflow.manage` | `workflowId`, `fromKey`, `toKey` (required), `fromHandle` (default `out`), `toHandle` (default `in`) |
| `disconnect_workflow_steps` | Remove the connection(s) between two steps, leaving both in place | `workflow.manage` | `workflowId`, `fromKey`, `toKey` (required) |
| `set_workflow_graph` | Replace the whole graph — for rebuilding rather than editing. Anything not listed is dropped | `workflow.manage` | `workflowId`, `steps` (required), `connections` |
| `validate_workflow` | Check whether the draft could be published; returns each error pinned to a step or a connection | `workflow.manage` | `workflowId` (required) |
| `publish_workflow` | Compile the draft, make it the live version, and start its schedule. Fails with the validation errors if it is not runnable | `workflow.manage` | `workflowId` (required) |
| `unpublish_workflow` | Retire the live version, stop the schedule, and return the workflow to `DRAFT` | `workflow.manage` | `workflowId` (required) |
| `run_workflow` | Start a run and return its id; runs are asynchronous, so poll `get_workflow_run` | `workflow.run` | `workflowId` (required), `mode` (`PUBLISHED` default, or `DRAFT_TEST`), `upToStepKey` (runs only that step and everything feeding it) |
| `cancel_workflow_run` | Cancel a run that is still in flight | `workflow.run` | `workflowId`, `runId` (required) |

A worked example — a weekly check of the site's busiest pages, emailed out:

```
list_workflow_step_types                              → what steps exist and what they accept
create_workflow  name: "Weekly page health"
add_workflow_step  type: SCHEDULE_TRIGGER   config: {"frequency": "WEEKLY"}
add_workflow_step  type: MY_SITE_PAGES      config: {"source": "TOP_TRAFFIC", "limit": 10}
                   afterKey: schedule_trigger
add_workflow_step  type: PAGE_HEALTH_CHECK  afterKey: my_site_pages
add_workflow_step  type: EMAIL_DELIVER      config: {"recipients": ["you@company.com"]}
                   afterKey: page_health_check
validate_workflow                                     → fix anything it reports
publish_workflow                                      → live, and running every week
```

### Team

| Tool | Description | Permission | Parameters |
|------|-------------|------------|------------|
| `invite_member` | Invite a user to the organization by email, with a role | `member.invite` | `email`, `roleId` (required) |
| `cancel_invitation` | Cancel a pending invitation | `invitation.cancel` | `invitationId` (required) |
| `change_member_role` | Change a member's role | `member.update_role` | `memberId`, `roleId` (required) |
| `remove_member` | Remove a member from the organization | `member.remove` | `memberId` (required) |

## Common Parameters

### Date Filtering

Most tools accept date filtering via either a preset or an explicit date range:

* **`preset`** — one of `last_30_minutes`, `last_hour`, `today`, `yesterday`, `last_7_days`, `last_30_days`, `this_month`, `this_year`
* **`from`** / **`to`** — explicit date range in `YYYY-MM-DD` format (inclusive)

If both a preset and explicit dates are provided, **the preset takes precedence**.

Every window is resolved on the server in the project's [reporting timezone](/web-analytics#reporting-timezone) — the same way the dashboard resolves it. That is why a relative period should be asked for with its preset rather than with dates the assistant computes: the dashboard's *Last 7 days* is `last_7_days` (today plus the six days before it), and an assistant that sends `from = today − 7, to = today` instead reads one day more and reports different numbers for the same funnel. Reserve `from`/`to` for explicit calendar dates. `analyze_funnel` echoes the days it actually covered as `from`/`to` in its response, so the period an assistant quotes is the one the numbers came from.

For the AI visibility tools, omitting all date parameters aggregates the last `jobs` scan jobs instead of a time window.

### Period comparison

Thirteen of the read tools answer for an earlier window in the same call, so an assistant can say what changed without running the query twice and subtracting. They do it by default; these parameters change which window:

* **`comparePreset`** — one of `previous_period`, `previous_week`, `previous_month`, `previous_quarter`, `previous_year`
* **`compareFrom`** / **`compareTo`** — a custom comparison window in `YYYY-MM-DD` format (inclusive); send both or neither

`comparePreset` takes precedence when both are given, and omitting all three compares against the previous period — an assistant gets the change for free and only names a parameter to ask for a different window. `previous_period` keeps the length of your window and ends where it starts; the calendar presets are instead the week, month, quarter or year running up to it, which are usually longer — so an assistant comparing a day against `previous_month` is comparing it with the month behind it, not with the same day a month ago. [Compare to an earlier period](/web-analytics#compare-periods) has the full semantics, the one-year guard, and how the extra fields read.

| Area | Tools |
|---|---|
| **Web analytics** | `get_web_summary`, `get_web_timeline`, `get_web_breakdown` |
| **Bots & crawlers** | `get_bot_timeline`, `get_bot_countries`, `get_top_bots`, `get_bot_top_paths` |
| **AI visibility** | `get_ai_visibility_series`, `get_ai_visibility_domains`, `get_ai_cited_pages`, `get_ai_competitors`, `get_ai_share_of_voice`, `get_ai_prompts_breakdown` |

::: warning AI visibility comparisons need a date window
The AI visibility tools fall back to aggregating the last `jobs` scans when no dates are given — a count, not a period. A comparison asked for in that mode is refused rather than silently dropped, so pass `preset` or `from`/`to` alongside it.
:::

### Pagination

List tools support pagination with **`page`** (0-indexed) and **`size`**.

### Filters

Web analytics and event tools support filters in the format `dimension:operator:values`:

```
"country:is:US,UK"
"browser:is:Chrome"
"page:contains:/blog"
```

Pass multiple filters as an array to combine them.

## Example Prompts

| Question | Tools Used |
|----------|------------|
| "How many visitors are on the site right now?" | `get_realtime_visitors` |
| "How many visitors did we get last week?" | `get_web_summary` |
| "Compare this week's traffic to last week by country" | `get_web_breakdown` with `comparePreset` |
| "What's our weekly retention looking like this quarter?" | `get_retention` |
| "Show me the signup funnel conversion rate" | `analyze_funnel` |
| "How visible is our brand in AI search this month?" | `get_ai_visibility_series` |
| "Which domains does ChatGPT cite instead of us?" | `get_ai_visibility_domains` |
| "What are the most cited pages for our lead-capture topic?" | `get_ai_cited_pages` |
| "How do we compare to competitors in AI answers?" | `get_ai_competitors` |
| "Is ChatGPT crawling our docs? Which pages?" | `get_top_bots` + `get_bot_top_paths` |
| "Which pages have the most rage clicks this week?" | `get_frustration_signals` |
| "Show me a summary of user U-123's activity" | `get_user_summary` |
| "Add Vercel as a competitor we track in AI answers" | `add_ai_competitor` |
| "Stop counting Blinq on the badge-scanning topic" | `list_ai_topics` + `list_ai_brands` + `update_ai_brand` |
| "Suggest prompts for my brand and track the good ones" | `suggest_ai_prompts` + `add_ai_prompt` |
| "What does AI say about us right now?" | `list_ai_exploration_engines` + `explore_ai_prompt` |
| "Run a site audit and walk me through the findings" | `start_site_audit` + `get_latest_site_audit` + `list_site_audit_issues` |
| "Fix everything the last audit found, in my codebase" | `get_site_audit_worklist` + `list_site_audit_issue_pages` |
| "Which pages are slowest, and what is slowing them down?" | `list_site_audit_performance` + `get_site_audit_performance_page` |
| "What's wrong with our pricing page?" | `get_page_content` |
| "Check this draft before I publish it" | `audit_draft` |
| "Which sites should we try to get links from?" | `get_backlink_opportunities` |

## Connect external MCP servers to the assistant

The direction above is Kitbase acting as an MCP server. The project assistant
can also act as an MCP **client**: connect any Streamable HTTP MCP server and
its tools become available to the assistant in chat.

**Where:** Project Settings → Assistant → Connect server.

| Field | Rules |
|---|---|
| Name | 2–24 chars, lowercase letters/digits/hyphens. Immutable — it namespaces the server's tools as `mcp__<name>__<tool>`. |
| URL | The server's Streamable HTTP endpoint. `https` only. |
| Authentication | **Auth header** or **OAuth**, chosen at creation and immutable afterwards. |
| Auth header | Header connections only. Either a full `Name: value` line, or a bare token sent as `Authorization: Bearer <token>`. Stored encrypted; write-only — it can be replaced or removed, never read back. |
| Require approval | On by default: every tool call from this server pauses the chat for your approval, showing the exact arguments. Turn it off only for servers you trust. |

### OAuth-protected servers

Servers that authenticate with OAuth — Atlassian's
`https://mcp.atlassian.com/v1/mcp/authv2`, for example — need no token pasted
in. Create the connection with **OAuth**, then press **Connect**: Kitbase asks
the server who authorizes it, registers itself as a client, and sends you to
that server's own consent screen. Approve there and you land back on the
project's settings page with the connection live.

* The grant belongs to the account you approved it with, and Kitbase keeps it
  alive by refreshing it in the background. Tokens are encrypted at rest and
  are never shown, exported, or returned by the API.
* Until you complete the flow, an OAuth connection holds no credential and
  contributes no tools.
* If the server revokes the grant — or you remove Kitbase's access on its
  side — its tools stop appearing. Press **Connect** again to re-authorize.
* Servers that do not offer dynamic client registration cannot be connected
  this way; use an auth header instead.

Behavior and limits:

* Up to 10 connections per project; the first 40 tools per server are offered.
* Tool lists refresh about once a minute; an unreachable server simply
  contributes no tools until it answers again. Use **Test** to dial a
  connection on demand and list its tools.
* External results are treated as untrusted data by the assistant — a page or
  tool output cannot instruct it to change its behavior.
* Connections are project-scoped, changes are audit-logged, and creating or
  editing them requires the `project.update` permission.

## Requirements

* A Kitbase account with an active project
* An MCP-compatible client that supports remote servers over Streamable HTTP
* For API-key auth: a private API key (`sk_kitbase_`) from **Project Settings > API Keys**

## Next steps

* [Project Assistant](/guide/assistant) — the same tools, built into the dashboard, with no client to connect.
* [CLI](/cli) — the same operations from your terminal instead of an AI assistant, and where `kitbase login` lives.
* [API reference](/api-reference) — the REST API behind every MCP tool.
* [Analytics dashboard guide](/guide/analytics) — the data these tools query.
