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

# OtterlyAI MCP Server

> Use OtterlyAI with Model Context Protocol (MCP)–compatible clients

The [Model Context Protocol](https://modelcontextprotocol.io) (MCP) lets AI
assistants like Claude Code, Claude Desktop, Cursor, and other compatible
clients call external tools in a structured way. The OtterlyAI MCP server exposes
your brand reports, prompts, citations, recommendations, and audit checks as
MCP tools so your assistant can answer questions about your AI search
visibility directly.

## Endpoint

```
https://data.otterly.ai/mcp
```

The server speaks the MCP **streamable HTTP** transport. Authentication is
handled entirely through **OAuth 2.0** — see below.

## Authentication

The OtterlyAI MCP server authenticates with **OAuth 2.0**. You do **not** pass
an API key. When you add the server to a compatible client, the client walks you
through a standard browser sign-in to OtterlyAI and obtains an access token on
your behalf. Permissions and workspace scoping match your OtterlyAI account.

<Note>
  The `oai_live_` API keys documented under
  [Authentication](/authentication) are for the **REST Public API only**. The
  MCP server does not accept them — it is OAuth-only.
</Note>

The flow is fully automatic for MCP clients: there is nothing to configure by
hand. Under the hood the server implements OAuth 2.0 with dynamic client
registration and PKCE, and advertises discovery metadata at:

```
https://data.otterly.ai/.well-known/oauth-protected-resource/mcp
https://data.otterly.ai/.well-known/oauth-authorization-server
```

When a request arrives without a valid token, the server responds `401` with a
`WWW-Authenticate` header pointing at the protected-resource metadata, which is
the client's cue to start the OAuth flow.

## Quick sanity check

You don't need a token to confirm the server is up and correctly OAuth-gated.
An unauthenticated request returns `401` with a `WWW-Authenticate` header:

```bash theme={null}
curl -i https://data.otterly.ai/mcp -X POST \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
```

You should see `HTTP/1.1 401` and a header of the form
`WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://data.otterly.ai/.well-known/oauth-protected-resource/mcp"`.

Fetching that metadata document returns the resource and its authorization
server:

```bash theme={null}
curl https://data.otterly.ai/.well-known/oauth-protected-resource/mcp
```

## Claude.ai

In **Customize → Connectors**, choose **Add connector / "+" sign**, search for OtterlyAI, and click **Connect**:

```
https://data.otterly.ai/mcp
```

A browser window opens to sign in to OtterlyAI and authorize access. Once
approved, the OtterlyAI tools are available in your chats.

Claude.ai holds the resulting token itself, so it never shares credentials with
another client on your machine.

## Claude Code

```bash theme={null}
claude mcp add --transport http otterly https://data.otterly.ai/mcp
```

The first time a tool is used, Claude Code opens a browser to complete the
OAuth sign-in. Verify with `claude mcp list` — you should see
`otterly: ... - ✓ Connected`. Tools are then available as
`mcp__otterly__<tool_name>`.

Claude Code holds the resulting token itself, so it never shares credentials
with another client on your machine.

## Claude Desktop

Add a remote connector pointing at the endpoint; Claude Desktop runs the OAuth
flow in your browser on first connect. If your version launches stdio servers
only, use the [`mcp-remote`](https://github.com/geelen/mcp-remote) bridge — it
performs the OAuth handshake for you, so **no static `Authorization` header is
needed**. Add the following to
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):

```json theme={null}
{
  "mcpServers": {
    "otterly": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://data.otterly.ai/mcp"]
    }
  }
}
```

Restart Claude Desktop; a browser opens to authorize, and the OtterlyAI tools
appear in the tool picker.

<Warning>
  If you also run Cursor, a second Claude Desktop profile, or any other
  `mcp-remote` client on this machine, the snippet above is not enough — see
  [Running more than one client](#running-more-than-one-client-on-one-machine).
</Warning>

## Cursor

In **Cursor → Settings → MCP**, add a new server using the same `mcp-remote`
bridge (the OAuth flow opens in your browser on first use):

```json theme={null}
{
  "mcpServers": {
    "otterly": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://data.otterly.ai/mcp"]
    }
  }
}
```

<Warning>
  If you also run Claude Desktop or any other `mcp-remote` client on this
  machine, the snippet above is not enough — see
  [Running more than one client](#running-more-than-one-client-on-one-machine).
</Warning>

## Running more than one client on one machine

The `mcp-remote` bridge stores every credential in `~/.mcp-auth`, keyed by
server URL, regardless of which client started it. Two clients pointed at the
same URL therefore share a single login: whichever one authorizes last evicts
the other, which then opens its own browser tab to authorize and evicts the
first. The symptom is an authorization tab that keeps reappearing and a client
that reports no connection to OtterlyAI.

Separate application folders do not separate these credentials — two Claude
Desktop profiles hit this too. Give each client its own store and its own
callback port.

Claude Desktop:

```json theme={null}
{
  "mcpServers": {
    "otterly": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://data.otterly.ai/mcp", "9696"],
      "env": { "MCP_REMOTE_CONFIG_DIR": "/Users/you/.mcp-auth-claude" }
    }
  }
}
```

Cursor:

```json theme={null}
{
  "mcpServers": {
    "otterly": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://data.otterly.ai/mcp", "9697"],
      "env": { "MCP_REMOTE_CONFIG_DIR": "/Users/you/.mcp-auth-cursor" }
    }
  }
}
```

Use an absolute path for `MCP_REMOTE_CONFIG_DIR`; `~` is not expanded. Then quit
every client, delete the shared `~/.mcp-auth`, and authorize one client at a
time.

This is also how you connect two different OtterlyAI accounts on one machine —
with one shared store both clients end up on whichever account authorized last.

## Available tools

Every tool maps 1:1 to an endpoint in the
[Public API OpenAPI spec](https://data.otterly.ai/v1/openapi.json). Required
arguments match the API; optional arguments are listed below the table.

### Workspaces & engines

| Tool                     | Required arguments | Description                                                                                           |
| ------------------------ | ------------------ | ----------------------------------------------------------------------------------------------------- |
| `list_workspaces`        | —                  | List all workspaces accessible by the API key.                                                        |
| `list_workspace_tags`    | `workspaceId`      | List tags defined for a workspace.                                                                    |
| `list_workspace_prompts` | `workspaceId`      | List the prompts monitored in a workspace. `id` is the identifier accepted by the prompt write tools. |
| `list_engines`           | —                  | List available AI engines and their supported countries. Optional filters: `country`, `engine`.       |

### Brand reports

| Tool                                    | Required arguments                                        | Description                                                                                                                                      |
| --------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `list_brand_reports`                    | —                                                         | List brand reports accessible by the API key. Optional `workspaceId` scopes to a single workspace.                                               |
| `get_brand_report`                      | `reportId`                                                | Get a brand report's details, including competitors, countries, and tags.                                                                        |
| `get_brand_report_stats`                | `reportId`, `startDate`, `endDate`, `country`             | Aggregated stats — brand coverage history, brand ranks with sentiment, top domains, and engine breakdown.                                        |
| `list_brand_report_prompts`             | `reportId`, `startDate`, `endDate`, `country`             | List prompts with brand and domain mention stats for a date range and country.                                                                   |
| `get_brand_report_prompt`               | `reportId`, `promptId`, `startDate`, `endDate`, `country` | Detailed metrics for a single prompt — brand coverage history, ranks, sentiment, domain categories, tags.                                        |
| `list_brand_report_prompt_ai_responses` | `reportId`, `promptId`, `startDate`, `endDate`, `country` | List AI response runs collected for a prompt — raw model content, brand mentions, and citations.                                                 |
| `list_brand_report_citations`           | `reportId`, `startDate`, `endDate`, `country`             | List cited URLs for a brand report, filterable by AI engine, tag, and search query.                                                              |
| `get_brand_report_citation_stats`       | `reportId`, `startDate`, `endDate`, `country`             | Citation stats — top cited domains, share-of-citations, and most-cited URLs over a date window.                                                  |
| `list_brand_report_citation_prompts`    | `reportId`, `url`, `startDate`, `endDate`, `country`      | List prompts that cited a specific URL within a brand report over a date window.                                                                 |
| `get_brand_report_citation_history`     | `reportId`, `url`, `startDate`, `endDate`, `country`      | Get Citation history for a cited URL — total citations, the change vs the previous period (`percentageChange`), and a daily breakdown by engine. |
| `list_brand_report_recommendations`     | `reportId`, `country`                                     | List AI-generated recommendations for improving brand visibility, optionally filtered by `engine`.                                               |

### Agent analytics

AI crawler and agent traffic hitting the brand report's own domains — the
inverse of citations, which are what the engines said about your brand. Same
data as the Agent Analytics tab in the app.

| Tool                            | Required arguments | Description                                                                                                                                                                        |
| ------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_brand_report_agent_stats`  | `reportId`         | Overview of agent traffic — total agent visits, distinct pages visited, the busiest engine, per-engine totals, and a per-(day, engine, bot) visit series (`trend`).                |
| `list_brand_report_agent_pages` | `reportId`         | Per-URL breakdown, busiest page first, with a per-engine split. Supports `search` on the page URL.                                                                                 |
| `list_brand_report_agents`      | `reportId`         | Per-agent breakdown — agent name, its engine, its category (`On-Demand AI Fetcher`, `Search Index`, `AI Training / Data Scraper`, or `Other`), and visit count. Supports `search`. |

Unlike the other brand-report tools, `startDate` and `endDate` are **optional**
here and default to the last 14 days (UTC) — the window the app shows by
default. There is no `country` argument: crawler traffic is attributed to a URL
and a user-agent, and neither carries a country.

`get_brand_report_agent_stats` speaks one engine vocabulary throughout —
`chatgpt`, `claude`, `copilot`, `perplexity`, `google` (Gemini included),
`other`. That holds for both the summary (`topEngine`, per-engine totals) and the
`trend` series, whose rows are `{ dayId, engine, bot, visitCount }`. `bot` is the
raw crawler name (`GPTBot`, `Google-Extended`), so one engine can appear across
several bots on a day — e.g. `google` for both `GoogleOther` and
`Google-Extended`.

Every response carries an `availability` field. Check it before reading the
numbers:

| `availability`        | Meaning                                                                                                                                           |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `not_connected`       | No logs connector is set up for this report. Metrics are **`null`, not zero** — nothing is being measured. Connect a log source in the app first. |
| `connected_no_data`   | A connector is active but matched no visits in this window. The zeros are real.                                                                   |
| `connected_with_data` | Populated payload.                                                                                                                                |

Treating `not_connected` as zero is the mistake worth avoiding: it reads as "no
AI crawler visited this site" when the truth is that nobody is measuring.

### GEO audits

| Tool                       | Required arguments       | Description                                                                                          |
| -------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `list_crawlability_checks` | `workspaceId`            | List crawlability audit checks (robots.txt analysis, bot access) for a workspace.                    |
| `get_crawlability_check`   | `checkId`, `workspaceId` | Get a crawlability check's full results.                                                             |
| `list_content_checks`      | `workspaceId`            | List content / AI-readiness checks for a workspace.                                                  |
| `get_content_check`        | `checkId`, `workspaceId` | Get a content check's full results — AI readiness, PageSpeed, structural and dynamic-content scores. |

### Query fan-outs

| Tool                  | Required arguments        | Description                                                                                                                           |
| --------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `list_query_fan_outs` | `workspaceId`             | List query fan-out runs for a workspace — each run expands an original query into the related queries AI search engines may generate. |
| `get_query_fan_out`   | `fanOutId`, `workspaceId` | Get a query fan-out run and its expanded queries (query, type, user intent, reasoning) split per AI engine.                           |

### Write tools (`write` permission only)

| Tool                        | Required arguments                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_crawlability_check` | `workspaceId`, `url`                | Create a new crawlability audit for a URL.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `create_content_check`      | `workspaceId`, `url`                | Create a new content / AI-readiness check. Optional: `crawlerIdentity` (crawler user-agent to test the page against), `sendOtterlyHeader` (send identity header).                                                                                                                                                                                                                                                                                                                                                                 |
| `create_query_fan_out`      | `workspaceId`, `query`              | Generate a new query fan-out — expands an original query into related queries across AI engines. Runs asynchronously; poll `get_query_fan_out` until status is `completed` or `failed`.                                                                                                                                                                                                                                                                                                                                           |
| `create_prompts`            | `workspaceId`, `country`, `prompts` | Create one or more prompts in a workspace and start monitoring them, exactly like creation in the UI. Optional: `tagIds`, `brandReportIds`. Up to 5 prompts are created synchronously and returned with their IDs; larger batches are processed asynchronously and return a processing summary instead — use `list_workspace_prompts` afterward to see what was created. The whole batch is validated against the workspace's remaining prompt allocation up front. A prompt's `text` and `country` are immutable after creation. |
| `delete_prompt`             | `workspaceId`, `promptId`           | Delete a prompt. It's removed from any brand report it's part of, and affected brand reports are recalculated without it (asynchronously).                                                                                                                                                                                                                                                                                                                                                                                        |
| `update_prompt_tags`        | `workspaceId`, `promptId`, `tagIds` | Replace the tags assigned to a prompt with the given set (pass `[]` to remove all tags). Only tags can be changed — attempting to change a prompt's text or country isn't supported; create a new prompt and delete the old one instead.                                                                                                                                                                                                                                                                                          |
| `create_tag`                | `workspaceId`, `name`, `color`      | Create a new tag in a workspace. Tags can be assigned to prompts with `update_prompt_tags`.                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `delete_tag`                | `workspaceId`, `tagId`              | Delete a tag. It's unassigned from all prompts asynchronously; brand reports filtered by it update accordingly.                                                                                                                                                                                                                                                                                                                                                                                                                   |

These tools are only registered when your account has `write` permission; with
read-only access they are not advertised by the server.

### Common arguments

* **Dates**: `startDate` and `endDate` are inclusive. Accept `YYYY-MM-DD` or a
  full ISO timestamp; only the date portion is used.
* **Country**: `country` is a lowercase ISO 3166-1 alpha-2 code (e.g. `us`,
  `de`). Use `uk` for the United Kingdom.
* **Engines**: `engines` (multi-value) and `engine` (single-value) accept
  `chatgpt`, `claude`, `google`, `perplexity`, `copilot`, `google_ai_mode`,
  `gemini`. Call `list_engines` to see which engines are available in which
  countries.
* **Engines on the agent-analytics tools**: single-value — `chatgpt`, `claude`,
  `copilot`, `perplexity`, `google`, `other`. `google` covers the whole Google
  family (Gemini included), unlike the app which lists Gemini separately. Pass
  `engine=other` to match every AI crawler outside the named engines (Amazon,
  Meta, xAI, ByteDance, Common Crawl, and so on). `other` also appears in the
  response breakdown, so it means the same thing in and out. The agent name in
  `list_brand_report_agents` is always exact, so `other` never hides which
  crawler it was.
* **Pagination**: list tools accept `page` (default `1`) and `pageSize`
  (default `50`, max `100`).
* **Extra filters on `list_brand_report_prompts` and `list_brand_report_citations`**:
  `engines`, `tagId`, `search`, `sortBy`, `sortOrder`.
* **`search` on the agent-analytics list tools** is a case-insensitive substring
  match, applied server-side across the whole result set. It is matched
  literally — `%` and `_` are not wildcards.

## Typical conversation flow

1. `list_workspaces` → pick a `workspaceId` (optional).
2. `list_brand_reports` → pick a `reportId`.
3. `list_brand_report_prompts` or `list_brand_report_citations` with
   `reportId`, a date range, and a country code (e.g. `us`, `gb`, `de`).
4. Drill into `get_brand_report_stats`, `get_brand_report_citation_stats`,
   `list_brand_report_recommendations`, or the GEO audit tools as needed.
5. To ask about AI crawlers on your own site rather than what the engines said,
   start at `get_brand_report_agent_stats` with just the `reportId`, then drill
   into `list_brand_report_agent_pages` or `list_brand_report_agents`.

## Troubleshooting

**The authorization tab keeps reopening, or the client reports no connection to
OtterlyAI.** Two clients are sharing one `mcp-remote` credential store — see
[Running more than one client](#running-more-than-one-client-on-one-machine).
Reconnecting repairs one client and breaks the other, so the loop survives any
number of reconnects.

**"state already in use".** A connection link is single-use and retrying the
same one lands here. Wait about ten minutes, then start the connection again
from the client rather than reloading the page.

**The wrong OtterlyAI account got connected.** Approval is granted by whichever
account the browser is already signed into; the client has no say in it. To
connect a specific account, sign that account in first — a separate browser
profile per account is the reliable way — and then authorize.

**`401` from a tool call.** The OAuth token is missing or expired; reconnect to
refresh it. If reconnecting works and then fails again within a day, it is the
shared-credential case above rather than an expiry.

**`403` from a tool call.** Your account lacks permission for the requested
workspace.

## Things to know

* Most tools are **read-only**. Write tools (`create_crawlability_check`,
  `create_content_check`, `create_query_fan_out`, `create_prompts`,
  `delete_prompt`, `update_prompt_tags`, `create_tag`, `delete_tag`) are only
  available to accounts with the `write` permission.
* Workspace scoping matches your OtterlyAI account. See
  [Troubleshooting](#troubleshooting) for `401` and `403` responses.
* The OpenAPI spec at
  [`https://data.otterly.ai/v1/openapi.json`](https://data.otterly.ai/v1/openapi.json)
  is the source of truth for the underlying data shapes returned by each tool.

<Tip>
  Hitting a bug or missing a tool you'd like to see? Email
  [support@otterly.ai](mailto:support@otterly.ai).
</Tip>
