# MCP server and clients (/mcp)



Sondo speaks the Model Context Protocol, so an AI client can work with your workspace from wherever
you already are: read the conversations your widget had overnight, reply to one as yourself, add a
page to the Knowledge Base, or change what the assistant is allowed to say.

The one endpoint every client connects to is `https://usesondo.com/api/mcp`.

The tools cover the work the dashboard does day to day: conversations, the Knowledge Base,
connected sites, the workspace and widget settings, the API keys and the model spend. Billing,
creating a workspace, issuing a key and the outbound webhook stay in the dashboard.

Nothing here takes a workspace argument. The key you connect with decides which workspace you are
in, and it cannot reach another one.

## Get a key first [#get-a-key-first]

Every client authenticates with its own API key from the Integrations page in your dashboard. It
starts with `sondo_sk_`, it is shown once, and you can revoke it without touching your other
clients. See [API keys and plans](/api-keys-and-plans) for how to issue one.

There is no OAuth. The key travels as a bearer token on the `Authorization` header, which is why
every command below carries one.

## Connect your client [#connect-your-client]

Replace the placeholder key in each command with your own.

### Claude Code [#claude-code]

Run this once:

```bash
claude mcp add --scope user --transport http sondo https://usesondo.com/api/mcp --header "Authorization: Bearer sondo_sk_…"
```

Issue the key under Settings > Integrations first. It is shown once.

### Claude Desktop [#claude-desktop]

Settings → Developer → Edit Config (claude\_desktop\_config.json), then restart the app.

```json
{
  "mcpServers": {
    "sondo": {
      "type": "http",
      "url": "https://usesondo.com/api/mcp",
      "headers": { "Authorization": "Bearer sondo_sk_…" }
    }
  }
}
```

### Cursor [#cursor]

Add this to your Cursor MCP settings file:

```bash
{
  "mcpServers": {
    "sondo": {
      "type": "http",
      "url": "https://usesondo.com/api/mcp",
      "headers": { "Authorization": "Bearer sondo_sk_…" }
    }
  }
}
```

Paste your key from Settings > Integrations, then enable Sondo in Cursor.

### VS Code [#vs-code]

Add this to .vscode/mcp.json:

```bash
{
  "mcpServers": {
    "sondo": {
      "type": "http",
      "url": "https://usesondo.com/api/mcp",
      "headers": { "Authorization": "Bearer sondo_sk_…" }
    }
  }
}
```

Paste your key from Settings > Integrations, then start the server.

### Codex CLI [#codex-cli]

Run this once:

```bash
codex mcp add sondo --url https://usesondo.com/api/mcp --bearer-token-env-var SONDO_API_KEY
```

Export SONDO\_API\_KEY with your key from Settings > Integrations, then start codex.

Codex reads that environment variable when it starts, not when you export it, so export it and then
start Codex, in that order.

### Gemini CLI [#gemini-cli]

Run this once:

```bash
gemini mcp add --transport http --header "Authorization: Bearer sondo_sk_…" sondo https://usesondo.com/api/mcp
```

Issue the key under Settings > Integrations first. It is shown once.

### Anything else [#anything-else]

Point it at the endpoint and send the key on this header:

```bash
https://usesondo.com/api/mcp
Authorization: Bearer sondo_sk_…
```

Issue the key under Settings > Integrations. It is shown once.

Chat products that connect through their own interface, including Claude, ChatGPT, Grok and Meta
Muse, need the same two things: the endpoint, and the key on that header. Where their interface puts
those fields is their business, and it changes often enough that this page will not guess.

## Check that it worked [#check-that-it-worked]

Ask your client to use Sondo's `ping` tool by name. It answers with the workspace it is connected
to, which is the fastest way to tell a working key from a working connection to the wrong workspace.

Naming the tool matters. Asked loosely, a coding agent will often reach for `curl` instead, which
proves only that the endpoint is reachable.

## Tool reference [#tool-reference]

Generated from the server, so it cannot drift from what your client will actually find.

An unknown id, or one from another workspace, comes back as "not found" rather than an error about
permission, because a permission error would confirm that the thing exists.

Sondo exposes 27 tools.

### Conversations [#conversations]

Read what visitors asked, reply as the operator, and keep the list tidy.

#### `conversation_reply` [#conversation_reply]

Write your own reply into a conversation, as the operator. The visitor sees it within a few seconds. This is not a takeover: the assistant still answers their next message, having read your reply. Pause it first with conversation\_set if you want the thread.

| Argument  | Type   | Notes                 |
| --------- | ------ | --------------------- |
| `id`      | uuid   |                       |
| `content` | string | 1 to 4,000 characters |

#### `conversation_set` [#conversation_set]

Set a conversation's star, block its visitor, or pause the assistant in it. Pass at least one of starred, visitorBlocked or aiPaused. Read and archived belong to set\_read and set\_archived.

| Argument                    | Type    | Notes                                                                                                     |
| --------------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `id`                        | uuid    |                                                                                                           |
| `starred` (optional)        | boolean |                                                                                                           |
| `visitorBlocked` (optional) | boolean |                                                                                                           |
| `blockMessage` (optional)   | string  | What a blocked visitor is told. Only meaningful when blocking; unblocking clears it. up to 300 characters |
| `aiPaused` (optional)       | boolean | Paused, the assistant stops answering until you resume it. Manual only                                    |

#### `get_conversation` [#get_conversation]

The full transcript of one conversation, plus visitor context, signals and model cost. Ids come from list\_conversations.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

#### `list_conversations` [#list_conversations]

Overview of this workspace's conversations: the total, then the 10 most recent by activity (id, title, unread). Start here and stay zoomed out. Pass detail:'summaries' for a gist and triage signals per conversation, or get\_conversation for one full transcript.

| Argument            | Type                               | Notes                                                  |
| ------------------- | ---------------------------------- | ------------------------------------------------------ |
| `view` (optional)   | one of `all`, `unread`, `archived` | all (default, excludes archived) \| unread \| archived |
| `q` (optional)      | string                             | Search titles, messages and visitor emails             |
| `detail` (optional) | one of `titles`, `summaries`       | Default titles                                         |
| `page` (optional)   | integer                            | at least 1                                             |

#### `set_archived` [#set_archived]

Archive one conversation (out of the inbox) or restore it. Archiving is reversible and deletes nothing.

| Argument              | Type    | Notes        |
| --------------------- | ------- | ------------ |
| `id`                  | uuid    |              |
| `archived` (optional) | boolean | Default true |

#### `set_read` [#set_read]

Mark one conversation read/unread by id, or pass id:'all' to clear the whole inbox. 'all' only marks READ; there is no bulk unread.

| Argument          | Type          | Notes        |
| ----------------- | ------------- | ------------ |
| `id`              | uuid or `all` |              |
| `read` (optional) | boolean       | Default true |

#### `unread_count` [#unread_count]

How many conversations are unread (excludes archived). Takes no arguments.

Takes no arguments.

### Knowledge Base [#knowledge-base]

Everything the Add source form does, plus editing and removing what is already there.

#### `kb_add` [#kb_add]

Add a document by pasting its text. Chunked and embedded at once, so the assistant can use it on the next message. Costs pages from the plan's allowance.

| Argument | Type   | Notes                   |
| -------- | ------ | ----------------------- |
| `title`  | string | 1 to 200 characters     |
| `text`   | string | 1 to 200,000 characters |

#### `kb_add_file` [#kb_add_file]

Add a text-based PDF, Markdown or plain-text file, base64-encoded. Up to 2 MB decoded. A scanned, image-only PDF has no text to extract and is refused.

| Argument           | Type   | Notes                                                         |
| ------------------ | ------ | ------------------------------------------------------------- |
| `filename`         | string | Used to pick the extractor and the title. 1 to 255 characters |
| `contentBase64`    | string | at least 1 characters                                         |
| `title` (optional) | string | 1 to 200 characters                                           |

#### `kb_add_url` [#kb_add_url]

Fetch a public page and add its main content as a document. The address is checked against private networks before anything is fetched. Title defaults to the page's own.

| Argument           | Type   | Notes                  |
| ------------------ | ------ | ---------------------- |
| `url`              | URL    | up to 2,048 characters |
| `title` (optional) | string | 1 to 200 characters    |

#### `kb_get` [#kb_get]

One document's stored text, with its title, source and size in pages. Ids come from kb\_list. This is the text the assistant retrieves from.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

#### `kb_list` [#kb_list]

This workspace's Knowledge Base: how many pages of the plan's allowance are used, then one line per document (id, title, where it came from, status, chunks). No text. Use kb\_get for one document's content.

| Argument          | Type    | Notes      |
| ----------------- | ------- | ---------- |
| `page` (optional) | integer | at least 1 |

#### `kb_refresh` [#kb_refresh]

Bring one document up to date. A document added from a URL is fetched again from that address, overwriting any hand edits. Every other document is re-chunked and re-embedded from its stored text. Not for a site's posts, which their site syncs.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

#### `kb_remove` [#kb_remove]

Delete a document and its chunks. The assistant stops retrieving from it immediately, and past answers that cited it read as 'source removed'. A site's posts are removed by removing the site.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

#### `kb_update` [#kb_update]

Replace a document's title, its text, or both. Re-chunks and re-embeds. Omitted fields keep what is stored. A document synced from a connected site cannot be edited here.

| Argument           | Type   | Notes                   |
| ------------------ | ------ | ----------------------- |
| `id`               | uuid   |                         |
| `title` (optional) | string | 1 to 200 characters     |
| `text` (optional)  | string | 1 to 200,000 characters |

### Connected sites [#connected-sites]

Connect a blog, sync it, and disconnect it.

#### `site_connect` [#site_connect]

Connect a blog so its posts sync into the Knowledge Base. Posts are found from the site's sitemap, feed or llms.txt; pass manualIndexUrl (same host) if it has none. The first sync starts in the background. Read site\_list for its progress.

| Argument                    | Type                    | Notes                  |
| --------------------------- | ----------------------- | ---------------------- |
| `rootUrl`                   | URL                     | up to 2,048 characters |
| `pathPrefix` (optional)     | string matching `^\/.*` | up to 200 characters   |
| `manualIndexUrl` (optional) | URL                     | up to 2,048 characters |

#### `site_list` [#site_list]

Every site connected to this workspace: where it is, how its posts were found, how many it holds, and what its last sync did. Read this after site\_connect or site\_sync to see how the run ended.

Takes no arguments.

#### `site_remove` [#site_remove]

Disconnect a site and delete every post it synced. The assistant stops retrieving from them at once. Nothing else in the Knowledge Base is touched.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

#### `site_sync` [#site_sync]

Re-run one site's sync: fetch its index, then re-embed the posts that changed. Runs in the background and returns at once. Read site\_list for progress and the result.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

### Settings and the widget [#settings-and-the-widget]

The workspace's own configuration, and how the chat looks and behaves.

#### `settings_get` [#settings_get]

Everything settings\_update can change, as it is now, plus the plan and how much of its Knowledge Base allowance is used. The OpenRouter key is reported as configured or not, never as its value.

Takes no arguments.

#### `settings_update` [#settings_update]

Change any of the workspace's settings. Pass only the keys you want changed; the rest are left alone. Validation is all or nothing: if any key is rejected, nothing at all is written. guardrails and responseStyle are whole objects, and postSuggestions and postCards must be sent together.

| Argument                       | Type                                                                                        | Notes                                                                                                                                                                                      |
| ------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` (optional)              | string                                                                                      | The workspace's display name. 1 to 64 characters                                                                                                                                           |
| `websiteUrl` (optional)        | string                                                                                      | The workspace's own website as a full http(s) URL, or "" to clear it. Its icon is fetched too. up to 2,048 characters                                                                      |
| `timezone` (optional)          | string, or `""` to clear it                                                                 | An IANA name such as "Europe/Amsterdam", or "" for Auto. 1 to 64 characters                                                                                                                |
| `persona` (optional)           | string                                                                                      | How the assistant should behave. up to 8,000 characters                                                                                                                                    |
| `guardrails` (optional)        | object with `allowCode` (required), `allowCreative` (required), `allowGeneralQA` (required) | All three flags, always. Which deliverables the assistant may produce                                                                                                                      |
| `guardrails.allowCode`         | boolean                                                                                     |                                                                                                                                                                                            |
| `guardrails.allowCreative`     | boolean                                                                                     |                                                                                                                                                                                            |
| `guardrails.allowGeneralQA`    | boolean                                                                                     |                                                                                                                                                                                            |
| `responseStyle` (optional)     | object with `enabled` (required), `level` (required)                                        | Both fields, always. level 1 is briefest, 5 is fullest                                                                                                                                     |
| `responseStyle.enabled`        | boolean                                                                                     |                                                                                                                                                                                            |
| `responseStyle.level`          | one of `1`, `2`, `3`, `4`, `5`                                                              |                                                                                                                                                                                            |
| `resumeWindowHours` (optional) | number                                                                                      | How long a returning visitor picks up where they left off. One of: 1, 6, 24, 48, 168, 336 (hours)                                                                                          |
| `postSuggestions` (optional)   | one of `never`, `when_relevant`, `only_when_asked`                                          | Send with postCards                                                                                                                                                                        |
| `postCards` (optional)         | boolean                                                                                     | Send with postSuggestions                                                                                                                                                                  |
| `models` (optional)            | object with `chat`, `summary`, `extract`                                                    | A provider/model slug pins a task; null clears it back to the default                                                                                                                      |
| `models.chat` (optional)       | string matching `^[\w.-]+\/[\w.:-]+$`, or `null`                                            | 1 to 120 characters                                                                                                                                                                        |
| `models.summary` (optional)    | string matching `^[\w.-]+\/[\w.:-]+$`, or `null`                                            | 1 to 120 characters                                                                                                                                                                        |
| `models.extract` (optional)    | string matching `^[\w.-]+\/[\w.:-]+$`, or `null`                                            | 1 to 120 characters                                                                                                                                                                        |
| `openrouterKey` (optional)     | string, or `""` to clear it                                                                 | Write-only; it is never read back. "" REMOVES the key, which pauses the assistant for every visitor until a new one is saved. There is no server key to fall back to. 20 to 300 characters |

#### `widget_get` [#widget_get]

What visitors see and where the widget may load: its name, header, welcome message, suggested questions, booking link, showcase cards, colors, allowed origins, and the embed snippet to paste into a site.

Takes no arguments.

#### `widget_update` [#widget_update]

Change any of the widget's settings. Pass only what you want changed; everything else keeps its current value. An array (starters, showcase, allowedOrigins) is replaced whole, so pass the full list, and \[] really does clear it.

| Argument                       | Type                                                                                                                                                               | Notes                                                                                         |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `name` (optional)              | string                                                                                                                                                             | 1 to 80 characters                                                                            |
| `welcomeMessage` (optional)    | string                                                                                                                                                             | up to 500 characters                                                                          |
| `bookingUrl` (optional)        | URL, or `""` to clear it                                                                                                                                           | up to 2,048 characters                                                                        |
| `starters` (optional)          | array (up to 8) of string                                                                                                                                          |                                                                                               |
| `headerTitle` (optional)       | string                                                                                                                                                             | up to 80 characters                                                                           |
| `showHeader` (optional)        | boolean                                                                                                                                                            |                                                                                               |
| `chatOrb` (optional)           | boolean                                                                                                                                                            |                                                                                               |
| `colors` (optional)            | object with `light` (required), `dark` (required)                                                                                                                  | Both modes, each #rrggbb or ""                                                                |
| `colors.light`                 | object with `primary` (required), `userBubble` (required), `userBubbleForeground` (required), `assistantBubble` (required), `assistantBubbleForeground` (required) |                                                                                               |
| `colors.dark`                  | object with `primary` (required), `userBubble` (required), `userBubbleForeground` (required), `assistantBubble` (required), `assistantBubbleForeground` (required) |                                                                                               |
| `shader` (optional)            | string                                                                                                                                                             | The launcher orb's shader key. 1 to 64 characters                                             |
| `emailCapture` (optional)      | object with `contactReason` (required), `gapReason` (required)                                                                                                     |                                                                                               |
| `emailCapture.contactReason`   | string                                                                                                                                                             | up to 300 characters                                                                          |
| `emailCapture.gapReason`       | string                                                                                                                                                             | up to 300 characters                                                                          |
| `contactEmail` (optional)      | email, or `""` to clear it                                                                                                                                         | up to 200 characters                                                                          |
| `showcase` (optional)          | array (up to 6) of object with `title` (required), `description` (required), `imageUrl` (required), `url` (required)                                               |                                                                                               |
| `showcase[].title`             | string                                                                                                                                                             | 1 to 80 characters                                                                            |
| `showcase[].description`       | string                                                                                                                                                             | up to 140 characters                                                                          |
| `showcase[].imageUrl`          | URL or string matching `^\/(?!\/)\S*$`, or `""` to clear it                                                                                                        | up to 2,048 characters                                                                        |
| `showcase[].url`               | URL, or `""` to clear it                                                                                                                                           | up to 2,048 characters                                                                        |
| `allowedOrigins` (optional)    | array (up to 20) of string                                                                                                                                         | Sites the widget may be embedded on. Replaces the whole list; \[] blocks every external embed |
| `resumeWindowHours` (optional) | number                                                                                                                                                             |                                                                                               |

### API keys [#api-keys]

List and revoke the keys other clients connect with.

#### `key_list` [#key_list]

Every API key for this workspace, oldest first: label, when it was made, when it was last used, and whether it is revoked. Key values are never stored and never shown. Issue a new key from the dashboard's Integrations page.

Takes no arguments.

#### `key_revoke` [#key_revoke]

Revoke one API key by id, from key\_list. The row survives as a record, with its revoke date. Revoking the key you are calling with is allowed, and ends this session's access immediately.

| Argument | Type | Notes |
| -------- | ---- | ----- |
| `id`     | uuid |       |

### Everything else [#everything-else]

A connectivity check, and what this workspace has spent.

#### `ping` [#ping]

Check the Sondo connection and report which workspace this API key opens. Takes no arguments.

Takes no arguments.

#### `usage_summary` [#usage_summary]

Model spend in USD, bucketed by day (last 30), week (12) or month (12). UTC calendar days.

| Argument          | Type                          | Notes |
| ----------------- | ----------------------------- | ----- |
| `unit` (optional) | one of `day`, `week`, `month` |       |
