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

# MCP tools

> What the Ryvo MCP server can do, which scope each tool needs, how they count against your rate limit and how they report errors.

The server exposes 27 curated tools, not one tool per API endpoint. Names are in English, like the API.

## Your key decides what the assistant sees

* **`tools/list` only returns the tools the key's scopes allow.** With a read-only key the assistant does not even know `create_call` exists.
* **Every `tools/call` checks the scope again.** A client that calls a tool it was not shown gets an `insufficient_scope` error, not the result.

To change what an assistant can do, change the key: create a new one with other scopes in **Settings > API keys** and swap it in your client's configuration. Scopes are explained in [Authentication](/authentication#scopes).

## Tool catalog

| Tool | Scope | Notes |
| - | - | - |
| `get_account` | none (any valid key) | Your account, your plan and the key's scopes |
| `list_agents`, `get_agent` | `agents:read` | Filters: runtime, status |
| `list_calls` | `calls:read` | Filters: status, agent, from/to dates, phone, channel. No transcripts |
| `get_call` | `calls:read` | Includes the transcript, wrapped as [untrusted content](/mcp/security#transcripts-are-untrusted-content) |
| `get_call_stats` | `calls:read` | Totals by status, duration and credits, overall and by agent, for a date range (default: last 30 days), in one call |
| `list_conversations`, `get_conversation` | `conversations:read` | Calls and chats. `get_conversation` wraps the messages as untrusted content |
| `list_knowledge` | `knowledge:read` | Filters: folder, status, source |
| `list_phone_numbers` | `phone_numbers:read` | |
| `list_campaigns`, `get_campaign` | `campaigns:read` | Filter: status. `get_campaign` includes each recipient's status |
| `list_webhooks`, `list_webhook_deliveries` | `webhooks:read` | Deliveries filter: status, event |
| `get_billing_summary` | `billing:read` | Plan, credit balance and daily usage (default: last 30 days) |
| `list_voices` | `voices:read` | Filters: provider, language, gender, name, recommended |
| `list_models` | none (any valid key) | The language models and their price per plan |
| `create_agent`, `update_agent`, `publish_agent` | `agents:write` | |
| `add_knowledge_document` | `knowledge:write` | From a public URL or from plain text |
| `assign_phone_number` | `phone_numbers:write` | Assign a number to an agent, or unassign it |
| `create_webhook`, `retry_webhook_delivery` | `webhooks:write` | `create_webhook` returns the signing secret once |
| `create_call` | `calls:write` | **Places a real call and spends credits.** Asks for confirmation |
| `create_batch_call`, `cancel_batch_call` | `campaigns:write` | **Launches or stops a campaign.** Asks for confirmation |

Read tools carry the MCP `readOnlyHint` annotation. The three tools that act on real calls carry `destructiveHint` and `openWorldHint`, so clients that honor annotations can ask you before running them; `update_agent`, `publish_agent` and `assign_phone_number` carry `destructiveHint` too, because they change what live calls use. How the confirmation of the three works is in [Security and spending](/mcp/security).

## Ask for summaries, not pages

The tools are built so an assistant gets its answer in few calls:

* List tools return **50 items by default**, up to 100 per call, accept the filters in the table and return a `next_cursor` for the next page.
* Lists leave out heavy fields: `list_calls` does not include transcripts. Ask for one call with `get_call` when you need what was said.
* **`get_call_stats`** answers aggregate questions ("how many calls failed yesterday?", "which agent spent the most this week?") in **one call**, instead of paging through hundreds of calls. It counts up to 1,000 calls per range; if there were more, it says so with `truncated: true` and the real total in `calls_in_range`, so the assistant can narrow the range.

## Rate limits

The MCP has no separate limit: it draws from your account's bucket, the same one the API uses.

* **Every tool call counts as one request** against your plan's per-minute limit, once, never more.
* `initialize`, `tools/list` and the rest of the protocol do not count.
* For the three tools that ask for confirmation, only the call that runs counts. The confirmation step does not.
* The per-IP check of the API applies to every request, including `initialize` and `tools/list`.

The numbers per plan and how the bucket refills are in [Rate limits](/rate-limits). An assistant that pages through calls one by one burns the bucket quickly; one that asks `get_call_stats` does not.

## Errors

When a tool fails, the server does not break the MCP session: it returns a normal tool result with **`isError: true`** and the same envelope as the API, both as `structuredContent` and as text:

```json theme={null}
{
  "error": "insufficient_scope",
  "message": "This API key is missing the `calls:write` scope. Create a key with that permission from Settings › API keys.",
  "request_id": "f0c0a9d2-8b1e-4d8a-9c2f-6e5b7a1d2c3b"
}
```

The assistant can read `error` and decide what to do: wait after `rate_limit_exceeded`, tell you the key needs another scope after `insufficient_scope`, fix an argument after `invalid_body`, get a new token after `invalid_confirmation`, or stop after `key_budget_exhausted`. The full list of codes is in [Errors](/errors). If you report a problem, send us the `request_id`.

A successful result carries its JSON the same way: in `structuredContent` and as text.

A missing, invalid or revoked key never reaches the tools: the HTTP request itself is refused with `401`.
