> ## Documentation Index
> Fetch the complete documentation index at: https://docs.searchable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Integration

> Connect Searchable to Claude, ChatGPT, Grok, Gemini, Microsoft Copilot agents, and other MCP-compatible assistants.

## What is MCP?

The **Model Context Protocol (MCP)** is an open standard that lets AI assistants connect to external tools and data sources. Searchable's MCP server lets you ask AI assistants questions about your AI visibility, site audits, and content — without leaving the assistant.

Compatible clients include:

* **Claude.ai** (web and mobile) — add as a connector
* **ChatGPT** — via developer-mode connectors
* **Grok** — via custom MCP connectors
* **Gemini Spark** — via custom Connected Apps
* **Microsoft 365 Copilot agents** and **Microsoft Copilot Studio**
* **Claude Desktop** and **Claude Code**
* **Cursor**
* **Windsurf**, **Perplexity Enterprise**, **Mistral**, and other MCP-compatible clients

The assistant controls whether custom MCP servers are available for your account, plan, and region.
Gemini custom apps currently require Gemini Spark eligibility, a US personal Google Account, and
English.
For Microsoft, this compatibility applies to Microsoft 365 Copilot agents and Copilot Studio — not
the consumer Microsoft Copilot chat product, which does not currently document a bring-your-own MCP
server flow.

<Info>
  Ask questions like *"What's my brand's visibility score on ChatGPT?"* or *"What critical AEO
  issues does my site have?"* and the assistant will query Searchable for live data.
</Info>

<Note>
  **Plan requirement:** MCP integration requires a paid Searchable plan. Both connection methods —
  signing in with OAuth and API keys — are unavailable on the Free plan.
</Note>

## How authentication works

The Searchable MCP server uses **OAuth 2.1 with PKCE**. It supports the current MCP client-metadata
discovery flow, plus Dynamic Client Registration for clients that still require it. Public clients
use PKCE; compatible enterprise clients can use a dynamically issued client secret. This means a
new standards-compatible assistant can connect without Searchable adding a provider-specific OAuth
integration first.

You do **not** paste an API key into your MCP client's config — you sign in and approve access, just
like connecting any other app:

1. You add the server URL to your client (`https://app.searchable.com/api/mcp-server/mcp`).
2. On first use, the client opens Searchable in your browser and asks you to **log in** (or reuses your existing session).
3. A **consent screen** shows which application is connecting and lets you choose:
   * **Which projects** it can access — pick specific ones, or leave all selected (all also covers projects you create later).
   * **Read-only** (default) or **Read & write** access.
4. You click **Authorize**. Your client receives an OAuth access token scoped to exactly what you approved and uses it for subsequent requests.

The token can never exceed what you granted on the consent screen — a read-only, single-project grant stays read-only and single-project.

<Warning>
  Only connect the Searchable MCP from trusted clients. Grant the narrowest access you need —
  read-only, and only the projects the assistant should see.
</Warning>

## Setup

<Note>
  **Server URL:** `https://app.searchable.com/api/mcp-server/mcp` — the same address for every
  client below.
</Note>

### Step 1: Add the server to your client

<Tabs>
  <Tab title="Claude.ai">
    1. Open Claude.ai → **Settings → Connectors**.
    2. Click **Add custom connector**.
    3. Enter URL: `https://app.searchable.com/api/mcp-server/mcp`
    4. Claude opens the Searchable sign-in and consent page — continue with Step 2.
  </Tab>

  <Tab title="ChatGPT">
    ChatGPT connects through developer-mode connectors (available on ChatGPT Plus and Pro):

    1. Open ChatGPT → **Settings → Apps & Connectors → Advanced settings** and enable **Developer mode**.
    2. Back in **Apps & Connectors**, click **Create** to add a connector.
    3. Enter URL: `https://app.searchable.com/api/mcp-server/mcp` and choose **OAuth** authentication.
    4. ChatGPT opens the Searchable sign-in and consent page — continue with Step 2.

    In a chat, enable the Searchable connector from the composer's tools menu, then ask away.
  </Tab>

  <Tab title="Claude Code">
    Add the server from your terminal:

    ```bash theme={null}
    claude mcp add searchable --transport http https://app.searchable.com/api/mcp-server/mcp
    ```

    Then inside a session run `/mcp`, pick **searchable → Authenticate** — your browser opens the Searchable sign-in and consent page. Continue with Step 2.
  </Tab>

  <Tab title="Claude Desktop">
    Add to `~/.claude/mcp.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "searchable": {
          "url": "https://app.searchable.com/api/mcp-server/mcp"
        }
      }
    }
    ```

    Restart Claude Desktop. On first use it opens the Searchable sign-in and consent page in your browser — continue with Step 2.
  </Tab>

  <Tab title="Cursor">
    Click to install with one step — Cursor opens, shows the server, and you confirm:

    <a href="cursor://anysphere.cursor-deeplink/mcp/install?name=searchable&config=eyJ1cmwiOiJodHRwczovL2FwcC5zZWFyY2hhYmxlLmNvbS9hcGkvbWNwLXNlcnZlci9tY3AifQ==">
      <img className="block dark:hidden" src="https://cursor.com/deeplink/mcp-install-dark.png" alt="Add searchable MCP server to Cursor" style={{ maxHeight: 32 }} noZoom />

      <img className="hidden dark:block" src="https://cursor.com/deeplink/mcp-install-light.png" alt="Add searchable MCP server to Cursor" style={{ maxHeight: 32 }} noZoom />
    </a>

    Or add manually to `.cursor/mcp.json`:

    ```json theme={null}
    {
      "mcpServers": {
        "searchable": {
          "url": "https://app.searchable.com/api/mcp-server/mcp"
        }
      }
    }
    ```

    Either way, Cursor discovers OAuth automatically — no manual `auth` block needed. On first use it opens the Searchable sign-in and consent page in your browser — continue with Step 2.
  </Tab>

  <Tab title="Windsurf">
    Add to your Windsurf MCP configuration:

    ```json theme={null}
    {
      "mcpServers": {
        "searchable": {
          "url": "https://app.searchable.com/api/mcp-server/mcp"
        }
      }
    }
    ```
  </Tab>
</Tabs>

For Grok, Gemini Spark, Microsoft 365 Copilot agents, Copilot Studio, Perplexity Enterprise,
Mistral, or another hosted assistant, create a custom MCP server or connector in that product and
use `https://app.searchable.com/api/mcp-server/mcp`. Searchable publishes OAuth discovery metadata, so choose OAuth
or automatic authentication when the host asks. The exact menu name and availability are controlled
by the host product.

### Step 2: Log in and authorize

The first time your client connects, Searchable opens in your browser:

1. **Log in** to Searchable (or continue with your existing session).
2. On the **consent screen**, choose which projects the client may access and whether it gets **Read-only** (default) or **Read & write** access.
3. Click **Authorize**. Your browser returns to the client, now connected — no API key to copy or paste.

<Note>
  Your login is the identity for this flow. To connect a script or a client that only accepts a
  static bearer token, use the legacy API-key path below instead.
</Note>

### Step 3: Verify the connection

Once connected, ask:

```
List my Searchable projects
```

You should see a list of your projects with their names and domains.

### Connect with an API key (legacy)

The OAuth login flow above is the recommended path for interactive MCP clients. For scripts, headless clients, or any client that only accepts a static bearer token, a Searchable **API key** works as a bearer token directly — the same `sea_` key the [REST API](/advanced/api-usage) accepts.

<Steps>
  <Step title="Create an API key">
    In **Settings → Workspace → Integrations**, click **Create API Key**, name it, and copy the key
    (starts with `sea_`). Store it securely — you won't see it again. A key can be scoped to a
    single project and to read or read-and-write, mirroring the consent options above.
  </Step>

  <Step title="Send it as a bearer token">
    Configure your client to send `Authorization: Bearer sea_...` on every request to
    `https://app.searchable.com/api/mcp-server/mcp`. No login or consent screen is involved on this
    path.
  </Step>
</Steps>

## Available tools

The Searchable MCP exposes **30 primary tools** — 27 read-only and 3 write (32 with the Associated Sources feature enabled), plus 4 deprecated names kept callable through their sunset window (see [Deprecated tool names](#deprecated-tool-names)). Every read tool is annotated `readOnlyHint: true`. Every tool that operates on a project takes a required `projectId` (get IDs from `list_projects`), and most read tools also accept an optional `response_format` — `"concise"` (default, summary + capped rows) or `"detailed"` (fuller rows, same shape).

Five tools exist purely to make assistants reliable, and are worth calling early in a session:

* **`get_current_date`** — the server's UTC date plus pre-computed window starts (7/30/90/365 days). Call it before interpreting relative time like "last month" so date math is never guessed.
* **`whoami`** — who this connection is authenticated as, its read/write scopes, and the project roster it can see. Call it when unsure what the connection can access.
* **`search_docs`** / **`read_doc`** — full-text search and retrieval over this documentation, so questions like "how is the visibility score calculated?" are answered in-chat, from the source.
* **`get_page_metrics`** — the per-URL rollup: one page's citations, AI-referral sessions, and AI-crawler hits as daily buckets, in one call.

The server also publishes two **resources** — `searchable://glossary` (compact index) and `searchable://glossary/full` (complete definitions) — metric and concept definitions with the tools that read each one, for hosts that attach resources as reference context.

`get_visibility`, `get_share_of_voice`, and `get_sentiment` additionally accept `compare: "previous_period" | "previous_year"` — the response gains a `comparison` block with the previous window's metrics and current-minus-previous deltas, so period-over-period questions are one tool call.

<Note>
  **Absolute date ranges.** The core analytics reads accept `from` and `to` (inclusive, UTC) as an
  alternative to the relative `days` — `from: "2026-03-01", to: "2026-03-31"` answers "how did March
  go" without arithmetic against today. Both bounds are required together, and they are **mutually
  exclusive with `days`**: sending both is an `invalid_argument` error rather than a silent
  precedence rule. Bare `YYYY-MM-DD` and full ISO timestamps are both accepted (the UTC day is what
  counts). An over-long `days` is clamped to the tool's maximum, but an over-long explicit range is
  **rejected** — narrowing a range you spelled out would answer a different question than the one
  you asked. Supported on `get_visibility` (every `group_by`), `get_share_of_voice`,
  `get_sentiment`, `get_topic_analysis`, and `get_ai_traffic` (every `report`), plus the matching
  REST endpoints. Tools not in that list — `get_competitors`, `get_shopping_visibility`, `get_ads`,
  `get_page_metrics`, `get_query_fanout`, `get_prompt_answers`, and the source reads — still take
  `days` only. (`get_gsc_performance` has always had its own `startDate` / `endDate`.)
</Note>

<Note>
  **Filter echo.** Every filterable read echoes an `appliedFilters` object: the platform / brand /
  topic / location filters that call actually narrowed by, or `{}` when it was unfiltered. That
  covers `get_visibility`, `get_share_of_voice`, `get_competitors`, `get_topic_analysis`,
  `list_prompts`, `get_sentiment` (every view), `get_source_trends`, and `get_shopping_visibility`
  (`summary` view). All of them except `get_topic_analysis` also return `availableFilters` (the
  project's topics and countries) so a client can offer the next filter without a second round-trip
  — on `get_visibility` that rides along on the `summary` and `platform` views. Use `appliedFilters`
  to state what a number covers rather than presenting a filtered figure as the whole picture.
</Note>

<Note>
  Write actions **are** MCP tools too: `generate_report`, `trigger_audit`, and `refresh_sitemap` are
  available to keys/grants with the **write** scope, and each requires an explicit `confirm: true`
  argument — without it the tool returns a dry-run preview and changes nothing. The REST equivalents
  (e.g. `POST /api/mcp/projects/{projectId}/reports`) remain available; see [Advanced API
  Usage](/advanced/api-usage).
</Note>

### Diagnostics

<Card title="investigate" icon="magnifying-glass-chart">
  Answers a whole diagnostic **question** in one call, by running its multi-read chain server-side
  and in parallel. Prefer it over chaining the granular tools yourself for "why did X change" and
  "where do we stand" questions.

  `question="visibility_drop"` (default) returns the score trend with the largest single-day fall,
  per-platform movement against the previous window, the share-of-voice shift with which
  competitors gained, and the weakest topics. `"competitive_position"` returns your standing with
  ranks and deltas plus the topics where a rival currently leads. `"citation_gap"` returns your
  citation trend, the domains AI answers actually cite in your space, and per-engine citation share.

  Takes the usual narrowing arguments — `days` or `from`/`to`, `platform`, `topicId`, `unbranded` /
  `branded`, `country` / `locationId`.

  **It reports measurements, never a cause.** It will tell you ChatGPT fell 38 points while Gemini
  held; it will not tell you why, because the reason is usually outside the data — a rebrand, a
  campaign ending, a competitor launch. Every response carries an `interpretation` list naming what
  the numbers cannot settle. A signal whose upstream failed appears in `unavailable` with a reason
  rather than as a zero, so missing data is never mistaken for a finding.

  Each question has a companion recipe explaining how to read the result and when it misleads:
  [visibility drop](/recipes/visibility-drop), [competitive position](/recipes/competitive-position),
  [citation gap](/recipes/citation-gap). Load one in-chat with
  `read_doc("recipes/visibility-drop")`.
</Card>

### Projects

<CardGroup cols={2}>
  <Card title="list_projects" icon="folder">
    List all projects this connection can access — names, domains, and IDs. Call this FIRST: every other tool needs a `projectId`.

    **Parameters:** `response_format` (optional)
  </Card>
</CardGroup>

### Visibility & share of voice

<CardGroup cols={2}>
  <Card title="get_visibility" icon="chart-line">
    AI visibility for a project. `group_by` selects the view: `summary` (overall visibility score), `platform` (per ChatGPT/Claude/Gemini/Perplexity breakdown), `prompt` (per-prompt breakdown, paginated), `location` (per country/city), or `date` (the per-report time series — trend, uplift, before/after analysis; default 90-day window). The `unbranded`/`branded`, `topicId`, `country` and `locationId` filters work on every view except `date`, which accepts only `unbranded`/`branded`; the `platform` filter is restricted to `group_by=prompt|location`. An interactive Visibility Snapshot card also renders automatically beside the answer on Apps-capable clients — see below.

    <Warning>
      `group_by="topic"` was **removed**. Per-topic visibility now lives on its own tool, `get_topic_analysis` (next card), which returns the competitive rank, strengths, and gaps the old view never had. Passing `group_by="topic"` is now a validation error — switch the call to `get_topic_analysis`.

      **`include`** attaches optional extra blocks (comma-separated): `industry_ranking` adds the competitive leaderboard — the **top 25** entities by visibility, with your own rank, share of voice, and change — to `group_by=summary|platform` (`hasMore` flags a longer field; `brandRankStatus` distinguishes ranking below the cutoff from being unranked, and `totalEntities` is null when truncated); `annotations` adds the dated markers (campaign launches, migrations) overlapping the window to `group_by=date`, so a spike comes back with its explanation attached. An `include` a view cannot attach is an `invalid_argument` error on **every** view — including `group_by=prompt` and `group_by=location`, which attach no blocks at all — never a response that quietly lacks the block.
    </Warning>

    **Parameters:** `projectId` (required), `group_by` (`"summary"` | `"platform"` | `"prompt"` | `"location"` | `"date"`, default summary), `days` (default 30, or 90 for `group_by=date`; max 365, or 180 for `group_by=prompt`), `unbranded`, `branded`, `platform`, `topicId`, `limit` (`group_by=prompt`, default 100, max 500), `offset` (`group_by=prompt`), `country`, `locationId` (all optional except `projectId`)
  </Card>

  <Card title="get_topic_analysis" icon="shapes">
    Per-topic AI visibility — the brand's mention rate in each topic, its competitive rank within that topic, how many tracked prompts sit under it, and the AI-identified attribute strengths and gaps behind the number. This is the **only** per-topic view: use it for any question about topics, subject areas, or categories, including where a brand is winning or losing and what to fix. Replaces the removed `get_visibility (group_by:"topic")`. An interactive Topic Analysis card also renders automatically beside the answer on Apps-capable clients — see below.

    **`view="prompts"`** drills into ONE topic (`topicId` required): every prompt assigned to it with your average rank, its visibility, and which competitors outrank you — the "which specific questions are we losing" view behind a weak topic. A prompt you never appeared for reports `rank: null`, never `0`.

    **`view="heatmap"`** returns average brand position per topic x AI platform — where you're strong on one engine and absent on another. There is deliberately no platform filter on this view: the platform axis is the answer.

    **Parameters:** `projectId` (required), `view` (`topics` default, `prompts`, `heatmap` — `platform` is rejected on `heatmap`, whose axis IS platform), `topicId` (required for `view=prompts`; rejected on the default view, which lists every topic), `days` (default 30, max 365), `from`/`to`, `limit`, `platform`, `unbranded`, `branded` (all optional except `projectId`)
  </Card>

  <Card title="get_visibility_history" icon="timeline">
    **Deprecated** — call `get_visibility` with `group_by: "date"` instead (same data, same parameters). This name keeps answering with an identical payload until its removal date; see [Deprecated tool names](#deprecated-tool-names).

    **Parameters:** `projectId` (required), `days` (default 90, max 365), `unbranded`, `branded` (all optional except `projectId`)
  </Card>

  <Card title="get_share_of_voice" icon="chart-pie">
    Brand vs competitor share of voice — mention-share percentage and rank among all tracked entities, plus each entity's day-over-day (today vs yesterday) point change. `mentions`/`citations` aren't part of this view (mirrors the in-app Share of Voice sheet) — use `get_competitors` for those. An interactive Share of Voice card also renders automatically beside the answer on Apps-capable clients — see below.

    **`group_by="date"`** returns the daily share series instead — your share and your top competitors', one point per day. That is the "is our share growing?" view; `include="annotations"` adds the dated markers explaining any jump. `compare` is not supported there (the series already shows the movement), and the interactive card hides itself on it.

    **Parameters:** `projectId` (required), `group_by` (`summary` default, or `date`), `days` (default 30, max 365), `from`/`to`, `include`, `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `compare` (all optional except `projectId`)
  </Card>
</CardGroup>

### Competitors

<CardGroup cols={2}>
  <Card title="get_competitors" icon="users">
    The project's real/tracked competitors, **ordered by rank (highest visibility first)** — visibility, share of voice, sentiment score, average mention position, and all-time mention count, plus a `brand` block carrying the same metrics for the project itself so the two sit on one ruler. Pass `competitorId` for a **head-to-head** against one competitor instead: `headToHead.brand` vs `headToHead.competitor`, each with visibility %, share of voice %, sentiment (0–100) and average position (a rank — **lower is better**), plus the phrases each is known for; for the competitor it also returns its rank, per-day history, and up to 10 recent prompts it was mentioned in. `competitorId` accepts either the competitor's ID or its **name exactly as the list shows it** (`competitorId: "Nike"`), so you can drill in straight from the list — `response_format: "detailed"` adds an `id` column if you want the exact key. A `null` metric means unmeasured in that window, not zero. `visibility`/`sov`/`sentimentScore`/`avgPosition` honor the filters; the list view's `mentions` is all-time and ignores them. On the head-to-head, `citations` (inline citations on responses mentioning the competitor) and `history[].domainSources` (source URLs — pages used for generation, not inline citations — on the competitor's own domain) are different metrics. An interactive Competitor Ranking card also renders automatically beside the answer on Apps-capable clients, in whichever shape matches the call — see below.

    **Parameters:** `projectId` (required), `competitorId` (optional — ID or name; switches to the head-to-head view), `days` (default 30, max 365), `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `limit` (list view, default 20, max 100), `offset` (list view) (all optional except `projectId`)
  </Card>
</CardGroup>

### Sources & citations

<CardGroup cols={2}>
  <Card title="search_sources" icon="globe">
    Top cited source domains from AI answers — rank, citation counts, usage %, models, and top content types. Paginated and sortable. Use to find which external sites AI models cite most; drill in with `get_source_detail`.

    **Parameters:** `projectId` (required), `sort` (e.g. `"-citations"`), `limit` (default 20, max 100), `offset`, `days`, `platform`, `topicId`, `sourceType`, `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`)
  </Card>

  <Card title="get_source_detail" icon="building">
    Drill into cited sources. `level="domain"` (+ `domain`): domain summary, or `include="urls"` (cited URLs on it) / `include="responses"` (AI responses citing it). `level="url"`: with `url` → single-page citation analytics, or `include="content"` → cached page markdown + entity mentions (never triggers a live scrape); without `url` → all cited URLs across the project (optionally filtered by `contentType`).

    **Parameters:** `projectId` (required), `level` (`"domain"` | `"url"`, default domain), `include` (`"urls"` | `"responses"` | `"content"`), `domain` (required for `level=domain`), `url` (`level=url`), `contentType`, `limit`, `offset`, `cursor`, `days`, `platform`, `topicId`, `sourceType`, `unbranded`, `branded`, `country`, `locationId` (all optional except `projectId`)
  </Card>

  <Card title="get_source_trends" icon="arrow-trend-up">
    How the project's top source **domains** trend over time — a per-domain time-series of the external sites most used as sources in AI answers, over the window (default 30 days). An interactive Sources card renders beside the answer on Apps-capable clients: the trend line inline, expanding to the top source URLs.

    **Parameters:** `projectId` (required), `days` (window; default 30 — pass `7`/`90`/`365` for a different one), `platform`, `topicId`, `sourceType`, `unbranded`, `branded`, `country`, `locationId` (all optional except `projectId`)
  </Card>
</CardGroup>

### Sentiment

<CardGroup cols={2}>
  <Card title="get_sentiment" icon="face-smile">
    Brand sentiment across AI platforms. `view` selects: `summary` (default) — overall score + positive/neutral/negative split + per-platform breakdown, plus `analysis` (the weekly brand-perception write-up for the platform that mentions you most, or the one you filter to with `platform`) and `analyses` (every platform's write-up); `history` — the daily sentiment trend (default 90-day window); `competitors` — head-to-head vs competitors, the brand flagged by `isBrand`. `topicId` is not accepted on `competitors`, and `compare` only on `summary`. The interactive Sentiment Profile card renders on `summary` answers only.

    **Parameters:** `projectId` (required), `view` (`"summary"` | `"history"` | `"competitors"`, default summary), `days` (default 30, or 90 for `view=history`; max 365), `platform`, `topicId`, `limit` (`view=competitors`, default 10, max 50), `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`)
  </Card>

  <Card title="get_sentiment_history" icon="chart-line">
    **Deprecated** — call `get_sentiment` with `view: "history"` instead (same data, same parameters). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names).

    **Parameters:** `projectId` (required), `days` (default 90, max 365), `platform`, `topicId`, `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`)
  </Card>

  <Card title="get_sentiment_competitors" icon="users">
    **Deprecated** — call `get_sentiment` with `view: "competitors"` instead (same data, same parameters). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names).

    **Parameters:** `projectId` (required), `days` (default 30, max 365), `platform`, `limit` (default 10, max 50), `unbranded`, `branded`, `country`, `locationId`, `response_format` (all optional except `projectId`)
  </Card>
</CardGroup>

### Query fanout

<CardGroup cols={2}>
  <Card title="get_query_fanout" icon="magnifying-glass">
    The sub-queries AI models generate when answering tracked prompts. Without `prompt_id`: the project aggregate (most frequent fanout queries, per-prompt counts, per-model stats; paginated). With `prompt_id`: every deduped sub-query for that one prompt, with per-query frequency and query type. Paid feature — returns `plan_upgrade_required` if not entitled.

    **Parameters:** `projectId` (required), `prompt_id` (optional — note the snake\_case; omit for the project aggregate), `days` (default 30, max 180), `platform`, `topicId`, `unbranded`, `branded`, `limit` (aggregate, default 50, max 50), `offset` (aggregate), `topQueriesLimit` (aggregate, default 50, max 200) (all optional except `projectId`)
  </Card>
</CardGroup>

### Brand profile & authority

<CardGroup cols={2}>
  <Card title="get_brand_profile" icon="id-card">
    The project's knowledge-base brand profile (name, headline, category, positioning, description, topics, business type, connected entities, competitor geography/context) plus the latest batch of **AI-observed brand facts** — statements AI platforms actually make about the brand, each with its platform and verification status. Pass `include: "domain_authority"` to also get the Moz domain-authority block (latest DA/PA/spam/linking-domains snapshot + roughly-monthly 365-day history) in the same call. Call this first for brand context in any analysis. `profile` is `null` until onboarding creates one; facts come from the weekly sentiment job.

    **Parameters:** `projectId` (required), `platform`, `verificationStatus` (`"unverified"` | `"pass"` | `"fail"`), `include` (`"domain_authority"`), `response_format` (all optional except `projectId`)
  </Card>

  <Card title="get_domain_authority" icon="ranking-star">
    **Deprecated** — call `get_brand_profile` with `include: "domain_authority"` instead (same data; the REST endpoint keeps custom `days` windows). Keeps answering identically until its removal date; see [Deprecated tool names](#deprecated-tool-names).

    **Parameters:** `projectId` (required), `days` (default 365, max 1095) (all optional except `projectId`)
  </Card>
</CardGroup>

### Prompts

<CardGroup cols={2}>
  <Card title="list_prompts" icon="list-check">
    The project's prompt catalog as configured — tracked prompts (type, intent, branded flag, topics, tracked locations, keyword volume/difficulty), paused (untracked) prompts, or the AI-suggested review queue (`status="suggested"` — proposals from research runs and the agent that nobody has accepted or rejected yet, each with the reason it was proposed). Configuration data, not performance — use `get_visibility group_by=prompt` for per-prompt visibility scores. The summary always reports all three counts, so pending suggestions surface from any call. An interactive Prompts card also renders automatically beside the answer on Apps-capable clients.

    **Parameters:** `projectId` (required), `status` (`"tracked"` | `"untracked"` | `"suggested"` | `"all"`, default tracked), `topicId`, `limit` (default 50, max 200), `offset`, `days` (metrics window, default 30, max 365), `sortBy` (`prompt` | `volume` | `difficulty` | `branded` | `intent` | `recency`, default recency — performance columns like visibility/position/sentiment are computed per page and are **not** server-sortable), `sortDir` (`"asc"` | `"desc"`, default desc), `unbranded`, `branded`, `response_format` (all optional except `projectId`)
  </Card>

  <Card title="get_prompt_answers" icon="comments">
    Raw, per-response AI-answer data for one prompt (Profound-parity) — platform, response date, response text, whether the brand was mentioned, competitors mentioned, and the source URLs the engine consulted (`sources[]` — pages used for generation, not inline citations), one entry per AI response. Not an aggregate — use `get_visibility` or `get_share_of_voice` for scores/rankings. Response text is hard-truncated at 2000 characters server-side; the markdown table shows a short snippet (120 characters, 500 in `detailed` format).

    **Parameters:** `projectId` (required), `promptId` (required — note the camelCase, unlike `get_query_fanout`'s `prompt_id`), `days` (default 30, max 365), `platform`, `limit` (default 10, max 50) (all optional except `projectId` and `promptId`)
  </Card>
</CardGroup>

### Site health & audits

<CardGroup cols={2}>
  <Card title="get_site_health" icon="heart-pulse">
    Technical + AEO site health. `view="pages"` (default) = the monitored-page inventory, **one server-paginated page at a time** (audited pages first) — every page (audited or not) with its latest technical/AEO scores, open-issue count, and last-audited date, plus `pagination.total`; page with `limit`/`offset`, filter server-side with `search` (URL/slug/title), reorder with `sortBy`, and omit the header aggregates with `includeSummary=false`; `view="audits"` = per-page technical/AEO scores for audited pages + averages; `view="issues"` = issues grouped by severity (filter `status`); `view="issue_details"` = issues grouped by type with fix guidance (filter `issueType`). An interactive Site Audit card renders beside the answer on Apps-capable clients: `pages`/`audits` show the inventory with a **search box** + **Prev/Next** paging (both server-side), a per-page **Audit** button (calls `trigger_audit` for that page), and a **Sync website** button (calls `refresh_sitemap`); `issues`/`issue_details` show the findings ranked by severity, each expanding to its fix guidance. Clicking a page row on the inventory drills into that page's own issues.

    **Parameters:** `projectId` (required), `view` (`"pages"` | `"audits"` | `"issues"` | `"issue_details"`, default pages), `status` (`view=issues`: `"open"` | `"resolved"` | `"all"`, default open), `pageId` (`view=issues`: scope to one monitored page — the card's page → issues drill-down), `issueType` (`view=issue_details`: `"technical"` | `"content"`), `includeIssues` (`view=audits`, boolean), `search` (`view=pages`: server-side filter by URL / slug / title), `sortBy` (`view=pages`: `"audited"` | `"technical"` | `"aeo"` | `"issues"`, default audited-first), `includeSummary` (`view=pages`, boolean, default true), `limit` (`view=pages` default 12, max 100; `view=issues` default 1000, max 5000), `offset` (`view=pages` / `view=issues`) (all optional except `projectId`)
  </Card>
</CardGroup>

### Content & articles

<CardGroup cols={2}>
  <Card title="list_articles" icon="newspaper">
    List articles / content pieces for a project (merges content-v2 + legacy). Shows title, status, type, word count, and source.

    **Parameters:** `projectId` (required), `status` (`"draft"` | `"published"` | `"editing"` | `"all"`, default all), `limit` (default 50, max 100) (all optional except `projectId`)
  </Card>

  <Card title="get_article" icon="file-lines">
    Full article content — HTML/markdown body, outline, FAQs, images, schema markup, internal/external links, and all metadata.

    **Parameters:** `articleId` (required)
  </Card>
</CardGroup>

### Opportunities

<CardGroup cols={2}>
  <Card title="get_opportunities" icon="lightbulb">
    Actionable opportunities Searchable surfaced for the project — prioritized fix / write / audit suggestions derived from visibility, sentiment, sources, traffic, prompts, site health, and articles. Returned in the product's priority order (status, then impact, then newest). Defaults to active opportunities; blocked for active pitch projects.

    **Parameters:** `projectId` (required), `status` (`"active"` | `"completed"` | `"dismissed"` | `"auto_resolved"`, default active only), `includeResolved`, `source` (e.g. `"visibility"`, `"sentiment"`, `"sources"`, `"traffic"`, `"prompts"`, `"site_health"`, `"articles"`), `impact` (`"critical"` | `"high"` | `"medium"` | `"low"`), `limit` (default 100, max 500), `offset` (all optional except `projectId`)
  </Card>
</CardGroup>

### GA4 / Traffic

<Note>
  This tool requires a linked GA4 property. When the project has none, the backing endpoint returns a `409` error with `{ code: "ga4_not_connected", message, howToFix }` — the `howToFix` field points at **Settings → Integrations** in Searchable, where you can connect GA4 and then retry.
</Note>

<CardGroup cols={2}>
  <Card title="get_ga4_traffic" icon="robot">
    Google Analytics 4 website traffic. `report="ai_referrals"` (default; per-LLM-host AI Source Visitors from ChatGPT/Copilot/Gemini/Perplexity/DeepSeek and more — use for AI-referral attribution), `"trend"` (daily users/sessions/pageviews), `"top_pages"` (top landing pages by sessions), or `"sources"` (channel-group + source breakdown).

    **Parameters:** `projectId` (required), `report` (`"ai_referrals"` | `"trend"` | `"top_pages"` | `"sources"`, default ai\_referrals), `weeks` (default 12, max 52), `limit` (`top_pages`/`sources`, default 20, max 100) (all optional except `projectId`)
  </Card>
</CardGroup>

### Search Console (GSC)

<Note>
  This tool requires a linked Google Search Console site. When the project has none, the backing endpoint returns a `409` error with `{ code: "gsc_not_connected", message, howToFix }` — the `howToFix` field points at **Settings → Integrations** in Searchable, where you can connect GSC and then retry.
</Note>

<CardGroup cols={2}>
  <Card title="get_gsc_performance" icon="magnifying-glass-chart">
    Live Google Search Console organic performance. `report="overview"` (default): clicks/impressions/CTR/average position plus a prior-period comparison. `"top_queries"` / `"top_pages"` / `"countries"` / `"devices"`: ranked rows for that dimension. `"trend"`: daily timeseries with window totals. `"opportunities"`: quick-win queries (ranking position 4-15, ≥100 impressions, under 5% CTR). `"position_buckets"`: query-count distribution across top 3 / page 1 / page 2 / beyond 20. Pass `period` (`"7d"`|`"28d"`|`"30d"`|`"90d"`|`"16m"`, default `"30d"`) or an explicit `startDate`+`endDate` (`YYYY-MM-DD`) window — GSC data lags \~3 days.

    **Parameters:** `projectId` (required), `report` (default overview), `period`, `startDate`, `endDate`, `limit` (`top_queries`/`top_pages`/`countries`/`opportunities` only; defaults 25/25/100/20) (all optional except `projectId`)
  </Card>
</CardGroup>

### AI Traffic

<Note>
  First-party AI-traffic measurement from the tracker/CDN pipeline — Searchable's flagship differentiator, distinct from GA4 above (which reads Google's own analytics). This tool requires a crawler-log, CDN, or Searchable-tracker source connected. When none is connected, the backing endpoint returns a `409` error with `{ code: "traffic_not_connected", message, howToFix }` — the `howToFix` field points at **AI Traffic → Setup** in Searchable, where you can connect a source and then retry.
</Note>

<CardGroup cols={2}>
  <Card title="get_ai_traffic" icon="server">
    First-party AI-crawler and AI-referral traffic. `report="overview"` (default): crawler + AI-referral totals by platform, plus the top crawled pages. `"crawlers"`: per-page AI-crawler activity, capped by `limit` (no deep pagination at the tool layer — use the REST endpoint's `offset` for that). `"referrals"`: daily AI-referral session timeseries by platform. `"top_cited_pages"`: top pages driving AI-referral traffic for one platform — `platform` is **required** for this report. `platform` is validated strictly, and an unrecognized value returns an error rather than a silent empty result: `crawlers` and `referrals` accept all eight platform IDs (`openai`, `anthropic`, `google`, `perplexity`, `microsoft`, `deepseek`, `xai`, `meta`), while `top_cited_pages` accepts only the first five. **`overview` ignores `platform` entirely** — it always returns every platform, so filter with `crawlers` or `referrals` instead. Long-tail vendors that can appear in *output* rows (e.g. `amazon`, `you.com`) are deliberately not filterable.

    `overview`, `crawlers`, and `correlation` take `groupBy="platform" | "botCategory" | "botName"`. This extends the same tool rather than adding traffic tools. Omitting it preserves the legacy response; `botCategory` adds `byCategory`, `botName` adds `byBot` (name, ID, vendor, and category), and `platform` adds correlation's per-page `byPlatform` crawl split. Overview and crawlers keep their existing `byPlatform` arrays with every grouping.

    The public bot categories are `ai_training`, `ai_search`, `ai_assistant`, `ai_agent`, and `search_engine`. `ai_assistant` is the distinct user-triggered class for ChatGPT-User, Claude-User, and Perplexity-User. A per-page `ai_training → ai_search → ai_assistant` view is therefore possible, but `ai_search` means retrieval/search crawling — actual answer citation remains a separate visibility metric.

    **Five further reports** cover the rest of the AI Traffic surface:

    * **`sitemap_coverage`** — per bot (named, with vendor and category): how often it fetched your sitemap, how many listed pages it discovered, and the median lag from publish to first crawl. It answers how *fast and how completely* each crawler is picking your sitemap up; it does not enumerate individual uncrawled URLs.
    * **`human`** — real human sessions that arrived FROM an AI assistant, split by platform. The conversion half of the funnel the crawler reports open. **Check `summarySource`:** site-wide totals come from the first-party tracker while the AI figures fall back to GA4 when no tracker is installed — in that split state every `summary` field and `percentOfTotal` are `null` (not `0`), and a failed GA4 read surfaces as `aiReferral.sourceError` rather than as zeroes.
    * **`correlation`** — per page, crawls and AI-referred sessions over the **same window**, joined by path. Its optional grouping classifies the crawl side only. This is co-occurrence, not attribution: nothing establishes that a session followed a crawl or came from a named bot. Ranked by sessions, but the ranked population is the upstream union of the top-500 crawled and top-500 human-traffic pages — `pagesConsidered` is a floor, and `populationTruncated` tells you when pages are certainly missing.
    * **`attribution`** — traffic by UTM tuple, referrer domain, and bot category.
    * **`logs`** — the raw request feed, filterable by `path`, `statusCode`, `botsOnly`, and `host`, cursor-paginated: pass a page's `nextCursor` back as **both** `cursorTimestamp` and `cursorEventId` (half a cursor is rejected rather than silently re-serving page one). Row-capped (25 default, 100 max) because it is the one row-level report. **`ip_address` and `user_agent` are never returned** — the IP is personal data with no analytical value once `country` is present, and the user-agent is superseded by the resolved bot identity.

    **`host`** narrows any report to specific hostnames (comma-separated) — useful on multi-domain projects where "blog versus docs" is the question.

    **Parameters:** `projectId` (required), `report` (default `overview`), `days` (default 30, max 365), `from`/`to`, `host`, `platform` (required for `top_cited_pages`), `groupBy` (`overview`/`crawlers`/`correlation` only), `limit`, plus `path` / `statusCode` / `botsOnly` for `report=logs` (all optional except `projectId`)
  </Card>
</CardGroup>

### Shopping visibility

<Note>
  Requires a plan with **Shopping Analytics** (Scale or higher). `get_shopping_visibility` returns
  `available: false` (not an error) when the project simply has no shopping/product data yet —
  that's a data-presence state, not a plan or connection problem, and needs no setup step. The plan
  gate only surfaces once there's data to fetch.
</Note>

<CardGroup cols={2}>
  <Card title="get_shopping_visibility" icon="cart-shopping">
    AI shopping/product visibility (Peec-parity) — products surfaced in AI shopping/product-carousel responses. `view="summary"` (default): ranked product list (rank, mentions, dominant platform) plus an activation-rate summary. `view="timeseries"`: daily activation-rate/product-count trend. Pass `productId` (the `id` from a summary row — the product's title) for full detail on one product instead: occurrences, vendor/pricing rows, a real per-platform breakdown, and up to 50 recent responses that surfaced it — `productId` overrides `view`. The `summary` view accepts the brand / topic / location filters and echoes `appliedFilters` + `availableFilters`. An interactive Shopping Shelf card also renders automatically beside the answer on the summary view for Apps-capable clients — see below.

    **Parameters:** `projectId` (required), `days` (default 30, max 365), `platform`, `productId` (optional — switches to detail view), `view` (`"summary"` | `"timeseries"`, default summary), `limit` (summary view, default 50, max 100), `offset` (summary view), `topicId`, `unbranded`, `branded`, `country`, `locationId` (all optional except `projectId`)
  </Card>
</CardGroup>

### AI ads

<Note>
  Ad data appears once tracked prompts surface sponsored ad units. An empty result means either that
  nothing has been captured yet **or** that a filter didn't resolve — `topicId`, `locationId` and
  `promptId` take IDs, not names. An unrecognized value is dropped from its filter; only when none
  of a filter's values resolve does that filter match nothing rather than being ignored — a mixed
  list silently returns just the recognized subset. Retry without filters before reporting that a
  project has no ad data, and state that the retried figures are unfiltered. `adCount`/`appearances`
  count ads we scraped in AI answers, not ad-platform impressions, and commercial ad libraries don't
  publish spend, so there are no cost or impression figures.
</Note>

<CardGroup cols={2}>
  <Card title="get_ads" icon="rectangle-ad">
    Sponsored ads surfaced in AI answers. `view="advertisers"` (default): who is running ads, ranked by ad volume with share % and a competitor flag, plus your own brand's ad share-of-voice and rank. `view="creatives"`: the individual ad creatives (advertiser, headline, appearances, prompts) — narrow with `advertiserKey`, `promptId`, or `search`. `view="prompts"`: which tracked prompts surface the most ads, with ad coverage %. `view="timeseries"`: per-advertiser ad-frequency trend.

    **Parameters:** `projectId` (required), `view` (`"advertisers"` | `"creatives"` | `"prompts"` | `"timeseries"`, default advertisers), `days` (default 30, max 365), `filter` (`"all"` | `"competitors"`, view=advertisers), `advertiserKey` (view=creatives/timeseries), `promptId` + `search` (view=creatives), `sortBy` (`promptText`|`adUnits`|`coveragePct`|`engines`|`totalResponses`, default adUnits) + `sortDir` (view=prompts), `limit` (advertisers default 20/max 100, creatives 25/200, prompts 50/500), `platform`, `topicId`, `locationId`, `unbranded`, `branded`, `response_format` (all optional except `projectId`)
  </Card>
</CardGroup>

### Write actions

<Note>
  All three require a grant/key with the **write** scope. None of them execute without `confirm:
      true` — omit it and the tool returns a dry-run preview (what would happen, current quota state)
  with no side effect.
</Note>

<CardGroup cols={2}>
  <Card title="generate_report" icon="share-nodes">
    Generate + publish a shareable report and return its public URL. `reportType`=`sentiment`|`visibility`|`combined` (default sentiment). Note the `Only` suffix on the brand filters here — `unbrandedOnly`/`brandedOnly` — unlike every read tool's `unbranded`/`branded`.

    **Parameters:** `projectId` (required), `reportType`, `timeRange` (`"7d"`|`"30d"`|`"90d"`|`"365d"`, default 30d), `platforms`, `topicIds`, `locationIds`, `unbrandedOnly`, `brandedOnly`, `title`, `status` (`"published"`|`"draft"`, default published), `whiteLabel` (requires a white-label-entitled plan), `confirm` (all optional except `projectId`)
  </Card>

  <Card title="trigger_audit" icon="clipboard-check">
    Start a technical + AEO site audit for the project's pages (all tracked pages by default, or the given `pageIds`); returns the job id. Results land on `get_site_health` (`view="audits"`). Consumes the monthly audit quota and is blocked for pitch projects.

    **Parameters:** `projectId` (required), `pageIds` (optional — omit to audit every tracked page), `confirm` (optional)
  </Card>

  <Card title="refresh_sitemap" icon="sitemap">
    Re-sync the project's configured sitemap — re-discovers it and reconciles monitored pages (add/update/remove) in the background; returns the job id. Read the result with `get_site_health` (`view="pages"`). Blocked for pitch projects.

    **Parameters:** `projectId` (required), `confirm` (optional)
  </Card>
</CardGroup>

### Removed tool names

Earlier iterations of this MCP shipped 35 individual tools. When those were consolidated, 26 of the old names stayed callable as deprecated aliases. **Those aliases were removed in August 2026** — calling one of the names below now returns an unknown-tool error. Use the replacement.

| Removed tool                  | Use instead                                                   |
| ----------------------------- | ------------------------------------------------------------- |
| `get_visibility_summary`      | `get_visibility` (`group_by: "summary"`)                      |
| `get_visibility_details`      | `get_visibility` (`group_by: "platform"`)                     |
| `get_visibility_by_topic`     | `get_topic_analysis`                                          |
| `get_visibility_by_prompt`    | `get_visibility` (`group_by: "prompt"`)                       |
| `get_visibility_by_location`  | `get_visibility` (`group_by: "location"`)                     |
| `get_prompt_topics`           | `get_topic_analysis`                                          |
| `get_sources`                 | `search_sources`                                              |
| `get_source_domain`           | `get_source_detail` (`level: "domain"`)                       |
| `get_source_domain_urls`      | `get_source_detail` (`level: "domain", include: "urls"`)      |
| `get_source_domain_responses` | `get_source_detail` (`level: "domain", include: "responses"`) |
| `get_source_urls`             | `get_source_detail` (`level: "url"`)                          |
| `get_source_page`             | `get_source_detail` (`level: "url"`, with `url`)              |
| `get_source_page_content`     | `get_source_detail` (`level: "url", include: "content"`)      |
| `get_source_content_types`    | `get_source_trends` (`breakdown: "content-types"`)            |
| `get_source_types`            | `get_source_trends` (`breakdown: "source-types"`)             |
| `get_source_article_types`    | `get_source_trends` (`breakdown: "article-types"`)            |
| `get_source_trend`            | `get_source_trends` (`breakdown: "trend"`)                    |
| `get_prompt_query_fanout`     | `get_query_fanout` (use `prompt_id`, not `promptId`)          |
| `get_ga4_ai_referrals`        | `get_ga4_traffic` (`report: "ai_referrals"`)                  |
| `get_ga4_traffic_trend`       | `get_ga4_traffic` (`report: "trend"`)                         |
| `get_ga4_top_pages`           | `get_ga4_traffic` (`report: "top_pages"`)                     |
| `get_ga4_traffic_sources`     | `get_ga4_traffic` (`report: "sources"`)                       |
| `get_website_issues`          | `get_site_health` (`view: "issues"`)                          |
| `get_page_audits`             | `get_site_health` (`view: "audits"`)                          |
| `get_monitored_pages`         | `get_site_health` (`view: "pages"`)                           |
| `get_issue_details`           | `get_site_health` (`view: "issue_details"`)                   |

Nine other pre-consolidation names kept their exact original name as a primary tool at the time. Six still do — `list_projects`, `list_articles`, `get_article`, `get_opportunities`, `get_sentiment`, and `get_query_fanout`; the other three (`get_visibility_history`, `get_sentiment_history`, `get_sentiment_competitors`) were folded into selector views in August 2026 and are now deprecated names — see the next section.

<Note>
  The four `get_source_*` breakdown rows above are the one case where the removed name did something
  no MCP tool now does: `breakdown` (and `get_source_trend`'s `group`) were arguments only those
  aliases accepted. `get_source_trends` returns the top-source-domain trend directly and has no
  `breakdown` argument. For the content-type, source-type, or article-type breakdown, use the [REST
  endpoint](/advanced/api-usage).
</Note>

### Deprecated tool names

The August 2026 family-grid consolidation folded four reads into selector parameters on their
family's primary tool. Unlike the removed aliases above, these four names **still work** — same
handler, byte-identical payload — so existing scripts and scheduled jobs keep running. They are
scheduled for removal **after 2026-10-15** (the same \~60-day window the removed aliases got);
move to the replacement call before then. The REST endpoints behind these reads
(`GET /visibility/history`, `GET /sentiment/history`, `GET /sentiment/competitors`,
`GET /domain-authority`) are **not** deprecated — the [12-month REST deprecation
policy](/changelog#deprecation-policy) governs those, and nothing REST-side is scheduled for
removal.

| Deprecated name             | Use instead                                         |
| --------------------------- | --------------------------------------------------- |
| `get_visibility_history`    | `get_visibility` (`group_by: "date"`)               |
| `get_sentiment_history`     | `get_sentiment` (`view: "history"`)                 |
| `get_sentiment_competitors` | `get_sentiment` (`view: "competitors"`)             |
| `get_domain_authority`      | `get_brand_profile` (`include: "domain_authority"`) |

## Interactive cards

<Info>
  Nine tools render an interactive card beside their answer in **MCP-Apps-capable hosts** (Claude
  web/desktop, ChatGPT, Goose, VS Code — all implement the same standard):

  | Tool                      | Card                    | What it shows                                                                                                     |
  | ------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------- |
  | `get_visibility`          | **Visibility Snapshot** | Score, trend, and per-platform bars, with brand/topic/location controls in fullscreen                             |
  | `get_share_of_voice`      | **Share of Voice**      | Your own share as a headline number, its movement, and bars showing where the rest of the share sits              |
  | `get_competitors`         | **Competitor Ranking**  | List call → brand + competitors in one ranked table; `competitorId` call → the head-to-head split comparison      |
  | `get_sentiment`           | **Sentiment Profile**   | Model selector across engines, the selected engine's brand-perception write-up, and its positive/neutral/negative |
  | `get_topic_analysis`      | **Topic Analysis**      | A visibility bar per topic, expandable to that topic's strengths, gaps, and top rival                             |
  | `get_source_trends`       | **Sources**             | Top-domains citation-trend chart, expanding to the cited-URLs table                                               |
  | `get_site_health`         | **Site Audit**          | Server-paginated page inventory with search + per-page **Audit** and **Sync website** buttons, or ranked findings |
  | `list_prompts`            | **Prompts**             | Compact prompt table inline; full metrics table with topic/branded filters and sortable columns in fullscreen     |
  | `get_shopping_visibility` | **Shopping Shelf**      | Product image, title, price, rating, and platform (summary view only)                                             |

  `get_visibility` hides its card on a `platform`-filtered call and on `group_by=prompt|location`,
  where the card's numbers would misrepresent the answer. Hosts without MCP Apps support just see the
  normal text/JSON answer. Cards are dark/light aware and have an Expand button for a fullscreen view.
  Where a card has a period control you switch window inside the card rather than sending another
  message — the card queries the tool itself and remembers each window it has already loaded, so
  returning to one is instant.
</Info>

## Pagination

Tools that return lists are paginated so you can read complete datasets without silent truncation.

### Per-prompt visibility

`get_visibility` with `group_by: "prompt"` is fully paginated (the deprecated `get_visibility_by_prompt` alias maps to the same call). Every response includes a `pagination` object that tells you whether more prompts exist and exactly how to fetch the next page:

```json theme={null}
{
  "pagination": {
    "limit": 100,
    "offset": 0,
    "returnedCount": 100,
    "totalCount": 740,
    "hasMore": true,
    "nextOffset": 100
  }
}
```

Loop until you've read every prompt:

1. Call `get_visibility` with `group_by: "prompt"` (defaults: `limit` 100, `offset` 0).
2. While `pagination.hasMore` is `true`, call again with `offset` = `pagination.nextOffset`.
3. Stop when `pagination.hasMore` is `false`.

`limit` accepts 1–500 prompts per page. The total number of prompts you can page through is unbounded — paging in fixed-size chunks scales to projects with thousands of prompts.

### Other list tools

Other list-returning tools paginate too: `get_opportunities`, `search_sources`, `get_source_detail` (`include: "responses"`), and `get_query_fanout` (per-prompt list) use `limit` + `offset`, while `get_source_detail` (`include: "urls"`, or `level: "url"` for the flat cross-project listing) is cursor-based (pass the `cursor` from the previous response until none is returned). `get_prompt_answers` also returns the standard `limit`/`offset`/`hasMore`/`nextOffset` pagination shape, but the tool layer only exposes `limit` (max 50) — use the REST endpoint's `offset` for deep pagination.

## Example prompts

### Visibility analysis

```
What's my brand's AI visibility score for the last 30 days?
```

```
How does my brand perform on ChatGPT vs Claude vs Perplexity?
```

```
Which topics have the weakest visibility? Show me the top 5.
```

### Site audit

```
What critical AEO issues does my site have and how do I fix them?
```

```
Show me the pages with the lowest AEO scores.
```

### Content

```
List all my draft articles.
```

```
Get the full content of my latest published article for review.
```

## Use cases

### Automated issue fixing

Pair the MCP with a coding assistant to retrieve AEO/SEO issues and apply fixes in your codebase:

```
Get all critical issues for my project and propose fixes in this repo
```

The assistant fetches issues from Searchable (missing meta descriptions, alt text, heading structure, etc.), locates the relevant files, and proposes or applies fixes.

### For developers

* Query AEO/SEO issues while debugging frontend code
* Check if code changes would impact page audit scores
* Review article content without leaving your IDE

### For AEO/SEO teams

* Pull visibility summaries during standups
* Extract audit data for reports
* Track issue resolution progress

### For content teams

* Review article status and metadata
* Pull full article content for editing
* Track content coverage across topics

## Troubleshooting

<AccordionGroup>
  <Accordion title="Connection failed or 403 Forbidden">
    * Confirm the server URL is exactly `https://app.searchable.com/api/mcp-server/mcp` (browser-sent `Origin` headers are validated against an allowlist — hosted connectors normally call server-side without one, so they're unaffected).
    * Check your API key starts with `sea_` and is enabled for MCP in Searchable settings.
    * Restart your MCP client after config changes.
  </Accordion>

  <Accordion title="The browser consent page won't complete">
    * Make sure you're logged in to Searchable in the same browser, then retry the connection from
      your client. - The authorization link is single-use and expires after a few minutes — if you left
      it open, restart the connection from your client to get a fresh one. - On the consent screen,
      select at least one project (or leave all selected) before clicking **Authorize**.
  </Accordion>

  <Accordion title="Legacy API key rejected (401)">
    * Confirm the key starts with `sea_` and hasn't been revoked in **Settings → Integrations**. -
      Confirm the key's workspace is on a paid Searchable plan — MCP is unavailable on the Free plan.
  </Accordion>

  <Accordion title="No projects returned">
    * Confirm you have projects in your Searchable account. - Check that the API key belongs to the
      correct workspace.
  </Accordion>

  <Accordion title="get_ga4_traffic fails with 'ga4_not_connected'">
    * The project has no GA4 property linked, so the tool call returns a `409` error with `code:
        "ga4_not_connected"`. - Connect Google Analytics 4 under **Settings → Integrations** in your
      Searchable dashboard (the error's `howToFix` field carries the direct link), then retry the tool
      call.
  </Accordion>

  <Accordion title="get_gsc_performance fails with 'gsc_not_connected'">
    * The project has no Google Search Console site linked, so the tool call returns a `409` error
      with `code: "gsc_not_connected"`. - Connect Google Search Console under **Settings →
      Integrations** in your Searchable dashboard (the error's `howToFix` field carries the direct
      link), then retry the tool call.
  </Accordion>

  <Accordion title="get_ai_traffic fails with 'traffic_not_connected'">
    * The project has no crawler-log, CDN, or Searchable-tracker source connected, so the tool call
      returns a `409` error with `code: "traffic_not_connected"`. - Connect a source under **AI Traffic
      → Setup** in your Searchable dashboard (the error's `howToFix` field carries the direct link),
      then retry the tool call.
  </Accordion>

  <Accordion title="get_shopping_visibility fails with 'plan_upgrade_required'">
    * The workspace's plan doesn't include **Shopping Analytics** (Scale or higher), and the project
      has shopping data to fetch, so the tool call returns a `403`-equivalent error with `code:
        "plan_upgrade_required"`. - This is different from `available: false` — that's a normal 200
      response meaning the project just has no shopping data yet, not a plan restriction. If you're
      seeing `available: false` instead of this error, no upgrade is needed. - Upgrade to a plan with
      Shopping Analytics in **Settings → Billing**, then retry.
  </Accordion>

  <Accordion title="A tool returns 'rate_limited' or 'query_timeout'">
    Both are retryable, and neither means you've exhausted a quota. `rate_limited` means another heavy
    read for the **same project** is still running — the limit is per project, not per key, so wait a
    moment and retry, and avoid firing many project-wide tools in parallel for one project.
    `query_timeout` means the aggregation ran past the server-side limit — reduce the `days` window or
    add filters (`platform`, `topicId`, `country`), then retry.
  </Accordion>

  <Accordion title="A write tool (generate_report, trigger_audit, refresh_sitemap) returns 'missing_scope'">
    * The connected grant or API key only has **read** access. Write tools require the **write**
      scope. - Re-authorize with **Read & write** selected on the consent screen, or create/edit an API
      key with read-and-write access in **Settings → Integrations**.
  </Accordion>

  <Accordion title="Tools not appearing">
    * Restart your MCP client.
    * Verify the server URL and JSON config syntax.
    * Check the client's MCP logs for errors.
  </Accordion>
</AccordionGroup>

## Security

### Read-only by default, write tools scope-gated

Every tool except the 3 write tools is annotated `readOnlyHint: true`: they can only list and read data. The 3 write tools (`generate_report`, `trigger_audit`, `refresh_sitemap`) additionally require a grant or key with the **write** scope, and each still needs an explicit `confirm: true` on the individual call before it does anything — a read-only connection can't invoke them at all (the call fails with `missing_scope`), and even a read-and-write connection gets a no-op dry-run preview until it passes `confirm: true`.

### Token & key handling

* The OAuth login flow issues your client an access token scoped to exactly the projects and read/write access you approved on the consent screen — no API key is created or stored for this path.
* Every request is re-checked against the server-side authorization grant, so a revoked grant stops working immediately — the token in the client's hands can't outlive the consent it came from.
* Tokens refresh silently while the connection is in use; active connections persist, and an idle connection expires about 60 days after its last use, after which you simply re-authorize.
* On the legacy API-key path, the `sea_` key **is** the bearer token — treat it as a secret, scope it narrowly, and revoke it in **Settings → Integrations** if it's exposed.

### Origin validation

The server validates browser `Origin` headers against an allowlist (`claude.ai`, `claude.com`, plus dev hosts) to prevent cross-site abuse. Requests without an `Origin` header are allowed through — that covers native clients (Claude Desktop, Cursor, curl) **and** hosted connectors like Claude.ai and ChatGPT, which call from their servers rather than a browser. This is defense-in-depth; bearer-token auth is the primary control.

### Best practices

* **Rotate keys regularly** — generate new keys periodically and revoke old ones.
* **Use per-use-case keys** — separate keys for separate clients makes revocation targeted.
* **Revoke unused keys** — remove keys you're no longer using from Searchable settings.

## API reference

### Server details

| Property       | Value                                                   |
| -------------- | ------------------------------------------------------- |
| Endpoint       | `https://app.searchable.com/api/mcp-server/mcp`         |
| Transport      | Streamable HTTP                                         |
| Protocol       | MCP 1.0                                                 |
| Authentication | OAuth 2.1 + PKCE, Client ID Metadata Documents, and DCR |

### Rate limits

Searchable does not enforce a per-key request-rate limit today, but two server-side controls can make a call fail retryably:

* **Per-project concurrency.** Heavy aggregation reads take a per-project lock, so a second heavy call for the same project while the first is still running is rejected rather than queued. On the REST API that's a `429` with a `Retry-After` header; MCP tools have no header channel, so the tool returns the error code `rate_limited` with retry guidance in `howToFix`. Wait a moment and retry — the limit is per project, not per key.
* **Query timeout.** Long aggregations are bounded by a server-side timeout, returning `query_timeout` with a hint to narrow the `days` window or add filters.

Both are retryable. Normal interactive usage from Claude, Cursor, or similar clients is not expected to hit either; parallel fan-out across many tools for the same project is the usual trigger.

## Support

<CardGroup cols={2}>
  <Card title="All integrations" icon="book" href="/integrations/overview">
    Browse the integrations directory
  </Card>

  <Card title="Contact support" icon="headset" href="mailto:support@searchable.com">
    Email our team
  </Card>
</CardGroup>
